##// END OF EJS Templates
ui: check EOF of getpass() response read from command-server channel...
Yuya Nishihara -
r30814:b96c57c1 default
parent child Browse files
Show More
@@ -1,1407 +1,1410 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 contextlib
10 import contextlib
11 import errno
11 import errno
12 import getpass
12 import getpass
13 import inspect
13 import inspect
14 import os
14 import os
15 import re
15 import re
16 import socket
16 import socket
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import traceback
19 import traceback
20
20
21 from .i18n import _
21 from .i18n import _
22 from .node import hex
22 from .node import hex
23
23
24 from . import (
24 from . import (
25 config,
25 config,
26 encoding,
26 encoding,
27 error,
27 error,
28 formatter,
28 formatter,
29 progress,
29 progress,
30 pycompat,
30 pycompat,
31 scmutil,
31 scmutil,
32 util,
32 util,
33 )
33 )
34
34
35 urlreq = util.urlreq
35 urlreq = util.urlreq
36
36
37 samplehgrcs = {
37 samplehgrcs = {
38 'user':
38 'user':
39 """# example user config (see 'hg help config' for more info)
39 """# example user config (see 'hg help config' for more info)
40 [ui]
40 [ui]
41 # name and email, e.g.
41 # name and email, e.g.
42 # username = Jane Doe <jdoe@example.com>
42 # username = Jane Doe <jdoe@example.com>
43 username =
43 username =
44
44
45 [extensions]
45 [extensions]
46 # uncomment these lines to enable some popular extensions
46 # uncomment these lines to enable some popular extensions
47 # (see 'hg help extensions' for more info)
47 # (see 'hg help extensions' for more info)
48 #
48 #
49 # pager =
49 # pager =
50 # color =""",
50 # color =""",
51
51
52 'cloned':
52 'cloned':
53 """# example repository config (see 'hg help config' for more info)
53 """# example repository config (see 'hg help config' for more info)
54 [paths]
54 [paths]
55 default = %s
55 default = %s
56
56
57 # path aliases to other clones of this repo in URLs or filesystem paths
57 # path aliases to other clones of this repo in URLs or filesystem paths
58 # (see 'hg help config.paths' for more info)
58 # (see 'hg help config.paths' for more info)
59 #
59 #
60 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
60 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
61 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
61 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
62 # my-clone = /home/jdoe/jdoes-clone
62 # my-clone = /home/jdoe/jdoes-clone
63
63
64 [ui]
64 [ui]
65 # name and email (local to this repository, optional), e.g.
65 # name and email (local to this repository, optional), e.g.
66 # username = Jane Doe <jdoe@example.com>
66 # username = Jane Doe <jdoe@example.com>
67 """,
67 """,
68
68
69 'local':
69 'local':
70 """# example repository config (see 'hg help config' for more info)
70 """# example repository config (see 'hg help config' for more info)
71 [paths]
71 [paths]
72 # path aliases to other clones of this repo in URLs or filesystem paths
72 # path aliases to other clones of this repo in URLs or filesystem paths
73 # (see 'hg help config.paths' for more info)
73 # (see 'hg help config.paths' for more info)
74 #
74 #
75 # default = http://example.com/hg/example-repo
75 # default = http://example.com/hg/example-repo
76 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
76 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
77 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
77 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
78 # my-clone = /home/jdoe/jdoes-clone
78 # my-clone = /home/jdoe/jdoes-clone
79
79
80 [ui]
80 [ui]
81 # name and email (local to this repository, optional), e.g.
81 # name and email (local to this repository, optional), e.g.
82 # username = Jane Doe <jdoe@example.com>
82 # username = Jane Doe <jdoe@example.com>
83 """,
83 """,
84
84
85 'global':
85 'global':
86 """# example system-wide hg config (see 'hg help config' for more info)
86 """# example system-wide hg config (see 'hg help config' for more info)
87
87
88 [extensions]
88 [extensions]
89 # uncomment these lines to enable some popular extensions
89 # uncomment these lines to enable some popular extensions
90 # (see 'hg help extensions' for more info)
90 # (see 'hg help extensions' for more info)
91 #
91 #
92 # blackbox =
92 # blackbox =
93 # color =
93 # color =
94 # pager =""",
94 # pager =""",
95 }
95 }
96
96
97 class ui(object):
97 class ui(object):
98 def __init__(self, src=None):
98 def __init__(self, src=None):
99 """Create a fresh new ui object if no src given
99 """Create a fresh new ui object if no src given
100
100
101 Use uimod.ui.load() to create a ui which knows global and user configs.
101 Use uimod.ui.load() to create a ui which knows global and user configs.
102 In most cases, you should use ui.copy() to create a copy of an existing
102 In most cases, you should use ui.copy() to create a copy of an existing
103 ui object.
103 ui object.
104 """
104 """
105 # _buffers: used for temporary capture of output
105 # _buffers: used for temporary capture of output
106 self._buffers = []
106 self._buffers = []
107 # 3-tuple describing how each buffer in the stack behaves.
107 # 3-tuple describing how each buffer in the stack behaves.
108 # Values are (capture stderr, capture subprocesses, apply labels).
108 # Values are (capture stderr, capture subprocesses, apply labels).
109 self._bufferstates = []
109 self._bufferstates = []
110 # When a buffer is active, defines whether we are expanding labels.
110 # When a buffer is active, defines whether we are expanding labels.
111 # This exists to prevent an extra list lookup.
111 # This exists to prevent an extra list lookup.
112 self._bufferapplylabels = None
112 self._bufferapplylabels = None
113 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
113 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
114 self._reportuntrusted = True
114 self._reportuntrusted = True
115 self._ocfg = config.config() # overlay
115 self._ocfg = config.config() # overlay
116 self._tcfg = config.config() # trusted
116 self._tcfg = config.config() # trusted
117 self._ucfg = config.config() # untrusted
117 self._ucfg = config.config() # untrusted
118 self._trustusers = set()
118 self._trustusers = set()
119 self._trustgroups = set()
119 self._trustgroups = set()
120 self.callhooks = True
120 self.callhooks = True
121 # Insecure server connections requested.
121 # Insecure server connections requested.
122 self.insecureconnections = False
122 self.insecureconnections = False
123
123
124 if src:
124 if src:
125 self.fout = src.fout
125 self.fout = src.fout
126 self.ferr = src.ferr
126 self.ferr = src.ferr
127 self.fin = src.fin
127 self.fin = src.fin
128
128
129 self._tcfg = src._tcfg.copy()
129 self._tcfg = src._tcfg.copy()
130 self._ucfg = src._ucfg.copy()
130 self._ucfg = src._ucfg.copy()
131 self._ocfg = src._ocfg.copy()
131 self._ocfg = src._ocfg.copy()
132 self._trustusers = src._trustusers.copy()
132 self._trustusers = src._trustusers.copy()
133 self._trustgroups = src._trustgroups.copy()
133 self._trustgroups = src._trustgroups.copy()
134 self.environ = src.environ
134 self.environ = src.environ
135 self.callhooks = src.callhooks
135 self.callhooks = src.callhooks
136 self.insecureconnections = src.insecureconnections
136 self.insecureconnections = src.insecureconnections
137 self.fixconfig()
137 self.fixconfig()
138
138
139 self.httppasswordmgrdb = src.httppasswordmgrdb
139 self.httppasswordmgrdb = src.httppasswordmgrdb
140 else:
140 else:
141 self.fout = util.stdout
141 self.fout = util.stdout
142 self.ferr = util.stderr
142 self.ferr = util.stderr
143 self.fin = util.stdin
143 self.fin = util.stdin
144
144
145 # shared read-only environment
145 # shared read-only environment
146 self.environ = encoding.environ
146 self.environ = encoding.environ
147
147
148 self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm()
148 self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm()
149
149
150 @classmethod
150 @classmethod
151 def load(cls):
151 def load(cls):
152 """Create a ui and load global and user configs"""
152 """Create a ui and load global and user configs"""
153 u = cls()
153 u = cls()
154 # we always trust global config files
154 # we always trust global config files
155 for f in scmutil.rcpath():
155 for f in scmutil.rcpath():
156 u.readconfig(f, trust=True)
156 u.readconfig(f, trust=True)
157 return u
157 return u
158
158
159 def copy(self):
159 def copy(self):
160 return self.__class__(self)
160 return self.__class__(self)
161
161
162 def resetstate(self):
162 def resetstate(self):
163 """Clear internal state that shouldn't persist across commands"""
163 """Clear internal state that shouldn't persist across commands"""
164 if self._progbar:
164 if self._progbar:
165 self._progbar.resetstate() # reset last-print time of progress bar
165 self._progbar.resetstate() # reset last-print time of progress bar
166 self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm()
166 self.httppasswordmgrdb = urlreq.httppasswordmgrwithdefaultrealm()
167
167
168 def formatter(self, topic, opts):
168 def formatter(self, topic, opts):
169 return formatter.formatter(self, topic, opts)
169 return formatter.formatter(self, topic, opts)
170
170
171 def _trusted(self, fp, f):
171 def _trusted(self, fp, f):
172 st = util.fstat(fp)
172 st = util.fstat(fp)
173 if util.isowner(st):
173 if util.isowner(st):
174 return True
174 return True
175
175
176 tusers, tgroups = self._trustusers, self._trustgroups
176 tusers, tgroups = self._trustusers, self._trustgroups
177 if '*' in tusers or '*' in tgroups:
177 if '*' in tusers or '*' in tgroups:
178 return True
178 return True
179
179
180 user = util.username(st.st_uid)
180 user = util.username(st.st_uid)
181 group = util.groupname(st.st_gid)
181 group = util.groupname(st.st_gid)
182 if user in tusers or group in tgroups or user == util.username():
182 if user in tusers or group in tgroups or user == util.username():
183 return True
183 return True
184
184
185 if self._reportuntrusted:
185 if self._reportuntrusted:
186 self.warn(_('not trusting file %s from untrusted '
186 self.warn(_('not trusting file %s from untrusted '
187 'user %s, group %s\n') % (f, user, group))
187 'user %s, group %s\n') % (f, user, group))
188 return False
188 return False
189
189
190 def readconfig(self, filename, root=None, trust=False,
190 def readconfig(self, filename, root=None, trust=False,
191 sections=None, remap=None):
191 sections=None, remap=None):
192 try:
192 try:
193 fp = open(filename, u'rb')
193 fp = open(filename, u'rb')
194 except IOError:
194 except IOError:
195 if not sections: # ignore unless we were looking for something
195 if not sections: # ignore unless we were looking for something
196 return
196 return
197 raise
197 raise
198
198
199 cfg = config.config()
199 cfg = config.config()
200 trusted = sections or trust or self._trusted(fp, filename)
200 trusted = sections or trust or self._trusted(fp, filename)
201
201
202 try:
202 try:
203 cfg.read(filename, fp, sections=sections, remap=remap)
203 cfg.read(filename, fp, sections=sections, remap=remap)
204 fp.close()
204 fp.close()
205 except error.ConfigError as inst:
205 except error.ConfigError as inst:
206 if trusted:
206 if trusted:
207 raise
207 raise
208 self.warn(_("ignored: %s\n") % str(inst))
208 self.warn(_("ignored: %s\n") % str(inst))
209
209
210 if self.plain():
210 if self.plain():
211 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
211 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
212 'logtemplate', 'statuscopies', 'style',
212 'logtemplate', 'statuscopies', 'style',
213 'traceback', 'verbose'):
213 'traceback', 'verbose'):
214 if k in cfg['ui']:
214 if k in cfg['ui']:
215 del cfg['ui'][k]
215 del cfg['ui'][k]
216 for k, v in cfg.items('defaults'):
216 for k, v in cfg.items('defaults'):
217 del cfg['defaults'][k]
217 del cfg['defaults'][k]
218 # Don't remove aliases from the configuration if in the exceptionlist
218 # Don't remove aliases from the configuration if in the exceptionlist
219 if self.plain('alias'):
219 if self.plain('alias'):
220 for k, v in cfg.items('alias'):
220 for k, v in cfg.items('alias'):
221 del cfg['alias'][k]
221 del cfg['alias'][k]
222 if self.plain('revsetalias'):
222 if self.plain('revsetalias'):
223 for k, v in cfg.items('revsetalias'):
223 for k, v in cfg.items('revsetalias'):
224 del cfg['revsetalias'][k]
224 del cfg['revsetalias'][k]
225 if self.plain('templatealias'):
225 if self.plain('templatealias'):
226 for k, v in cfg.items('templatealias'):
226 for k, v in cfg.items('templatealias'):
227 del cfg['templatealias'][k]
227 del cfg['templatealias'][k]
228
228
229 if trusted:
229 if trusted:
230 self._tcfg.update(cfg)
230 self._tcfg.update(cfg)
231 self._tcfg.update(self._ocfg)
231 self._tcfg.update(self._ocfg)
232 self._ucfg.update(cfg)
232 self._ucfg.update(cfg)
233 self._ucfg.update(self._ocfg)
233 self._ucfg.update(self._ocfg)
234
234
235 if root is None:
235 if root is None:
236 root = os.path.expanduser('~')
236 root = os.path.expanduser('~')
237 self.fixconfig(root=root)
237 self.fixconfig(root=root)
238
238
239 def fixconfig(self, root=None, section=None):
239 def fixconfig(self, root=None, section=None):
240 if section in (None, 'paths'):
240 if section in (None, 'paths'):
241 # expand vars and ~
241 # expand vars and ~
242 # translate paths relative to root (or home) into absolute paths
242 # translate paths relative to root (or home) into absolute paths
243 root = root or pycompat.getcwd()
243 root = root or pycompat.getcwd()
244 for c in self._tcfg, self._ucfg, self._ocfg:
244 for c in self._tcfg, self._ucfg, self._ocfg:
245 for n, p in c.items('paths'):
245 for n, p in c.items('paths'):
246 # Ignore sub-options.
246 # Ignore sub-options.
247 if ':' in n:
247 if ':' in n:
248 continue
248 continue
249 if not p:
249 if not p:
250 continue
250 continue
251 if '%%' in p:
251 if '%%' in p:
252 s = self.configsource('paths', n) or 'none'
252 s = self.configsource('paths', n) or 'none'
253 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
253 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
254 % (n, p, s))
254 % (n, p, s))
255 p = p.replace('%%', '%')
255 p = p.replace('%%', '%')
256 p = util.expandpath(p)
256 p = util.expandpath(p)
257 if not util.hasscheme(p) and not os.path.isabs(p):
257 if not util.hasscheme(p) and not os.path.isabs(p):
258 p = os.path.normpath(os.path.join(root, p))
258 p = os.path.normpath(os.path.join(root, p))
259 c.set("paths", n, p)
259 c.set("paths", n, p)
260
260
261 if section in (None, 'ui'):
261 if section in (None, 'ui'):
262 # update ui options
262 # update ui options
263 self.debugflag = self.configbool('ui', 'debug')
263 self.debugflag = self.configbool('ui', 'debug')
264 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
264 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
265 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
265 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
266 if self.verbose and self.quiet:
266 if self.verbose and self.quiet:
267 self.quiet = self.verbose = False
267 self.quiet = self.verbose = False
268 self._reportuntrusted = self.debugflag or self.configbool("ui",
268 self._reportuntrusted = self.debugflag or self.configbool("ui",
269 "report_untrusted", True)
269 "report_untrusted", True)
270 self.tracebackflag = self.configbool('ui', 'traceback', False)
270 self.tracebackflag = self.configbool('ui', 'traceback', False)
271
271
272 if section in (None, 'trusted'):
272 if section in (None, 'trusted'):
273 # update trust information
273 # update trust information
274 self._trustusers.update(self.configlist('trusted', 'users'))
274 self._trustusers.update(self.configlist('trusted', 'users'))
275 self._trustgroups.update(self.configlist('trusted', 'groups'))
275 self._trustgroups.update(self.configlist('trusted', 'groups'))
276
276
277 def backupconfig(self, section, item):
277 def backupconfig(self, section, item):
278 return (self._ocfg.backup(section, item),
278 return (self._ocfg.backup(section, item),
279 self._tcfg.backup(section, item),
279 self._tcfg.backup(section, item),
280 self._ucfg.backup(section, item),)
280 self._ucfg.backup(section, item),)
281 def restoreconfig(self, data):
281 def restoreconfig(self, data):
282 self._ocfg.restore(data[0])
282 self._ocfg.restore(data[0])
283 self._tcfg.restore(data[1])
283 self._tcfg.restore(data[1])
284 self._ucfg.restore(data[2])
284 self._ucfg.restore(data[2])
285
285
286 def setconfig(self, section, name, value, source=''):
286 def setconfig(self, section, name, value, source=''):
287 for cfg in (self._ocfg, self._tcfg, self._ucfg):
287 for cfg in (self._ocfg, self._tcfg, self._ucfg):
288 cfg.set(section, name, value, source)
288 cfg.set(section, name, value, source)
289 self.fixconfig(section=section)
289 self.fixconfig(section=section)
290
290
291 def _data(self, untrusted):
291 def _data(self, untrusted):
292 return untrusted and self._ucfg or self._tcfg
292 return untrusted and self._ucfg or self._tcfg
293
293
294 def configsource(self, section, name, untrusted=False):
294 def configsource(self, section, name, untrusted=False):
295 return self._data(untrusted).source(section, name)
295 return self._data(untrusted).source(section, name)
296
296
297 def config(self, section, name, default=None, untrusted=False):
297 def config(self, section, name, default=None, untrusted=False):
298 if isinstance(name, list):
298 if isinstance(name, list):
299 alternates = name
299 alternates = name
300 else:
300 else:
301 alternates = [name]
301 alternates = [name]
302
302
303 for n in alternates:
303 for n in alternates:
304 value = self._data(untrusted).get(section, n, None)
304 value = self._data(untrusted).get(section, n, None)
305 if value is not None:
305 if value is not None:
306 name = n
306 name = n
307 break
307 break
308 else:
308 else:
309 value = default
309 value = default
310
310
311 if self.debugflag and not untrusted and self._reportuntrusted:
311 if self.debugflag and not untrusted and self._reportuntrusted:
312 for n in alternates:
312 for n in alternates:
313 uvalue = self._ucfg.get(section, n)
313 uvalue = self._ucfg.get(section, n)
314 if uvalue is not None and uvalue != value:
314 if uvalue is not None and uvalue != value:
315 self.debug("ignoring untrusted configuration option "
315 self.debug("ignoring untrusted configuration option "
316 "%s.%s = %s\n" % (section, n, uvalue))
316 "%s.%s = %s\n" % (section, n, uvalue))
317 return value
317 return value
318
318
319 def configsuboptions(self, section, name, default=None, untrusted=False):
319 def configsuboptions(self, section, name, default=None, untrusted=False):
320 """Get a config option and all sub-options.
320 """Get a config option and all sub-options.
321
321
322 Some config options have sub-options that are declared with the
322 Some config options have sub-options that are declared with the
323 format "key:opt = value". This method is used to return the main
323 format "key:opt = value". This method is used to return the main
324 option and all its declared sub-options.
324 option and all its declared sub-options.
325
325
326 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
326 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
327 is a dict of defined sub-options where keys and values are strings.
327 is a dict of defined sub-options where keys and values are strings.
328 """
328 """
329 data = self._data(untrusted)
329 data = self._data(untrusted)
330 main = data.get(section, name, default)
330 main = data.get(section, name, default)
331 if self.debugflag and not untrusted and self._reportuntrusted:
331 if self.debugflag and not untrusted and self._reportuntrusted:
332 uvalue = self._ucfg.get(section, name)
332 uvalue = self._ucfg.get(section, name)
333 if uvalue is not None and uvalue != main:
333 if uvalue is not None and uvalue != main:
334 self.debug('ignoring untrusted configuration option '
334 self.debug('ignoring untrusted configuration option '
335 '%s.%s = %s\n' % (section, name, uvalue))
335 '%s.%s = %s\n' % (section, name, uvalue))
336
336
337 sub = {}
337 sub = {}
338 prefix = '%s:' % name
338 prefix = '%s:' % name
339 for k, v in data.items(section):
339 for k, v in data.items(section):
340 if k.startswith(prefix):
340 if k.startswith(prefix):
341 sub[k[len(prefix):]] = v
341 sub[k[len(prefix):]] = v
342
342
343 if self.debugflag and not untrusted and self._reportuntrusted:
343 if self.debugflag and not untrusted and self._reportuntrusted:
344 for k, v in sub.items():
344 for k, v in sub.items():
345 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
345 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
346 if uvalue is not None and uvalue != v:
346 if uvalue is not None and uvalue != v:
347 self.debug('ignoring untrusted configuration option '
347 self.debug('ignoring untrusted configuration option '
348 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
348 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
349
349
350 return main, sub
350 return main, sub
351
351
352 def configpath(self, section, name, default=None, untrusted=False):
352 def configpath(self, section, name, default=None, untrusted=False):
353 'get a path config item, expanded relative to repo root or config file'
353 'get a path config item, expanded relative to repo root or config file'
354 v = self.config(section, name, default, untrusted)
354 v = self.config(section, name, default, untrusted)
355 if v is None:
355 if v is None:
356 return None
356 return None
357 if not os.path.isabs(v) or "://" not in v:
357 if not os.path.isabs(v) or "://" not in v:
358 src = self.configsource(section, name, untrusted)
358 src = self.configsource(section, name, untrusted)
359 if ':' in src:
359 if ':' in src:
360 base = os.path.dirname(src.rsplit(':')[0])
360 base = os.path.dirname(src.rsplit(':')[0])
361 v = os.path.join(base, os.path.expanduser(v))
361 v = os.path.join(base, os.path.expanduser(v))
362 return v
362 return v
363
363
364 def configbool(self, section, name, default=False, untrusted=False):
364 def configbool(self, section, name, default=False, untrusted=False):
365 """parse a configuration element as a boolean
365 """parse a configuration element as a boolean
366
366
367 >>> u = ui(); s = 'foo'
367 >>> u = ui(); s = 'foo'
368 >>> u.setconfig(s, 'true', 'yes')
368 >>> u.setconfig(s, 'true', 'yes')
369 >>> u.configbool(s, 'true')
369 >>> u.configbool(s, 'true')
370 True
370 True
371 >>> u.setconfig(s, 'false', 'no')
371 >>> u.setconfig(s, 'false', 'no')
372 >>> u.configbool(s, 'false')
372 >>> u.configbool(s, 'false')
373 False
373 False
374 >>> u.configbool(s, 'unknown')
374 >>> u.configbool(s, 'unknown')
375 False
375 False
376 >>> u.configbool(s, 'unknown', True)
376 >>> u.configbool(s, 'unknown', True)
377 True
377 True
378 >>> u.setconfig(s, 'invalid', 'somevalue')
378 >>> u.setconfig(s, 'invalid', 'somevalue')
379 >>> u.configbool(s, 'invalid')
379 >>> u.configbool(s, 'invalid')
380 Traceback (most recent call last):
380 Traceback (most recent call last):
381 ...
381 ...
382 ConfigError: foo.invalid is not a boolean ('somevalue')
382 ConfigError: foo.invalid is not a boolean ('somevalue')
383 """
383 """
384
384
385 v = self.config(section, name, None, untrusted)
385 v = self.config(section, name, None, untrusted)
386 if v is None:
386 if v is None:
387 return default
387 return default
388 if isinstance(v, bool):
388 if isinstance(v, bool):
389 return v
389 return v
390 b = util.parsebool(v)
390 b = util.parsebool(v)
391 if b is None:
391 if b is None:
392 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
392 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
393 % (section, name, v))
393 % (section, name, v))
394 return b
394 return b
395
395
396 def configint(self, section, name, default=None, untrusted=False):
396 def configint(self, section, name, default=None, untrusted=False):
397 """parse a configuration element as an integer
397 """parse a configuration element as an integer
398
398
399 >>> u = ui(); s = 'foo'
399 >>> u = ui(); s = 'foo'
400 >>> u.setconfig(s, 'int1', '42')
400 >>> u.setconfig(s, 'int1', '42')
401 >>> u.configint(s, 'int1')
401 >>> u.configint(s, 'int1')
402 42
402 42
403 >>> u.setconfig(s, 'int2', '-42')
403 >>> u.setconfig(s, 'int2', '-42')
404 >>> u.configint(s, 'int2')
404 >>> u.configint(s, 'int2')
405 -42
405 -42
406 >>> u.configint(s, 'unknown', 7)
406 >>> u.configint(s, 'unknown', 7)
407 7
407 7
408 >>> u.setconfig(s, 'invalid', 'somevalue')
408 >>> u.setconfig(s, 'invalid', 'somevalue')
409 >>> u.configint(s, 'invalid')
409 >>> u.configint(s, 'invalid')
410 Traceback (most recent call last):
410 Traceback (most recent call last):
411 ...
411 ...
412 ConfigError: foo.invalid is not an integer ('somevalue')
412 ConfigError: foo.invalid is not an integer ('somevalue')
413 """
413 """
414
414
415 v = self.config(section, name, None, untrusted)
415 v = self.config(section, name, None, untrusted)
416 if v is None:
416 if v is None:
417 return default
417 return default
418 try:
418 try:
419 return int(v)
419 return int(v)
420 except ValueError:
420 except ValueError:
421 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
421 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
422 % (section, name, v))
422 % (section, name, v))
423
423
424 def configbytes(self, section, name, default=0, untrusted=False):
424 def configbytes(self, section, name, default=0, untrusted=False):
425 """parse a configuration element as a quantity in bytes
425 """parse a configuration element as a quantity in bytes
426
426
427 Units can be specified as b (bytes), k or kb (kilobytes), m or
427 Units can be specified as b (bytes), k or kb (kilobytes), m or
428 mb (megabytes), g or gb (gigabytes).
428 mb (megabytes), g or gb (gigabytes).
429
429
430 >>> u = ui(); s = 'foo'
430 >>> u = ui(); s = 'foo'
431 >>> u.setconfig(s, 'val1', '42')
431 >>> u.setconfig(s, 'val1', '42')
432 >>> u.configbytes(s, 'val1')
432 >>> u.configbytes(s, 'val1')
433 42
433 42
434 >>> u.setconfig(s, 'val2', '42.5 kb')
434 >>> u.setconfig(s, 'val2', '42.5 kb')
435 >>> u.configbytes(s, 'val2')
435 >>> u.configbytes(s, 'val2')
436 43520
436 43520
437 >>> u.configbytes(s, 'unknown', '7 MB')
437 >>> u.configbytes(s, 'unknown', '7 MB')
438 7340032
438 7340032
439 >>> u.setconfig(s, 'invalid', 'somevalue')
439 >>> u.setconfig(s, 'invalid', 'somevalue')
440 >>> u.configbytes(s, 'invalid')
440 >>> u.configbytes(s, 'invalid')
441 Traceback (most recent call last):
441 Traceback (most recent call last):
442 ...
442 ...
443 ConfigError: foo.invalid is not a byte quantity ('somevalue')
443 ConfigError: foo.invalid is not a byte quantity ('somevalue')
444 """
444 """
445
445
446 value = self.config(section, name)
446 value = self.config(section, name)
447 if value is None:
447 if value is None:
448 if not isinstance(default, str):
448 if not isinstance(default, str):
449 return default
449 return default
450 value = default
450 value = default
451 try:
451 try:
452 return util.sizetoint(value)
452 return util.sizetoint(value)
453 except error.ParseError:
453 except error.ParseError:
454 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
454 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
455 % (section, name, value))
455 % (section, name, value))
456
456
457 def configlist(self, section, name, default=None, untrusted=False):
457 def configlist(self, section, name, default=None, untrusted=False):
458 """parse a configuration element as a list of comma/space separated
458 """parse a configuration element as a list of comma/space separated
459 strings
459 strings
460
460
461 >>> u = ui(); s = 'foo'
461 >>> u = ui(); s = 'foo'
462 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
462 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
463 >>> u.configlist(s, 'list1')
463 >>> u.configlist(s, 'list1')
464 ['this', 'is', 'a small', 'test']
464 ['this', 'is', 'a small', 'test']
465 """
465 """
466
466
467 def _parse_plain(parts, s, offset):
467 def _parse_plain(parts, s, offset):
468 whitespace = False
468 whitespace = False
469 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
469 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
470 whitespace = True
470 whitespace = True
471 offset += 1
471 offset += 1
472 if offset >= len(s):
472 if offset >= len(s):
473 return None, parts, offset
473 return None, parts, offset
474 if whitespace:
474 if whitespace:
475 parts.append('')
475 parts.append('')
476 if s[offset] == '"' and not parts[-1]:
476 if s[offset] == '"' and not parts[-1]:
477 return _parse_quote, parts, offset + 1
477 return _parse_quote, parts, offset + 1
478 elif s[offset] == '"' and parts[-1][-1] == '\\':
478 elif s[offset] == '"' and parts[-1][-1] == '\\':
479 parts[-1] = parts[-1][:-1] + s[offset]
479 parts[-1] = parts[-1][:-1] + s[offset]
480 return _parse_plain, parts, offset + 1
480 return _parse_plain, parts, offset + 1
481 parts[-1] += s[offset]
481 parts[-1] += s[offset]
482 return _parse_plain, parts, offset + 1
482 return _parse_plain, parts, offset + 1
483
483
484 def _parse_quote(parts, s, offset):
484 def _parse_quote(parts, s, offset):
485 if offset < len(s) and s[offset] == '"': # ""
485 if offset < len(s) and s[offset] == '"': # ""
486 parts.append('')
486 parts.append('')
487 offset += 1
487 offset += 1
488 while offset < len(s) and (s[offset].isspace() or
488 while offset < len(s) and (s[offset].isspace() or
489 s[offset] == ','):
489 s[offset] == ','):
490 offset += 1
490 offset += 1
491 return _parse_plain, parts, offset
491 return _parse_plain, parts, offset
492
492
493 while offset < len(s) and s[offset] != '"':
493 while offset < len(s) and s[offset] != '"':
494 if (s[offset] == '\\' and offset + 1 < len(s)
494 if (s[offset] == '\\' and offset + 1 < len(s)
495 and s[offset + 1] == '"'):
495 and s[offset + 1] == '"'):
496 offset += 1
496 offset += 1
497 parts[-1] += '"'
497 parts[-1] += '"'
498 else:
498 else:
499 parts[-1] += s[offset]
499 parts[-1] += s[offset]
500 offset += 1
500 offset += 1
501
501
502 if offset >= len(s):
502 if offset >= len(s):
503 real_parts = _configlist(parts[-1])
503 real_parts = _configlist(parts[-1])
504 if not real_parts:
504 if not real_parts:
505 parts[-1] = '"'
505 parts[-1] = '"'
506 else:
506 else:
507 real_parts[0] = '"' + real_parts[0]
507 real_parts[0] = '"' + real_parts[0]
508 parts = parts[:-1]
508 parts = parts[:-1]
509 parts.extend(real_parts)
509 parts.extend(real_parts)
510 return None, parts, offset
510 return None, parts, offset
511
511
512 offset += 1
512 offset += 1
513 while offset < len(s) and s[offset] in [' ', ',']:
513 while offset < len(s) and s[offset] in [' ', ',']:
514 offset += 1
514 offset += 1
515
515
516 if offset < len(s):
516 if offset < len(s):
517 if offset + 1 == len(s) and s[offset] == '"':
517 if offset + 1 == len(s) and s[offset] == '"':
518 parts[-1] += '"'
518 parts[-1] += '"'
519 offset += 1
519 offset += 1
520 else:
520 else:
521 parts.append('')
521 parts.append('')
522 else:
522 else:
523 return None, parts, offset
523 return None, parts, offset
524
524
525 return _parse_plain, parts, offset
525 return _parse_plain, parts, offset
526
526
527 def _configlist(s):
527 def _configlist(s):
528 s = s.rstrip(' ,')
528 s = s.rstrip(' ,')
529 if not s:
529 if not s:
530 return []
530 return []
531 parser, parts, offset = _parse_plain, [''], 0
531 parser, parts, offset = _parse_plain, [''], 0
532 while parser:
532 while parser:
533 parser, parts, offset = parser(parts, s, offset)
533 parser, parts, offset = parser(parts, s, offset)
534 return parts
534 return parts
535
535
536 result = self.config(section, name, untrusted=untrusted)
536 result = self.config(section, name, untrusted=untrusted)
537 if result is None:
537 if result is None:
538 result = default or []
538 result = default or []
539 if isinstance(result, bytes):
539 if isinstance(result, bytes):
540 result = _configlist(result.lstrip(' ,\n'))
540 result = _configlist(result.lstrip(' ,\n'))
541 if result is None:
541 if result is None:
542 result = default or []
542 result = default or []
543 return result
543 return result
544
544
545 def hasconfig(self, section, name, untrusted=False):
545 def hasconfig(self, section, name, untrusted=False):
546 return self._data(untrusted).hasitem(section, name)
546 return self._data(untrusted).hasitem(section, name)
547
547
548 def has_section(self, section, untrusted=False):
548 def has_section(self, section, untrusted=False):
549 '''tell whether section exists in config.'''
549 '''tell whether section exists in config.'''
550 return section in self._data(untrusted)
550 return section in self._data(untrusted)
551
551
552 def configitems(self, section, untrusted=False, ignoresub=False):
552 def configitems(self, section, untrusted=False, ignoresub=False):
553 items = self._data(untrusted).items(section)
553 items = self._data(untrusted).items(section)
554 if ignoresub:
554 if ignoresub:
555 newitems = {}
555 newitems = {}
556 for k, v in items:
556 for k, v in items:
557 if ':' not in k:
557 if ':' not in k:
558 newitems[k] = v
558 newitems[k] = v
559 items = newitems.items()
559 items = newitems.items()
560 if self.debugflag and not untrusted and self._reportuntrusted:
560 if self.debugflag and not untrusted and self._reportuntrusted:
561 for k, v in self._ucfg.items(section):
561 for k, v in self._ucfg.items(section):
562 if self._tcfg.get(section, k) != v:
562 if self._tcfg.get(section, k) != v:
563 self.debug("ignoring untrusted configuration option "
563 self.debug("ignoring untrusted configuration option "
564 "%s.%s = %s\n" % (section, k, v))
564 "%s.%s = %s\n" % (section, k, v))
565 return items
565 return items
566
566
567 def walkconfig(self, untrusted=False):
567 def walkconfig(self, untrusted=False):
568 cfg = self._data(untrusted)
568 cfg = self._data(untrusted)
569 for section in cfg.sections():
569 for section in cfg.sections():
570 for name, value in self.configitems(section, untrusted):
570 for name, value in self.configitems(section, untrusted):
571 yield section, name, value
571 yield section, name, value
572
572
573 def plain(self, feature=None):
573 def plain(self, feature=None):
574 '''is plain mode active?
574 '''is plain mode active?
575
575
576 Plain mode means that all configuration variables which affect
576 Plain mode means that all configuration variables which affect
577 the behavior and output of Mercurial should be
577 the behavior and output of Mercurial should be
578 ignored. Additionally, the output should be stable,
578 ignored. Additionally, the output should be stable,
579 reproducible and suitable for use in scripts or applications.
579 reproducible and suitable for use in scripts or applications.
580
580
581 The only way to trigger plain mode is by setting either the
581 The only way to trigger plain mode is by setting either the
582 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
582 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
583
583
584 The return value can either be
584 The return value can either be
585 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
585 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
586 - True otherwise
586 - True otherwise
587 '''
587 '''
588 if ('HGPLAIN' not in encoding.environ and
588 if ('HGPLAIN' not in encoding.environ and
589 'HGPLAINEXCEPT' not in encoding.environ):
589 'HGPLAINEXCEPT' not in encoding.environ):
590 return False
590 return False
591 exceptions = encoding.environ.get('HGPLAINEXCEPT',
591 exceptions = encoding.environ.get('HGPLAINEXCEPT',
592 '').strip().split(',')
592 '').strip().split(',')
593 if feature and exceptions:
593 if feature and exceptions:
594 return feature not in exceptions
594 return feature not in exceptions
595 return True
595 return True
596
596
597 def username(self):
597 def username(self):
598 """Return default username to be used in commits.
598 """Return default username to be used in commits.
599
599
600 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
600 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
601 and stop searching if one of these is set.
601 and stop searching if one of these is set.
602 If not found and ui.askusername is True, ask the user, else use
602 If not found and ui.askusername is True, ask the user, else use
603 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
603 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
604 """
604 """
605 user = encoding.environ.get("HGUSER")
605 user = encoding.environ.get("HGUSER")
606 if user is None:
606 if user is None:
607 user = self.config("ui", ["username", "user"])
607 user = self.config("ui", ["username", "user"])
608 if user is not None:
608 if user is not None:
609 user = os.path.expandvars(user)
609 user = os.path.expandvars(user)
610 if user is None:
610 if user is None:
611 user = encoding.environ.get("EMAIL")
611 user = encoding.environ.get("EMAIL")
612 if user is None and self.configbool("ui", "askusername"):
612 if user is None and self.configbool("ui", "askusername"):
613 user = self.prompt(_("enter a commit username:"), default=None)
613 user = self.prompt(_("enter a commit username:"), default=None)
614 if user is None and not self.interactive():
614 if user is None and not self.interactive():
615 try:
615 try:
616 user = '%s@%s' % (util.getuser(), socket.getfqdn())
616 user = '%s@%s' % (util.getuser(), socket.getfqdn())
617 self.warn(_("no username found, using '%s' instead\n") % user)
617 self.warn(_("no username found, using '%s' instead\n") % user)
618 except KeyError:
618 except KeyError:
619 pass
619 pass
620 if not user:
620 if not user:
621 raise error.Abort(_('no username supplied'),
621 raise error.Abort(_('no username supplied'),
622 hint=_("use 'hg config --edit' "
622 hint=_("use 'hg config --edit' "
623 'to set your username'))
623 'to set your username'))
624 if "\n" in user:
624 if "\n" in user:
625 raise error.Abort(_("username %s contains a newline\n")
625 raise error.Abort(_("username %s contains a newline\n")
626 % repr(user))
626 % repr(user))
627 return user
627 return user
628
628
629 def shortuser(self, user):
629 def shortuser(self, user):
630 """Return a short representation of a user name or email address."""
630 """Return a short representation of a user name or email address."""
631 if not self.verbose:
631 if not self.verbose:
632 user = util.shortuser(user)
632 user = util.shortuser(user)
633 return user
633 return user
634
634
635 def expandpath(self, loc, default=None):
635 def expandpath(self, loc, default=None):
636 """Return repository location relative to cwd or from [paths]"""
636 """Return repository location relative to cwd or from [paths]"""
637 try:
637 try:
638 p = self.paths.getpath(loc)
638 p = self.paths.getpath(loc)
639 if p:
639 if p:
640 return p.rawloc
640 return p.rawloc
641 except error.RepoError:
641 except error.RepoError:
642 pass
642 pass
643
643
644 if default:
644 if default:
645 try:
645 try:
646 p = self.paths.getpath(default)
646 p = self.paths.getpath(default)
647 if p:
647 if p:
648 return p.rawloc
648 return p.rawloc
649 except error.RepoError:
649 except error.RepoError:
650 pass
650 pass
651
651
652 return loc
652 return loc
653
653
654 @util.propertycache
654 @util.propertycache
655 def paths(self):
655 def paths(self):
656 return paths(self)
656 return paths(self)
657
657
658 def pushbuffer(self, error=False, subproc=False, labeled=False):
658 def pushbuffer(self, error=False, subproc=False, labeled=False):
659 """install a buffer to capture standard output of the ui object
659 """install a buffer to capture standard output of the ui object
660
660
661 If error is True, the error output will be captured too.
661 If error is True, the error output will be captured too.
662
662
663 If subproc is True, output from subprocesses (typically hooks) will be
663 If subproc is True, output from subprocesses (typically hooks) will be
664 captured too.
664 captured too.
665
665
666 If labeled is True, any labels associated with buffered
666 If labeled is True, any labels associated with buffered
667 output will be handled. By default, this has no effect
667 output will be handled. By default, this has no effect
668 on the output returned, but extensions and GUI tools may
668 on the output returned, but extensions and GUI tools may
669 handle this argument and returned styled output. If output
669 handle this argument and returned styled output. If output
670 is being buffered so it can be captured and parsed or
670 is being buffered so it can be captured and parsed or
671 processed, labeled should not be set to True.
671 processed, labeled should not be set to True.
672 """
672 """
673 self._buffers.append([])
673 self._buffers.append([])
674 self._bufferstates.append((error, subproc, labeled))
674 self._bufferstates.append((error, subproc, labeled))
675 self._bufferapplylabels = labeled
675 self._bufferapplylabels = labeled
676
676
677 def popbuffer(self):
677 def popbuffer(self):
678 '''pop the last buffer and return the buffered output'''
678 '''pop the last buffer and return the buffered output'''
679 self._bufferstates.pop()
679 self._bufferstates.pop()
680 if self._bufferstates:
680 if self._bufferstates:
681 self._bufferapplylabels = self._bufferstates[-1][2]
681 self._bufferapplylabels = self._bufferstates[-1][2]
682 else:
682 else:
683 self._bufferapplylabels = None
683 self._bufferapplylabels = None
684
684
685 return "".join(self._buffers.pop())
685 return "".join(self._buffers.pop())
686
686
687 def write(self, *args, **opts):
687 def write(self, *args, **opts):
688 '''write args to output
688 '''write args to output
689
689
690 By default, this method simply writes to the buffer or stdout,
690 By default, this method simply writes to the buffer or stdout,
691 but extensions or GUI tools may override this method,
691 but extensions or GUI tools may override this method,
692 write_err(), popbuffer(), and label() to style output from
692 write_err(), popbuffer(), and label() to style output from
693 various parts of hg.
693 various parts of hg.
694
694
695 An optional keyword argument, "label", can be passed in.
695 An optional keyword argument, "label", can be passed in.
696 This should be a string containing label names separated by
696 This should be a string containing label names separated by
697 space. Label names take the form of "topic.type". For example,
697 space. Label names take the form of "topic.type". For example,
698 ui.debug() issues a label of "ui.debug".
698 ui.debug() issues a label of "ui.debug".
699
699
700 When labeling output for a specific command, a label of
700 When labeling output for a specific command, a label of
701 "cmdname.type" is recommended. For example, status issues
701 "cmdname.type" is recommended. For example, status issues
702 a label of "status.modified" for modified files.
702 a label of "status.modified" for modified files.
703 '''
703 '''
704 if self._buffers and not opts.get('prompt', False):
704 if self._buffers and not opts.get('prompt', False):
705 self._buffers[-1].extend(a for a in args)
705 self._buffers[-1].extend(a for a in args)
706 else:
706 else:
707 self._progclear()
707 self._progclear()
708 for a in args:
708 for a in args:
709 self.fout.write(a)
709 self.fout.write(a)
710
710
711 def write_err(self, *args, **opts):
711 def write_err(self, *args, **opts):
712 self._progclear()
712 self._progclear()
713 try:
713 try:
714 if self._bufferstates and self._bufferstates[-1][0]:
714 if self._bufferstates and self._bufferstates[-1][0]:
715 return self.write(*args, **opts)
715 return self.write(*args, **opts)
716 if not getattr(self.fout, 'closed', False):
716 if not getattr(self.fout, 'closed', False):
717 self.fout.flush()
717 self.fout.flush()
718 for a in args:
718 for a in args:
719 self.ferr.write(a)
719 self.ferr.write(a)
720 # stderr may be buffered under win32 when redirected to files,
720 # stderr may be buffered under win32 when redirected to files,
721 # including stdout.
721 # including stdout.
722 if not getattr(self.ferr, 'closed', False):
722 if not getattr(self.ferr, 'closed', False):
723 self.ferr.flush()
723 self.ferr.flush()
724 except IOError as inst:
724 except IOError as inst:
725 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
725 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
726 raise
726 raise
727
727
728 def flush(self):
728 def flush(self):
729 try: self.fout.flush()
729 try: self.fout.flush()
730 except (IOError, ValueError): pass
730 except (IOError, ValueError): pass
731 try: self.ferr.flush()
731 try: self.ferr.flush()
732 except (IOError, ValueError): pass
732 except (IOError, ValueError): pass
733
733
734 def _isatty(self, fh):
734 def _isatty(self, fh):
735 if self.configbool('ui', 'nontty', False):
735 if self.configbool('ui', 'nontty', False):
736 return False
736 return False
737 return util.isatty(fh)
737 return util.isatty(fh)
738
738
739 def interface(self, feature):
739 def interface(self, feature):
740 """what interface to use for interactive console features?
740 """what interface to use for interactive console features?
741
741
742 The interface is controlled by the value of `ui.interface` but also by
742 The interface is controlled by the value of `ui.interface` but also by
743 the value of feature-specific configuration. For example:
743 the value of feature-specific configuration. For example:
744
744
745 ui.interface.histedit = text
745 ui.interface.histedit = text
746 ui.interface.chunkselector = curses
746 ui.interface.chunkselector = curses
747
747
748 Here the features are "histedit" and "chunkselector".
748 Here the features are "histedit" and "chunkselector".
749
749
750 The configuration above means that the default interfaces for commands
750 The configuration above means that the default interfaces for commands
751 is curses, the interface for histedit is text and the interface for
751 is curses, the interface for histedit is text and the interface for
752 selecting chunk is crecord (the best curses interface available).
752 selecting chunk is crecord (the best curses interface available).
753
753
754 Consider the following example:
754 Consider the following example:
755 ui.interface = curses
755 ui.interface = curses
756 ui.interface.histedit = text
756 ui.interface.histedit = text
757
757
758 Then histedit will use the text interface and chunkselector will use
758 Then histedit will use the text interface and chunkselector will use
759 the default curses interface (crecord at the moment).
759 the default curses interface (crecord at the moment).
760 """
760 """
761 alldefaults = frozenset(["text", "curses"])
761 alldefaults = frozenset(["text", "curses"])
762
762
763 featureinterfaces = {
763 featureinterfaces = {
764 "chunkselector": [
764 "chunkselector": [
765 "text",
765 "text",
766 "curses",
766 "curses",
767 ]
767 ]
768 }
768 }
769
769
770 # Feature-specific interface
770 # Feature-specific interface
771 if feature not in featureinterfaces.keys():
771 if feature not in featureinterfaces.keys():
772 # Programming error, not user error
772 # Programming error, not user error
773 raise ValueError("Unknown feature requested %s" % feature)
773 raise ValueError("Unknown feature requested %s" % feature)
774
774
775 availableinterfaces = frozenset(featureinterfaces[feature])
775 availableinterfaces = frozenset(featureinterfaces[feature])
776 if alldefaults > availableinterfaces:
776 if alldefaults > availableinterfaces:
777 # Programming error, not user error. We need a use case to
777 # Programming error, not user error. We need a use case to
778 # define the right thing to do here.
778 # define the right thing to do here.
779 raise ValueError(
779 raise ValueError(
780 "Feature %s does not handle all default interfaces" %
780 "Feature %s does not handle all default interfaces" %
781 feature)
781 feature)
782
782
783 if self.plain():
783 if self.plain():
784 return "text"
784 return "text"
785
785
786 # Default interface for all the features
786 # Default interface for all the features
787 defaultinterface = "text"
787 defaultinterface = "text"
788 i = self.config("ui", "interface", None)
788 i = self.config("ui", "interface", None)
789 if i in alldefaults:
789 if i in alldefaults:
790 defaultinterface = i
790 defaultinterface = i
791
791
792 choseninterface = defaultinterface
792 choseninterface = defaultinterface
793 f = self.config("ui", "interface.%s" % feature, None)
793 f = self.config("ui", "interface.%s" % feature, None)
794 if f in availableinterfaces:
794 if f in availableinterfaces:
795 choseninterface = f
795 choseninterface = f
796
796
797 if i is not None and defaultinterface != i:
797 if i is not None and defaultinterface != i:
798 if f is not None:
798 if f is not None:
799 self.warn(_("invalid value for ui.interface: %s\n") %
799 self.warn(_("invalid value for ui.interface: %s\n") %
800 (i,))
800 (i,))
801 else:
801 else:
802 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
802 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
803 (i, choseninterface))
803 (i, choseninterface))
804 if f is not None and choseninterface != f:
804 if f is not None and choseninterface != f:
805 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
805 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
806 (feature, f, choseninterface))
806 (feature, f, choseninterface))
807
807
808 return choseninterface
808 return choseninterface
809
809
810 def interactive(self):
810 def interactive(self):
811 '''is interactive input allowed?
811 '''is interactive input allowed?
812
812
813 An interactive session is a session where input can be reasonably read
813 An interactive session is a session where input can be reasonably read
814 from `sys.stdin'. If this function returns false, any attempt to read
814 from `sys.stdin'. If this function returns false, any attempt to read
815 from stdin should fail with an error, unless a sensible default has been
815 from stdin should fail with an error, unless a sensible default has been
816 specified.
816 specified.
817
817
818 Interactiveness is triggered by the value of the `ui.interactive'
818 Interactiveness is triggered by the value of the `ui.interactive'
819 configuration variable or - if it is unset - when `sys.stdin' points
819 configuration variable or - if it is unset - when `sys.stdin' points
820 to a terminal device.
820 to a terminal device.
821
821
822 This function refers to input only; for output, see `ui.formatted()'.
822 This function refers to input only; for output, see `ui.formatted()'.
823 '''
823 '''
824 i = self.configbool("ui", "interactive", None)
824 i = self.configbool("ui", "interactive", None)
825 if i is None:
825 if i is None:
826 # some environments replace stdin without implementing isatty
826 # some environments replace stdin without implementing isatty
827 # usually those are non-interactive
827 # usually those are non-interactive
828 return self._isatty(self.fin)
828 return self._isatty(self.fin)
829
829
830 return i
830 return i
831
831
832 def termwidth(self):
832 def termwidth(self):
833 '''how wide is the terminal in columns?
833 '''how wide is the terminal in columns?
834 '''
834 '''
835 if 'COLUMNS' in encoding.environ:
835 if 'COLUMNS' in encoding.environ:
836 try:
836 try:
837 return int(encoding.environ['COLUMNS'])
837 return int(encoding.environ['COLUMNS'])
838 except ValueError:
838 except ValueError:
839 pass
839 pass
840 return scmutil.termsize(self)[0]
840 return scmutil.termsize(self)[0]
841
841
842 def formatted(self):
842 def formatted(self):
843 '''should formatted output be used?
843 '''should formatted output be used?
844
844
845 It is often desirable to format the output to suite the output medium.
845 It is often desirable to format the output to suite the output medium.
846 Examples of this are truncating long lines or colorizing messages.
846 Examples of this are truncating long lines or colorizing messages.
847 However, this is not often not desirable when piping output into other
847 However, this is not often not desirable when piping output into other
848 utilities, e.g. `grep'.
848 utilities, e.g. `grep'.
849
849
850 Formatted output is triggered by the value of the `ui.formatted'
850 Formatted output is triggered by the value of the `ui.formatted'
851 configuration variable or - if it is unset - when `sys.stdout' points
851 configuration variable or - if it is unset - when `sys.stdout' points
852 to a terminal device. Please note that `ui.formatted' should be
852 to a terminal device. Please note that `ui.formatted' should be
853 considered an implementation detail; it is not intended for use outside
853 considered an implementation detail; it is not intended for use outside
854 Mercurial or its extensions.
854 Mercurial or its extensions.
855
855
856 This function refers to output only; for input, see `ui.interactive()'.
856 This function refers to output only; for input, see `ui.interactive()'.
857 This function always returns false when in plain mode, see `ui.plain()'.
857 This function always returns false when in plain mode, see `ui.plain()'.
858 '''
858 '''
859 if self.plain():
859 if self.plain():
860 return False
860 return False
861
861
862 i = self.configbool("ui", "formatted", None)
862 i = self.configbool("ui", "formatted", None)
863 if i is None:
863 if i is None:
864 # some environments replace stdout without implementing isatty
864 # some environments replace stdout without implementing isatty
865 # usually those are non-interactive
865 # usually those are non-interactive
866 return self._isatty(self.fout)
866 return self._isatty(self.fout)
867
867
868 return i
868 return i
869
869
870 def _readline(self, prompt=''):
870 def _readline(self, prompt=''):
871 if self._isatty(self.fin):
871 if self._isatty(self.fin):
872 try:
872 try:
873 # magically add command line editing support, where
873 # magically add command line editing support, where
874 # available
874 # available
875 import readline
875 import readline
876 # force demandimport to really load the module
876 # force demandimport to really load the module
877 readline.read_history_file
877 readline.read_history_file
878 # windows sometimes raises something other than ImportError
878 # windows sometimes raises something other than ImportError
879 except Exception:
879 except Exception:
880 pass
880 pass
881
881
882 # call write() so output goes through subclassed implementation
882 # call write() so output goes through subclassed implementation
883 # e.g. color extension on Windows
883 # e.g. color extension on Windows
884 self.write(prompt, prompt=True)
884 self.write(prompt, prompt=True)
885
885
886 # instead of trying to emulate raw_input, swap (self.fin,
886 # instead of trying to emulate raw_input, swap (self.fin,
887 # self.fout) with (sys.stdin, sys.stdout)
887 # self.fout) with (sys.stdin, sys.stdout)
888 oldin = sys.stdin
888 oldin = sys.stdin
889 oldout = sys.stdout
889 oldout = sys.stdout
890 sys.stdin = self.fin
890 sys.stdin = self.fin
891 sys.stdout = self.fout
891 sys.stdout = self.fout
892 # prompt ' ' must exist; otherwise readline may delete entire line
892 # prompt ' ' must exist; otherwise readline may delete entire line
893 # - http://bugs.python.org/issue12833
893 # - http://bugs.python.org/issue12833
894 line = raw_input(' ')
894 line = raw_input(' ')
895 sys.stdin = oldin
895 sys.stdin = oldin
896 sys.stdout = oldout
896 sys.stdout = oldout
897
897
898 # When stdin is in binary mode on Windows, it can cause
898 # When stdin is in binary mode on Windows, it can cause
899 # raw_input() to emit an extra trailing carriage return
899 # raw_input() to emit an extra trailing carriage return
900 if os.linesep == '\r\n' and line and line[-1] == '\r':
900 if os.linesep == '\r\n' and line and line[-1] == '\r':
901 line = line[:-1]
901 line = line[:-1]
902 return line
902 return line
903
903
904 def prompt(self, msg, default="y"):
904 def prompt(self, msg, default="y"):
905 """Prompt user with msg, read response.
905 """Prompt user with msg, read response.
906 If ui is not interactive, the default is returned.
906 If ui is not interactive, the default is returned.
907 """
907 """
908 if not self.interactive():
908 if not self.interactive():
909 self.write(msg, ' ', default or '', "\n")
909 self.write(msg, ' ', default or '', "\n")
910 return default
910 return default
911 try:
911 try:
912 r = self._readline(self.label(msg, 'ui.prompt'))
912 r = self._readline(self.label(msg, 'ui.prompt'))
913 if not r:
913 if not r:
914 r = default
914 r = default
915 if self.configbool('ui', 'promptecho'):
915 if self.configbool('ui', 'promptecho'):
916 self.write(r, "\n")
916 self.write(r, "\n")
917 return r
917 return r
918 except EOFError:
918 except EOFError:
919 raise error.ResponseExpected()
919 raise error.ResponseExpected()
920
920
921 @staticmethod
921 @staticmethod
922 def extractchoices(prompt):
922 def extractchoices(prompt):
923 """Extract prompt message and list of choices from specified prompt.
923 """Extract prompt message and list of choices from specified prompt.
924
924
925 This returns tuple "(message, choices)", and "choices" is the
925 This returns tuple "(message, choices)", and "choices" is the
926 list of tuple "(response character, text without &)".
926 list of tuple "(response character, text without &)".
927
927
928 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
928 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
929 ('awake? ', [('y', 'Yes'), ('n', 'No')])
929 ('awake? ', [('y', 'Yes'), ('n', 'No')])
930 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
930 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
931 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
931 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
932 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
932 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
933 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
933 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
934 """
934 """
935
935
936 # Sadly, the prompt string may have been built with a filename
936 # Sadly, the prompt string may have been built with a filename
937 # containing "$$" so let's try to find the first valid-looking
937 # containing "$$" so let's try to find the first valid-looking
938 # prompt to start parsing. Sadly, we also can't rely on
938 # prompt to start parsing. Sadly, we also can't rely on
939 # choices containing spaces, ASCII, or basically anything
939 # choices containing spaces, ASCII, or basically anything
940 # except an ampersand followed by a character.
940 # except an ampersand followed by a character.
941 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
941 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
942 msg = m.group(1)
942 msg = m.group(1)
943 choices = [p.strip(' ') for p in m.group(2).split('$$')]
943 choices = [p.strip(' ') for p in m.group(2).split('$$')]
944 return (msg,
944 return (msg,
945 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
945 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
946 for s in choices])
946 for s in choices])
947
947
948 def promptchoice(self, prompt, default=0):
948 def promptchoice(self, prompt, default=0):
949 """Prompt user with a message, read response, and ensure it matches
949 """Prompt user with a message, read response, and ensure it matches
950 one of the provided choices. The prompt is formatted as follows:
950 one of the provided choices. The prompt is formatted as follows:
951
951
952 "would you like fries with that (Yn)? $$ &Yes $$ &No"
952 "would you like fries with that (Yn)? $$ &Yes $$ &No"
953
953
954 The index of the choice is returned. Responses are case
954 The index of the choice is returned. Responses are case
955 insensitive. If ui is not interactive, the default is
955 insensitive. If ui is not interactive, the default is
956 returned.
956 returned.
957 """
957 """
958
958
959 msg, choices = self.extractchoices(prompt)
959 msg, choices = self.extractchoices(prompt)
960 resps = [r for r, t in choices]
960 resps = [r for r, t in choices]
961 while True:
961 while True:
962 r = self.prompt(msg, resps[default])
962 r = self.prompt(msg, resps[default])
963 if r.lower() in resps:
963 if r.lower() in resps:
964 return resps.index(r.lower())
964 return resps.index(r.lower())
965 self.write(_("unrecognized response\n"))
965 self.write(_("unrecognized response\n"))
966
966
967 def getpass(self, prompt=None, default=None):
967 def getpass(self, prompt=None, default=None):
968 if not self.interactive():
968 if not self.interactive():
969 return default
969 return default
970 try:
970 try:
971 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
971 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
972 # disable getpass() only if explicitly specified. it's still valid
972 # disable getpass() only if explicitly specified. it's still valid
973 # to interact with tty even if fin is not a tty.
973 # to interact with tty even if fin is not a tty.
974 if self.configbool('ui', 'nontty'):
974 if self.configbool('ui', 'nontty'):
975 return self.fin.readline().rstrip('\n')
975 l = self.fin.readline()
976 if not l:
977 raise EOFError
978 return l.rstrip('\n')
976 else:
979 else:
977 return getpass.getpass('')
980 return getpass.getpass('')
978 except EOFError:
981 except EOFError:
979 raise error.ResponseExpected()
982 raise error.ResponseExpected()
980 def status(self, *msg, **opts):
983 def status(self, *msg, **opts):
981 '''write status message to output (if ui.quiet is False)
984 '''write status message to output (if ui.quiet is False)
982
985
983 This adds an output label of "ui.status".
986 This adds an output label of "ui.status".
984 '''
987 '''
985 if not self.quiet:
988 if not self.quiet:
986 opts['label'] = opts.get('label', '') + ' ui.status'
989 opts['label'] = opts.get('label', '') + ' ui.status'
987 self.write(*msg, **opts)
990 self.write(*msg, **opts)
988 def warn(self, *msg, **opts):
991 def warn(self, *msg, **opts):
989 '''write warning message to output (stderr)
992 '''write warning message to output (stderr)
990
993
991 This adds an output label of "ui.warning".
994 This adds an output label of "ui.warning".
992 '''
995 '''
993 opts['label'] = opts.get('label', '') + ' ui.warning'
996 opts['label'] = opts.get('label', '') + ' ui.warning'
994 self.write_err(*msg, **opts)
997 self.write_err(*msg, **opts)
995 def note(self, *msg, **opts):
998 def note(self, *msg, **opts):
996 '''write note to output (if ui.verbose is True)
999 '''write note to output (if ui.verbose is True)
997
1000
998 This adds an output label of "ui.note".
1001 This adds an output label of "ui.note".
999 '''
1002 '''
1000 if self.verbose:
1003 if self.verbose:
1001 opts['label'] = opts.get('label', '') + ' ui.note'
1004 opts['label'] = opts.get('label', '') + ' ui.note'
1002 self.write(*msg, **opts)
1005 self.write(*msg, **opts)
1003 def debug(self, *msg, **opts):
1006 def debug(self, *msg, **opts):
1004 '''write debug message to output (if ui.debugflag is True)
1007 '''write debug message to output (if ui.debugflag is True)
1005
1008
1006 This adds an output label of "ui.debug".
1009 This adds an output label of "ui.debug".
1007 '''
1010 '''
1008 if self.debugflag:
1011 if self.debugflag:
1009 opts['label'] = opts.get('label', '') + ' ui.debug'
1012 opts['label'] = opts.get('label', '') + ' ui.debug'
1010 self.write(*msg, **opts)
1013 self.write(*msg, **opts)
1011
1014
1012 def edit(self, text, user, extra=None, editform=None, pending=None):
1015 def edit(self, text, user, extra=None, editform=None, pending=None):
1013 extra_defaults = {
1016 extra_defaults = {
1014 'prefix': 'editor',
1017 'prefix': 'editor',
1015 'suffix': '.txt',
1018 'suffix': '.txt',
1016 }
1019 }
1017 if extra is not None:
1020 if extra is not None:
1018 extra_defaults.update(extra)
1021 extra_defaults.update(extra)
1019 extra = extra_defaults
1022 extra = extra_defaults
1020 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1023 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1021 suffix=extra['suffix'], text=True)
1024 suffix=extra['suffix'], text=True)
1022 try:
1025 try:
1023 f = os.fdopen(fd, "w")
1026 f = os.fdopen(fd, "w")
1024 f.write(text)
1027 f.write(text)
1025 f.close()
1028 f.close()
1026
1029
1027 environ = {'HGUSER': user}
1030 environ = {'HGUSER': user}
1028 if 'transplant_source' in extra:
1031 if 'transplant_source' in extra:
1029 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1032 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1030 for label in ('intermediate-source', 'source', 'rebase_source'):
1033 for label in ('intermediate-source', 'source', 'rebase_source'):
1031 if label in extra:
1034 if label in extra:
1032 environ.update({'HGREVISION': extra[label]})
1035 environ.update({'HGREVISION': extra[label]})
1033 break
1036 break
1034 if editform:
1037 if editform:
1035 environ.update({'HGEDITFORM': editform})
1038 environ.update({'HGEDITFORM': editform})
1036 if pending:
1039 if pending:
1037 environ.update({'HG_PENDING': pending})
1040 environ.update({'HG_PENDING': pending})
1038
1041
1039 editor = self.geteditor()
1042 editor = self.geteditor()
1040
1043
1041 self.system("%s \"%s\"" % (editor, name),
1044 self.system("%s \"%s\"" % (editor, name),
1042 environ=environ,
1045 environ=environ,
1043 onerr=error.Abort, errprefix=_("edit failed"))
1046 onerr=error.Abort, errprefix=_("edit failed"))
1044
1047
1045 f = open(name)
1048 f = open(name)
1046 t = f.read()
1049 t = f.read()
1047 f.close()
1050 f.close()
1048 finally:
1051 finally:
1049 os.unlink(name)
1052 os.unlink(name)
1050
1053
1051 return t
1054 return t
1052
1055
1053 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
1056 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
1054 '''execute shell command with appropriate output stream. command
1057 '''execute shell command with appropriate output stream. command
1055 output will be redirected if fout is not stdout.
1058 output will be redirected if fout is not stdout.
1056 '''
1059 '''
1057 out = self.fout
1060 out = self.fout
1058 if any(s[1] for s in self._bufferstates):
1061 if any(s[1] for s in self._bufferstates):
1059 out = self
1062 out = self
1060 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1063 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1061 errprefix=errprefix, out=out)
1064 errprefix=errprefix, out=out)
1062
1065
1063 def traceback(self, exc=None, force=False):
1066 def traceback(self, exc=None, force=False):
1064 '''print exception traceback if traceback printing enabled or forced.
1067 '''print exception traceback if traceback printing enabled or forced.
1065 only to call in exception handler. returns true if traceback
1068 only to call in exception handler. returns true if traceback
1066 printed.'''
1069 printed.'''
1067 if self.tracebackflag or force:
1070 if self.tracebackflag or force:
1068 if exc is None:
1071 if exc is None:
1069 exc = sys.exc_info()
1072 exc = sys.exc_info()
1070 cause = getattr(exc[1], 'cause', None)
1073 cause = getattr(exc[1], 'cause', None)
1071
1074
1072 if cause is not None:
1075 if cause is not None:
1073 causetb = traceback.format_tb(cause[2])
1076 causetb = traceback.format_tb(cause[2])
1074 exctb = traceback.format_tb(exc[2])
1077 exctb = traceback.format_tb(exc[2])
1075 exconly = traceback.format_exception_only(cause[0], cause[1])
1078 exconly = traceback.format_exception_only(cause[0], cause[1])
1076
1079
1077 # exclude frame where 'exc' was chained and rethrown from exctb
1080 # exclude frame where 'exc' was chained and rethrown from exctb
1078 self.write_err('Traceback (most recent call last):\n',
1081 self.write_err('Traceback (most recent call last):\n',
1079 ''.join(exctb[:-1]),
1082 ''.join(exctb[:-1]),
1080 ''.join(causetb),
1083 ''.join(causetb),
1081 ''.join(exconly))
1084 ''.join(exconly))
1082 else:
1085 else:
1083 output = traceback.format_exception(exc[0], exc[1], exc[2])
1086 output = traceback.format_exception(exc[0], exc[1], exc[2])
1084 self.write_err(''.join(output))
1087 self.write_err(''.join(output))
1085 return self.tracebackflag or force
1088 return self.tracebackflag or force
1086
1089
1087 def geteditor(self):
1090 def geteditor(self):
1088 '''return editor to use'''
1091 '''return editor to use'''
1089 if pycompat.sysplatform == 'plan9':
1092 if pycompat.sysplatform == 'plan9':
1090 # vi is the MIPS instruction simulator on Plan 9. We
1093 # vi is the MIPS instruction simulator on Plan 9. We
1091 # instead default to E to plumb commit messages to
1094 # instead default to E to plumb commit messages to
1092 # avoid confusion.
1095 # avoid confusion.
1093 editor = 'E'
1096 editor = 'E'
1094 else:
1097 else:
1095 editor = 'vi'
1098 editor = 'vi'
1096 return (encoding.environ.get("HGEDITOR") or
1099 return (encoding.environ.get("HGEDITOR") or
1097 self.config("ui", "editor") or
1100 self.config("ui", "editor") or
1098 encoding.environ.get("VISUAL") or
1101 encoding.environ.get("VISUAL") or
1099 encoding.environ.get("EDITOR", editor))
1102 encoding.environ.get("EDITOR", editor))
1100
1103
1101 @util.propertycache
1104 @util.propertycache
1102 def _progbar(self):
1105 def _progbar(self):
1103 """setup the progbar singleton to the ui object"""
1106 """setup the progbar singleton to the ui object"""
1104 if (self.quiet or self.debugflag
1107 if (self.quiet or self.debugflag
1105 or self.configbool('progress', 'disable', False)
1108 or self.configbool('progress', 'disable', False)
1106 or not progress.shouldprint(self)):
1109 or not progress.shouldprint(self)):
1107 return None
1110 return None
1108 return getprogbar(self)
1111 return getprogbar(self)
1109
1112
1110 def _progclear(self):
1113 def _progclear(self):
1111 """clear progress bar output if any. use it before any output"""
1114 """clear progress bar output if any. use it before any output"""
1112 if '_progbar' not in vars(self): # nothing loaded yet
1115 if '_progbar' not in vars(self): # nothing loaded yet
1113 return
1116 return
1114 if self._progbar is not None and self._progbar.printed:
1117 if self._progbar is not None and self._progbar.printed:
1115 self._progbar.clear()
1118 self._progbar.clear()
1116
1119
1117 def progress(self, topic, pos, item="", unit="", total=None):
1120 def progress(self, topic, pos, item="", unit="", total=None):
1118 '''show a progress message
1121 '''show a progress message
1119
1122
1120 By default a textual progress bar will be displayed if an operation
1123 By default a textual progress bar will be displayed if an operation
1121 takes too long. 'topic' is the current operation, 'item' is a
1124 takes too long. 'topic' is the current operation, 'item' is a
1122 non-numeric marker of the current position (i.e. the currently
1125 non-numeric marker of the current position (i.e. the currently
1123 in-process file), 'pos' is the current numeric position (i.e.
1126 in-process file), 'pos' is the current numeric position (i.e.
1124 revision, bytes, etc.), unit is a corresponding unit label,
1127 revision, bytes, etc.), unit is a corresponding unit label,
1125 and total is the highest expected pos.
1128 and total is the highest expected pos.
1126
1129
1127 Multiple nested topics may be active at a time.
1130 Multiple nested topics may be active at a time.
1128
1131
1129 All topics should be marked closed by setting pos to None at
1132 All topics should be marked closed by setting pos to None at
1130 termination.
1133 termination.
1131 '''
1134 '''
1132 if self._progbar is not None:
1135 if self._progbar is not None:
1133 self._progbar.progress(topic, pos, item=item, unit=unit,
1136 self._progbar.progress(topic, pos, item=item, unit=unit,
1134 total=total)
1137 total=total)
1135 if pos is None or not self.configbool('progress', 'debug'):
1138 if pos is None or not self.configbool('progress', 'debug'):
1136 return
1139 return
1137
1140
1138 if unit:
1141 if unit:
1139 unit = ' ' + unit
1142 unit = ' ' + unit
1140 if item:
1143 if item:
1141 item = ' ' + item
1144 item = ' ' + item
1142
1145
1143 if total:
1146 if total:
1144 pct = 100.0 * pos / total
1147 pct = 100.0 * pos / total
1145 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1148 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1146 % (topic, item, pos, total, unit, pct))
1149 % (topic, item, pos, total, unit, pct))
1147 else:
1150 else:
1148 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1151 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1149
1152
1150 def log(self, service, *msg, **opts):
1153 def log(self, service, *msg, **opts):
1151 '''hook for logging facility extensions
1154 '''hook for logging facility extensions
1152
1155
1153 service should be a readily-identifiable subsystem, which will
1156 service should be a readily-identifiable subsystem, which will
1154 allow filtering.
1157 allow filtering.
1155
1158
1156 *msg should be a newline-terminated format string to log, and
1159 *msg should be a newline-terminated format string to log, and
1157 then any values to %-format into that format string.
1160 then any values to %-format into that format string.
1158
1161
1159 **opts currently has no defined meanings.
1162 **opts currently has no defined meanings.
1160 '''
1163 '''
1161
1164
1162 def label(self, msg, label):
1165 def label(self, msg, label):
1163 '''style msg based on supplied label
1166 '''style msg based on supplied label
1164
1167
1165 Like ui.write(), this just returns msg unchanged, but extensions
1168 Like ui.write(), this just returns msg unchanged, but extensions
1166 and GUI tools can override it to allow styling output without
1169 and GUI tools can override it to allow styling output without
1167 writing it.
1170 writing it.
1168
1171
1169 ui.write(s, 'label') is equivalent to
1172 ui.write(s, 'label') is equivalent to
1170 ui.write(ui.label(s, 'label')).
1173 ui.write(ui.label(s, 'label')).
1171 '''
1174 '''
1172 return msg
1175 return msg
1173
1176
1174 def develwarn(self, msg, stacklevel=1, config=None):
1177 def develwarn(self, msg, stacklevel=1, config=None):
1175 """issue a developer warning message
1178 """issue a developer warning message
1176
1179
1177 Use 'stacklevel' to report the offender some layers further up in the
1180 Use 'stacklevel' to report the offender some layers further up in the
1178 stack.
1181 stack.
1179 """
1182 """
1180 if not self.configbool('devel', 'all-warnings'):
1183 if not self.configbool('devel', 'all-warnings'):
1181 if config is not None and not self.configbool('devel', config):
1184 if config is not None and not self.configbool('devel', config):
1182 return
1185 return
1183 msg = 'devel-warn: ' + msg
1186 msg = 'devel-warn: ' + msg
1184 stacklevel += 1 # get in develwarn
1187 stacklevel += 1 # get in develwarn
1185 if self.tracebackflag:
1188 if self.tracebackflag:
1186 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1189 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1187 self.log('develwarn', '%s at:\n%s' %
1190 self.log('develwarn', '%s at:\n%s' %
1188 (msg, ''.join(util.getstackframes(stacklevel))))
1191 (msg, ''.join(util.getstackframes(stacklevel))))
1189 else:
1192 else:
1190 curframe = inspect.currentframe()
1193 curframe = inspect.currentframe()
1191 calframe = inspect.getouterframes(curframe, 2)
1194 calframe = inspect.getouterframes(curframe, 2)
1192 self.write_err('%s at: %s:%s (%s)\n'
1195 self.write_err('%s at: %s:%s (%s)\n'
1193 % ((msg,) + calframe[stacklevel][1:4]))
1196 % ((msg,) + calframe[stacklevel][1:4]))
1194 self.log('develwarn', '%s at: %s:%s (%s)\n',
1197 self.log('develwarn', '%s at: %s:%s (%s)\n',
1195 msg, *calframe[stacklevel][1:4])
1198 msg, *calframe[stacklevel][1:4])
1196 curframe = calframe = None # avoid cycles
1199 curframe = calframe = None # avoid cycles
1197
1200
1198 def deprecwarn(self, msg, version):
1201 def deprecwarn(self, msg, version):
1199 """issue a deprecation warning
1202 """issue a deprecation warning
1200
1203
1201 - msg: message explaining what is deprecated and how to upgrade,
1204 - msg: message explaining what is deprecated and how to upgrade,
1202 - version: last version where the API will be supported,
1205 - version: last version where the API will be supported,
1203 """
1206 """
1204 if not (self.configbool('devel', 'all-warnings')
1207 if not (self.configbool('devel', 'all-warnings')
1205 or self.configbool('devel', 'deprec-warn')):
1208 or self.configbool('devel', 'deprec-warn')):
1206 return
1209 return
1207 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1210 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1208 " update your code.)") % version
1211 " update your code.)") % version
1209 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1212 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1210
1213
1211 @contextlib.contextmanager
1214 @contextlib.contextmanager
1212 def configoverride(self, overrides, source=""):
1215 def configoverride(self, overrides, source=""):
1213 """Context manager for temporary config overrides
1216 """Context manager for temporary config overrides
1214 `overrides` must be a dict of the following structure:
1217 `overrides` must be a dict of the following structure:
1215 {(section, name) : value}"""
1218 {(section, name) : value}"""
1216 backups = {}
1219 backups = {}
1217 try:
1220 try:
1218 for (section, name), value in overrides.items():
1221 for (section, name), value in overrides.items():
1219 backups[(section, name)] = self.backupconfig(section, name)
1222 backups[(section, name)] = self.backupconfig(section, name)
1220 self.setconfig(section, name, value, source)
1223 self.setconfig(section, name, value, source)
1221 yield
1224 yield
1222 finally:
1225 finally:
1223 for __, backup in backups.items():
1226 for __, backup in backups.items():
1224 self.restoreconfig(backup)
1227 self.restoreconfig(backup)
1225 # just restoring ui.quiet config to the previous value is not enough
1228 # just restoring ui.quiet config to the previous value is not enough
1226 # as it does not update ui.quiet class member
1229 # as it does not update ui.quiet class member
1227 if ('ui', 'quiet') in overrides:
1230 if ('ui', 'quiet') in overrides:
1228 self.fixconfig(section='ui')
1231 self.fixconfig(section='ui')
1229
1232
1230 class paths(dict):
1233 class paths(dict):
1231 """Represents a collection of paths and their configs.
1234 """Represents a collection of paths and their configs.
1232
1235
1233 Data is initially derived from ui instances and the config files they have
1236 Data is initially derived from ui instances and the config files they have
1234 loaded.
1237 loaded.
1235 """
1238 """
1236 def __init__(self, ui):
1239 def __init__(self, ui):
1237 dict.__init__(self)
1240 dict.__init__(self)
1238
1241
1239 for name, loc in ui.configitems('paths', ignoresub=True):
1242 for name, loc in ui.configitems('paths', ignoresub=True):
1240 # No location is the same as not existing.
1243 # No location is the same as not existing.
1241 if not loc:
1244 if not loc:
1242 continue
1245 continue
1243 loc, sub = ui.configsuboptions('paths', name)
1246 loc, sub = ui.configsuboptions('paths', name)
1244 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1247 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1245
1248
1246 def getpath(self, name, default=None):
1249 def getpath(self, name, default=None):
1247 """Return a ``path`` from a string, falling back to default.
1250 """Return a ``path`` from a string, falling back to default.
1248
1251
1249 ``name`` can be a named path or locations. Locations are filesystem
1252 ``name`` can be a named path or locations. Locations are filesystem
1250 paths or URIs.
1253 paths or URIs.
1251
1254
1252 Returns None if ``name`` is not a registered path, a URI, or a local
1255 Returns None if ``name`` is not a registered path, a URI, or a local
1253 path to a repo.
1256 path to a repo.
1254 """
1257 """
1255 # Only fall back to default if no path was requested.
1258 # Only fall back to default if no path was requested.
1256 if name is None:
1259 if name is None:
1257 if not default:
1260 if not default:
1258 default = ()
1261 default = ()
1259 elif not isinstance(default, (tuple, list)):
1262 elif not isinstance(default, (tuple, list)):
1260 default = (default,)
1263 default = (default,)
1261 for k in default:
1264 for k in default:
1262 try:
1265 try:
1263 return self[k]
1266 return self[k]
1264 except KeyError:
1267 except KeyError:
1265 continue
1268 continue
1266 return None
1269 return None
1267
1270
1268 # Most likely empty string.
1271 # Most likely empty string.
1269 # This may need to raise in the future.
1272 # This may need to raise in the future.
1270 if not name:
1273 if not name:
1271 return None
1274 return None
1272
1275
1273 try:
1276 try:
1274 return self[name]
1277 return self[name]
1275 except KeyError:
1278 except KeyError:
1276 # Try to resolve as a local path or URI.
1279 # Try to resolve as a local path or URI.
1277 try:
1280 try:
1278 # We don't pass sub-options in, so no need to pass ui instance.
1281 # We don't pass sub-options in, so no need to pass ui instance.
1279 return path(None, None, rawloc=name)
1282 return path(None, None, rawloc=name)
1280 except ValueError:
1283 except ValueError:
1281 raise error.RepoError(_('repository %s does not exist') %
1284 raise error.RepoError(_('repository %s does not exist') %
1282 name)
1285 name)
1283
1286
1284 _pathsuboptions = {}
1287 _pathsuboptions = {}
1285
1288
1286 def pathsuboption(option, attr):
1289 def pathsuboption(option, attr):
1287 """Decorator used to declare a path sub-option.
1290 """Decorator used to declare a path sub-option.
1288
1291
1289 Arguments are the sub-option name and the attribute it should set on
1292 Arguments are the sub-option name and the attribute it should set on
1290 ``path`` instances.
1293 ``path`` instances.
1291
1294
1292 The decorated function will receive as arguments a ``ui`` instance,
1295 The decorated function will receive as arguments a ``ui`` instance,
1293 ``path`` instance, and the string value of this option from the config.
1296 ``path`` instance, and the string value of this option from the config.
1294 The function should return the value that will be set on the ``path``
1297 The function should return the value that will be set on the ``path``
1295 instance.
1298 instance.
1296
1299
1297 This decorator can be used to perform additional verification of
1300 This decorator can be used to perform additional verification of
1298 sub-options and to change the type of sub-options.
1301 sub-options and to change the type of sub-options.
1299 """
1302 """
1300 def register(func):
1303 def register(func):
1301 _pathsuboptions[option] = (attr, func)
1304 _pathsuboptions[option] = (attr, func)
1302 return func
1305 return func
1303 return register
1306 return register
1304
1307
1305 @pathsuboption('pushurl', 'pushloc')
1308 @pathsuboption('pushurl', 'pushloc')
1306 def pushurlpathoption(ui, path, value):
1309 def pushurlpathoption(ui, path, value):
1307 u = util.url(value)
1310 u = util.url(value)
1308 # Actually require a URL.
1311 # Actually require a URL.
1309 if not u.scheme:
1312 if not u.scheme:
1310 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1313 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1311 return None
1314 return None
1312
1315
1313 # Don't support the #foo syntax in the push URL to declare branch to
1316 # Don't support the #foo syntax in the push URL to declare branch to
1314 # push.
1317 # push.
1315 if u.fragment:
1318 if u.fragment:
1316 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1319 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1317 'ignoring)\n') % path.name)
1320 'ignoring)\n') % path.name)
1318 u.fragment = None
1321 u.fragment = None
1319
1322
1320 return str(u)
1323 return str(u)
1321
1324
1322 @pathsuboption('pushrev', 'pushrev')
1325 @pathsuboption('pushrev', 'pushrev')
1323 def pushrevpathoption(ui, path, value):
1326 def pushrevpathoption(ui, path, value):
1324 return value
1327 return value
1325
1328
1326 class path(object):
1329 class path(object):
1327 """Represents an individual path and its configuration."""
1330 """Represents an individual path and its configuration."""
1328
1331
1329 def __init__(self, ui, name, rawloc=None, suboptions=None):
1332 def __init__(self, ui, name, rawloc=None, suboptions=None):
1330 """Construct a path from its config options.
1333 """Construct a path from its config options.
1331
1334
1332 ``ui`` is the ``ui`` instance the path is coming from.
1335 ``ui`` is the ``ui`` instance the path is coming from.
1333 ``name`` is the symbolic name of the path.
1336 ``name`` is the symbolic name of the path.
1334 ``rawloc`` is the raw location, as defined in the config.
1337 ``rawloc`` is the raw location, as defined in the config.
1335 ``pushloc`` is the raw locations pushes should be made to.
1338 ``pushloc`` is the raw locations pushes should be made to.
1336
1339
1337 If ``name`` is not defined, we require that the location be a) a local
1340 If ``name`` is not defined, we require that the location be a) a local
1338 filesystem path with a .hg directory or b) a URL. If not,
1341 filesystem path with a .hg directory or b) a URL. If not,
1339 ``ValueError`` is raised.
1342 ``ValueError`` is raised.
1340 """
1343 """
1341 if not rawloc:
1344 if not rawloc:
1342 raise ValueError('rawloc must be defined')
1345 raise ValueError('rawloc must be defined')
1343
1346
1344 # Locations may define branches via syntax <base>#<branch>.
1347 # Locations may define branches via syntax <base>#<branch>.
1345 u = util.url(rawloc)
1348 u = util.url(rawloc)
1346 branch = None
1349 branch = None
1347 if u.fragment:
1350 if u.fragment:
1348 branch = u.fragment
1351 branch = u.fragment
1349 u.fragment = None
1352 u.fragment = None
1350
1353
1351 self.url = u
1354 self.url = u
1352 self.branch = branch
1355 self.branch = branch
1353
1356
1354 self.name = name
1357 self.name = name
1355 self.rawloc = rawloc
1358 self.rawloc = rawloc
1356 self.loc = str(u)
1359 self.loc = str(u)
1357
1360
1358 # When given a raw location but not a symbolic name, validate the
1361 # When given a raw location but not a symbolic name, validate the
1359 # location is valid.
1362 # location is valid.
1360 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1363 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1361 raise ValueError('location is not a URL or path to a local '
1364 raise ValueError('location is not a URL or path to a local '
1362 'repo: %s' % rawloc)
1365 'repo: %s' % rawloc)
1363
1366
1364 suboptions = suboptions or {}
1367 suboptions = suboptions or {}
1365
1368
1366 # Now process the sub-options. If a sub-option is registered, its
1369 # Now process the sub-options. If a sub-option is registered, its
1367 # attribute will always be present. The value will be None if there
1370 # attribute will always be present. The value will be None if there
1368 # was no valid sub-option.
1371 # was no valid sub-option.
1369 for suboption, (attr, func) in _pathsuboptions.iteritems():
1372 for suboption, (attr, func) in _pathsuboptions.iteritems():
1370 if suboption not in suboptions:
1373 if suboption not in suboptions:
1371 setattr(self, attr, None)
1374 setattr(self, attr, None)
1372 continue
1375 continue
1373
1376
1374 value = func(ui, self, suboptions[suboption])
1377 value = func(ui, self, suboptions[suboption])
1375 setattr(self, attr, value)
1378 setattr(self, attr, value)
1376
1379
1377 def _isvalidlocalpath(self, path):
1380 def _isvalidlocalpath(self, path):
1378 """Returns True if the given path is a potentially valid repository.
1381 """Returns True if the given path is a potentially valid repository.
1379 This is its own function so that extensions can change the definition of
1382 This is its own function so that extensions can change the definition of
1380 'valid' in this case (like when pulling from a git repo into a hg
1383 'valid' in this case (like when pulling from a git repo into a hg
1381 one)."""
1384 one)."""
1382 return os.path.isdir(os.path.join(path, '.hg'))
1385 return os.path.isdir(os.path.join(path, '.hg'))
1383
1386
1384 @property
1387 @property
1385 def suboptions(self):
1388 def suboptions(self):
1386 """Return sub-options and their values for this path.
1389 """Return sub-options and their values for this path.
1387
1390
1388 This is intended to be used for presentation purposes.
1391 This is intended to be used for presentation purposes.
1389 """
1392 """
1390 d = {}
1393 d = {}
1391 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1394 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1392 value = getattr(self, attr)
1395 value = getattr(self, attr)
1393 if value is not None:
1396 if value is not None:
1394 d[subopt] = value
1397 d[subopt] = value
1395 return d
1398 return d
1396
1399
1397 # we instantiate one globally shared progress bar to avoid
1400 # we instantiate one globally shared progress bar to avoid
1398 # competing progress bars when multiple UI objects get created
1401 # competing progress bars when multiple UI objects get created
1399 _progresssingleton = None
1402 _progresssingleton = None
1400
1403
1401 def getprogbar(ui):
1404 def getprogbar(ui):
1402 global _progresssingleton
1405 global _progresssingleton
1403 if _progresssingleton is None:
1406 if _progresssingleton is None:
1404 # passing 'ui' object to the singleton is fishy,
1407 # passing 'ui' object to the singleton is fishy,
1405 # this is how the extension used to work but feel free to rework it.
1408 # this is how the extension used to work but feel free to rework it.
1406 _progresssingleton = progress.progbar(ui)
1409 _progresssingleton = progress.progbar(ui)
1407 return _progresssingleton
1410 return _progresssingleton
@@ -1,900 +1,911 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from __future__ import print_function
16 >>> from __future__ import print_function
17 >>> from hgclient import readchannel, runcommand, check
17 >>> from hgclient import readchannel, runcommand, check
18 >>> @check
18 >>> @check
19 ... def hellomessage(server):
19 ... def hellomessage(server):
20 ... ch, data = readchannel(server)
20 ... ch, data = readchannel(server)
21 ... print('%c, %r' % (ch, data))
21 ... print('%c, %r' % (ch, data))
22 ... # run an arbitrary command to make sure the next thing the server
22 ... # run an arbitrary command to make sure the next thing the server
23 ... # sends isn't part of the hello message
23 ... # sends isn't part of the hello message
24 ... runcommand(server, ['id'])
24 ... runcommand(server, ['id'])
25 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
25 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
26 *** runcommand id
26 *** runcommand id
27 000000000000 tip
27 000000000000 tip
28
28
29 >>> from hgclient import check
29 >>> from hgclient import check
30 >>> @check
30 >>> @check
31 ... def unknowncommand(server):
31 ... def unknowncommand(server):
32 ... server.stdin.write('unknowncommand\n')
32 ... server.stdin.write('unknowncommand\n')
33 abort: unknown command unknowncommand
33 abort: unknown command unknowncommand
34
34
35 >>> from hgclient import readchannel, runcommand, check
35 >>> from hgclient import readchannel, runcommand, check
36 >>> @check
36 >>> @check
37 ... def checkruncommand(server):
37 ... def checkruncommand(server):
38 ... # hello block
38 ... # hello block
39 ... readchannel(server)
39 ... readchannel(server)
40 ...
40 ...
41 ... # no args
41 ... # no args
42 ... runcommand(server, [])
42 ... runcommand(server, [])
43 ...
43 ...
44 ... # global options
44 ... # global options
45 ... runcommand(server, ['id', '--quiet'])
45 ... runcommand(server, ['id', '--quiet'])
46 ...
46 ...
47 ... # make sure global options don't stick through requests
47 ... # make sure global options don't stick through requests
48 ... runcommand(server, ['id'])
48 ... runcommand(server, ['id'])
49 ...
49 ...
50 ... # --config
50 ... # --config
51 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
51 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
52 ...
52 ...
53 ... # make sure --config doesn't stick
53 ... # make sure --config doesn't stick
54 ... runcommand(server, ['id'])
54 ... runcommand(server, ['id'])
55 ...
55 ...
56 ... # negative return code should be masked
56 ... # negative return code should be masked
57 ... runcommand(server, ['id', '-runknown'])
57 ... runcommand(server, ['id', '-runknown'])
58 *** runcommand
58 *** runcommand
59 Mercurial Distributed SCM
59 Mercurial Distributed SCM
60
60
61 basic commands:
61 basic commands:
62
62
63 add add the specified files on the next commit
63 add add the specified files on the next commit
64 annotate show changeset information by line for each file
64 annotate show changeset information by line for each file
65 clone make a copy of an existing repository
65 clone make a copy of an existing repository
66 commit commit the specified files or all outstanding changes
66 commit commit the specified files or all outstanding changes
67 diff diff repository (or selected files)
67 diff diff repository (or selected files)
68 export dump the header and diffs for one or more changesets
68 export dump the header and diffs for one or more changesets
69 forget forget the specified files on the next commit
69 forget forget the specified files on the next commit
70 init create a new repository in the given directory
70 init create a new repository in the given directory
71 log show revision history of entire repository or files
71 log show revision history of entire repository or files
72 merge merge another revision into working directory
72 merge merge another revision into working directory
73 pull pull changes from the specified source
73 pull pull changes from the specified source
74 push push changes to the specified destination
74 push push changes to the specified destination
75 remove remove the specified files on the next commit
75 remove remove the specified files on the next commit
76 serve start stand-alone webserver
76 serve start stand-alone webserver
77 status show changed files in the working directory
77 status show changed files in the working directory
78 summary summarize working directory state
78 summary summarize working directory state
79 update update working directory (or switch revisions)
79 update update working directory (or switch revisions)
80
80
81 (use 'hg help' for the full list of commands or 'hg -v' for details)
81 (use 'hg help' for the full list of commands or 'hg -v' for details)
82 *** runcommand id --quiet
82 *** runcommand id --quiet
83 000000000000
83 000000000000
84 *** runcommand id
84 *** runcommand id
85 000000000000 tip
85 000000000000 tip
86 *** runcommand id --config ui.quiet=True
86 *** runcommand id --config ui.quiet=True
87 000000000000
87 000000000000
88 *** runcommand id
88 *** runcommand id
89 000000000000 tip
89 000000000000 tip
90 *** runcommand id -runknown
90 *** runcommand id -runknown
91 abort: unknown revision 'unknown'!
91 abort: unknown revision 'unknown'!
92 [255]
92 [255]
93
93
94 >>> from hgclient import readchannel, check
94 >>> from hgclient import readchannel, check
95 >>> @check
95 >>> @check
96 ... def inputeof(server):
96 ... def inputeof(server):
97 ... readchannel(server)
97 ... readchannel(server)
98 ... server.stdin.write('runcommand\n')
98 ... server.stdin.write('runcommand\n')
99 ... # close stdin while server is waiting for input
99 ... # close stdin while server is waiting for input
100 ... server.stdin.close()
100 ... server.stdin.close()
101 ...
101 ...
102 ... # server exits with 1 if the pipe closed while reading the command
102 ... # server exits with 1 if the pipe closed while reading the command
103 ... print('server exit code =', server.wait())
103 ... print('server exit code =', server.wait())
104 server exit code = 1
104 server exit code = 1
105
105
106 >>> from hgclient import readchannel, runcommand, check, stringio
106 >>> from hgclient import readchannel, runcommand, check, stringio
107 >>> @check
107 >>> @check
108 ... def serverinput(server):
108 ... def serverinput(server):
109 ... readchannel(server)
109 ... readchannel(server)
110 ...
110 ...
111 ... patch = """
111 ... patch = """
112 ... # HG changeset patch
112 ... # HG changeset patch
113 ... # User test
113 ... # User test
114 ... # Date 0 0
114 ... # Date 0 0
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
116 ... # Parent 0000000000000000000000000000000000000000
116 ... # Parent 0000000000000000000000000000000000000000
117 ... 1
117 ... 1
118 ...
118 ...
119 ... diff -r 000000000000 -r c103a3dec114 a
119 ... diff -r 000000000000 -r c103a3dec114 a
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
122 ... @@ -0,0 +1,1 @@
122 ... @@ -0,0 +1,1 @@
123 ... +1
123 ... +1
124 ... """
124 ... """
125 ...
125 ...
126 ... runcommand(server, ['import', '-'], input=stringio(patch))
126 ... runcommand(server, ['import', '-'], input=stringio(patch))
127 ... runcommand(server, ['log'])
127 ... runcommand(server, ['log'])
128 *** runcommand import -
128 *** runcommand import -
129 applying patch from stdin
129 applying patch from stdin
130 *** runcommand log
130 *** runcommand log
131 changeset: 0:eff892de26ec
131 changeset: 0:eff892de26ec
132 tag: tip
132 tag: tip
133 user: test
133 user: test
134 date: Thu Jan 01 00:00:00 1970 +0000
134 date: Thu Jan 01 00:00:00 1970 +0000
135 summary: 1
135 summary: 1
136
136
137
137
138 check that "histedit --commands=-" can read rules from the input channel:
138 check that "histedit --commands=-" can read rules from the input channel:
139
139
140 >>> import cStringIO
140 >>> import cStringIO
141 >>> from hgclient import readchannel, runcommand, check
141 >>> from hgclient import readchannel, runcommand, check
142 >>> @check
142 >>> @check
143 ... def serverinput(server):
143 ... def serverinput(server):
144 ... readchannel(server)
144 ... readchannel(server)
145 ... rules = 'pick eff892de26ec\n'
145 ... rules = 'pick eff892de26ec\n'
146 ... runcommand(server, ['histedit', '0', '--commands=-',
146 ... runcommand(server, ['histedit', '0', '--commands=-',
147 ... '--config', 'extensions.histedit='],
147 ... '--config', 'extensions.histedit='],
148 ... input=cStringIO.StringIO(rules))
148 ... input=cStringIO.StringIO(rules))
149 *** runcommand histedit 0 --commands=- --config extensions.histedit=
149 *** runcommand histedit 0 --commands=- --config extensions.histedit=
150
150
151 check that --cwd doesn't persist between requests:
151 check that --cwd doesn't persist between requests:
152
152
153 $ mkdir foo
153 $ mkdir foo
154 $ touch foo/bar
154 $ touch foo/bar
155 >>> from hgclient import readchannel, runcommand, check
155 >>> from hgclient import readchannel, runcommand, check
156 >>> @check
156 >>> @check
157 ... def cwd(server):
157 ... def cwd(server):
158 ... readchannel(server)
158 ... readchannel(server)
159 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
159 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
160 ... runcommand(server, ['st', 'foo/bar'])
160 ... runcommand(server, ['st', 'foo/bar'])
161 *** runcommand --cwd foo st bar
161 *** runcommand --cwd foo st bar
162 ? bar
162 ? bar
163 *** runcommand st foo/bar
163 *** runcommand st foo/bar
164 ? foo/bar
164 ? foo/bar
165
165
166 $ rm foo/bar
166 $ rm foo/bar
167
167
168
168
169 check that local configs for the cached repo aren't inherited when -R is used:
169 check that local configs for the cached repo aren't inherited when -R is used:
170
170
171 $ cat <<EOF >> .hg/hgrc
171 $ cat <<EOF >> .hg/hgrc
172 > [ui]
172 > [ui]
173 > foo = bar
173 > foo = bar
174 > EOF
174 > EOF
175
175
176 >>> from hgclient import readchannel, sep, runcommand, check
176 >>> from hgclient import readchannel, sep, runcommand, check
177 >>> @check
177 >>> @check
178 ... def localhgrc(server):
178 ... def localhgrc(server):
179 ... readchannel(server)
179 ... readchannel(server)
180 ...
180 ...
181 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
181 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
182 ... # show it
182 ... # show it
183 ... runcommand(server, ['showconfig'], outfilter=sep)
183 ... runcommand(server, ['showconfig'], outfilter=sep)
184 ...
184 ...
185 ... # but not for this repo
185 ... # but not for this repo
186 ... runcommand(server, ['init', 'foo'])
186 ... runcommand(server, ['init', 'foo'])
187 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
187 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
188 *** runcommand showconfig
188 *** runcommand showconfig
189 bundle.mainreporoot=$TESTTMP/repo
189 bundle.mainreporoot=$TESTTMP/repo
190 defaults.backout=-d "0 0"
190 defaults.backout=-d "0 0"
191 defaults.commit=-d "0 0"
191 defaults.commit=-d "0 0"
192 defaults.shelve=--date "0 0"
192 defaults.shelve=--date "0 0"
193 defaults.tag=-d "0 0"
193 defaults.tag=-d "0 0"
194 devel.all-warnings=true
194 devel.all-warnings=true
195 largefiles.usercache=$TESTTMP/.cache/largefiles
195 largefiles.usercache=$TESTTMP/.cache/largefiles
196 ui.slash=True
196 ui.slash=True
197 ui.interactive=False
197 ui.interactive=False
198 ui.mergemarkers=detailed
198 ui.mergemarkers=detailed
199 ui.usehttp2=true (?)
199 ui.usehttp2=true (?)
200 ui.foo=bar
200 ui.foo=bar
201 ui.nontty=true
201 ui.nontty=true
202 *** runcommand init foo
202 *** runcommand init foo
203 *** runcommand -R foo showconfig ui defaults
203 *** runcommand -R foo showconfig ui defaults
204 defaults.backout=-d "0 0"
204 defaults.backout=-d "0 0"
205 defaults.commit=-d "0 0"
205 defaults.commit=-d "0 0"
206 defaults.shelve=--date "0 0"
206 defaults.shelve=--date "0 0"
207 defaults.tag=-d "0 0"
207 defaults.tag=-d "0 0"
208 ui.slash=True
208 ui.slash=True
209 ui.interactive=False
209 ui.interactive=False
210 ui.mergemarkers=detailed
210 ui.mergemarkers=detailed
211 ui.usehttp2=true (?)
211 ui.usehttp2=true (?)
212 ui.nontty=true
212 ui.nontty=true
213
213
214 $ rm -R foo
214 $ rm -R foo
215
215
216 #if windows
216 #if windows
217 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
217 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
218 #else
218 #else
219 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
219 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
220 #endif
220 #endif
221
221
222 $ cat <<EOF > hook.py
222 $ cat <<EOF > hook.py
223 > from __future__ import print_function
223 > from __future__ import print_function
224 > import sys
224 > import sys
225 > def hook(**args):
225 > def hook(**args):
226 > print('hook talking')
226 > print('hook talking')
227 > print('now try to read something: %r' % sys.stdin.read())
227 > print('now try to read something: %r' % sys.stdin.read())
228 > EOF
228 > EOF
229
229
230 >>> from hgclient import readchannel, runcommand, check, stringio
230 >>> from hgclient import readchannel, runcommand, check, stringio
231 >>> @check
231 >>> @check
232 ... def hookoutput(server):
232 ... def hookoutput(server):
233 ... readchannel(server)
233 ... readchannel(server)
234 ... runcommand(server, ['--config',
234 ... runcommand(server, ['--config',
235 ... 'hooks.pre-identify=python:hook.hook',
235 ... 'hooks.pre-identify=python:hook.hook',
236 ... 'id'],
236 ... 'id'],
237 ... input=stringio('some input'))
237 ... input=stringio('some input'))
238 *** runcommand --config hooks.pre-identify=python:hook.hook id
238 *** runcommand --config hooks.pre-identify=python:hook.hook id
239 eff892de26ec tip
239 eff892de26ec tip
240
240
241 $ rm hook.py*
241 $ rm hook.py*
242
242
243 $ echo a >> a
243 $ echo a >> a
244 >>> import os
244 >>> import os
245 >>> from hgclient import readchannel, runcommand, check
245 >>> from hgclient import readchannel, runcommand, check
246 >>> @check
246 >>> @check
247 ... def outsidechanges(server):
247 ... def outsidechanges(server):
248 ... readchannel(server)
248 ... readchannel(server)
249 ... runcommand(server, ['status'])
249 ... runcommand(server, ['status'])
250 ... os.system('hg ci -Am2')
250 ... os.system('hg ci -Am2')
251 ... runcommand(server, ['tip'])
251 ... runcommand(server, ['tip'])
252 ... runcommand(server, ['status'])
252 ... runcommand(server, ['status'])
253 *** runcommand status
253 *** runcommand status
254 M a
254 M a
255 *** runcommand tip
255 *** runcommand tip
256 changeset: 1:d3a0a68be6de
256 changeset: 1:d3a0a68be6de
257 tag: tip
257 tag: tip
258 user: test
258 user: test
259 date: Thu Jan 01 00:00:00 1970 +0000
259 date: Thu Jan 01 00:00:00 1970 +0000
260 summary: 2
260 summary: 2
261
261
262 *** runcommand status
262 *** runcommand status
263
263
264 >>> import os
264 >>> import os
265 >>> from hgclient import readchannel, runcommand, check
265 >>> from hgclient import readchannel, runcommand, check
266 >>> @check
266 >>> @check
267 ... def bookmarks(server):
267 ... def bookmarks(server):
268 ... readchannel(server)
268 ... readchannel(server)
269 ... runcommand(server, ['bookmarks'])
269 ... runcommand(server, ['bookmarks'])
270 ...
270 ...
271 ... # changes .hg/bookmarks
271 ... # changes .hg/bookmarks
272 ... os.system('hg bookmark -i bm1')
272 ... os.system('hg bookmark -i bm1')
273 ... os.system('hg bookmark -i bm2')
273 ... os.system('hg bookmark -i bm2')
274 ... runcommand(server, ['bookmarks'])
274 ... runcommand(server, ['bookmarks'])
275 ...
275 ...
276 ... # changes .hg/bookmarks.current
276 ... # changes .hg/bookmarks.current
277 ... os.system('hg upd bm1 -q')
277 ... os.system('hg upd bm1 -q')
278 ... runcommand(server, ['bookmarks'])
278 ... runcommand(server, ['bookmarks'])
279 ...
279 ...
280 ... runcommand(server, ['bookmarks', 'bm3'])
280 ... runcommand(server, ['bookmarks', 'bm3'])
281 ... f = open('a', 'ab')
281 ... f = open('a', 'ab')
282 ... f.write('a\n')
282 ... f.write('a\n')
283 ... f.close()
283 ... f.close()
284 ... runcommand(server, ['commit', '-Amm'])
284 ... runcommand(server, ['commit', '-Amm'])
285 ... runcommand(server, ['bookmarks'])
285 ... runcommand(server, ['bookmarks'])
286 *** runcommand bookmarks
286 *** runcommand bookmarks
287 no bookmarks set
287 no bookmarks set
288 *** runcommand bookmarks
288 *** runcommand bookmarks
289 bm1 1:d3a0a68be6de
289 bm1 1:d3a0a68be6de
290 bm2 1:d3a0a68be6de
290 bm2 1:d3a0a68be6de
291 *** runcommand bookmarks
291 *** runcommand bookmarks
292 * bm1 1:d3a0a68be6de
292 * bm1 1:d3a0a68be6de
293 bm2 1:d3a0a68be6de
293 bm2 1:d3a0a68be6de
294 *** runcommand bookmarks bm3
294 *** runcommand bookmarks bm3
295 *** runcommand commit -Amm
295 *** runcommand commit -Amm
296 *** runcommand bookmarks
296 *** runcommand bookmarks
297 bm1 1:d3a0a68be6de
297 bm1 1:d3a0a68be6de
298 bm2 1:d3a0a68be6de
298 bm2 1:d3a0a68be6de
299 * bm3 2:aef17e88f5f0
299 * bm3 2:aef17e88f5f0
300
300
301 >>> import os
301 >>> import os
302 >>> from hgclient import readchannel, runcommand, check
302 >>> from hgclient import readchannel, runcommand, check
303 >>> @check
303 >>> @check
304 ... def tagscache(server):
304 ... def tagscache(server):
305 ... readchannel(server)
305 ... readchannel(server)
306 ... runcommand(server, ['id', '-t', '-r', '0'])
306 ... runcommand(server, ['id', '-t', '-r', '0'])
307 ... os.system('hg tag -r 0 foo')
307 ... os.system('hg tag -r 0 foo')
308 ... runcommand(server, ['id', '-t', '-r', '0'])
308 ... runcommand(server, ['id', '-t', '-r', '0'])
309 *** runcommand id -t -r 0
309 *** runcommand id -t -r 0
310
310
311 *** runcommand id -t -r 0
311 *** runcommand id -t -r 0
312 foo
312 foo
313
313
314 >>> import os
314 >>> import os
315 >>> from hgclient import readchannel, runcommand, check
315 >>> from hgclient import readchannel, runcommand, check
316 >>> @check
316 >>> @check
317 ... def setphase(server):
317 ... def setphase(server):
318 ... readchannel(server)
318 ... readchannel(server)
319 ... runcommand(server, ['phase', '-r', '.'])
319 ... runcommand(server, ['phase', '-r', '.'])
320 ... os.system('hg phase -r . -p')
320 ... os.system('hg phase -r . -p')
321 ... runcommand(server, ['phase', '-r', '.'])
321 ... runcommand(server, ['phase', '-r', '.'])
322 *** runcommand phase -r .
322 *** runcommand phase -r .
323 3: draft
323 3: draft
324 *** runcommand phase -r .
324 *** runcommand phase -r .
325 3: public
325 3: public
326
326
327 $ echo a >> a
327 $ echo a >> a
328 >>> from hgclient import readchannel, runcommand, check
328 >>> from hgclient import readchannel, runcommand, check
329 >>> @check
329 >>> @check
330 ... def rollback(server):
330 ... def rollback(server):
331 ... readchannel(server)
331 ... readchannel(server)
332 ... runcommand(server, ['phase', '-r', '.', '-p'])
332 ... runcommand(server, ['phase', '-r', '.', '-p'])
333 ... runcommand(server, ['commit', '-Am.'])
333 ... runcommand(server, ['commit', '-Am.'])
334 ... runcommand(server, ['rollback'])
334 ... runcommand(server, ['rollback'])
335 ... runcommand(server, ['phase', '-r', '.'])
335 ... runcommand(server, ['phase', '-r', '.'])
336 *** runcommand phase -r . -p
336 *** runcommand phase -r . -p
337 no phases changed
337 no phases changed
338 *** runcommand commit -Am.
338 *** runcommand commit -Am.
339 *** runcommand rollback
339 *** runcommand rollback
340 repository tip rolled back to revision 3 (undo commit)
340 repository tip rolled back to revision 3 (undo commit)
341 working directory now based on revision 3
341 working directory now based on revision 3
342 *** runcommand phase -r .
342 *** runcommand phase -r .
343 3: public
343 3: public
344
344
345 >>> import os
345 >>> import os
346 >>> from hgclient import readchannel, runcommand, check
346 >>> from hgclient import readchannel, runcommand, check
347 >>> @check
347 >>> @check
348 ... def branch(server):
348 ... def branch(server):
349 ... readchannel(server)
349 ... readchannel(server)
350 ... runcommand(server, ['branch'])
350 ... runcommand(server, ['branch'])
351 ... os.system('hg branch foo')
351 ... os.system('hg branch foo')
352 ... runcommand(server, ['branch'])
352 ... runcommand(server, ['branch'])
353 ... os.system('hg branch default')
353 ... os.system('hg branch default')
354 *** runcommand branch
354 *** runcommand branch
355 default
355 default
356 marked working directory as branch foo
356 marked working directory as branch foo
357 (branches are permanent and global, did you want a bookmark?)
357 (branches are permanent and global, did you want a bookmark?)
358 *** runcommand branch
358 *** runcommand branch
359 foo
359 foo
360 marked working directory as branch default
360 marked working directory as branch default
361 (branches are permanent and global, did you want a bookmark?)
361 (branches are permanent and global, did you want a bookmark?)
362
362
363 $ touch .hgignore
363 $ touch .hgignore
364 >>> import os
364 >>> import os
365 >>> from hgclient import readchannel, runcommand, check
365 >>> from hgclient import readchannel, runcommand, check
366 >>> @check
366 >>> @check
367 ... def hgignore(server):
367 ... def hgignore(server):
368 ... readchannel(server)
368 ... readchannel(server)
369 ... runcommand(server, ['commit', '-Am.'])
369 ... runcommand(server, ['commit', '-Am.'])
370 ... f = open('ignored-file', 'ab')
370 ... f = open('ignored-file', 'ab')
371 ... f.write('')
371 ... f.write('')
372 ... f.close()
372 ... f.close()
373 ... f = open('.hgignore', 'ab')
373 ... f = open('.hgignore', 'ab')
374 ... f.write('ignored-file')
374 ... f.write('ignored-file')
375 ... f.close()
375 ... f.close()
376 ... runcommand(server, ['status', '-i', '-u'])
376 ... runcommand(server, ['status', '-i', '-u'])
377 *** runcommand commit -Am.
377 *** runcommand commit -Am.
378 adding .hgignore
378 adding .hgignore
379 *** runcommand status -i -u
379 *** runcommand status -i -u
380 I ignored-file
380 I ignored-file
381
381
382 cache of non-public revisions should be invalidated on repository change
382 cache of non-public revisions should be invalidated on repository change
383 (issue4855):
383 (issue4855):
384
384
385 >>> import os
385 >>> import os
386 >>> from hgclient import readchannel, runcommand, check
386 >>> from hgclient import readchannel, runcommand, check
387 >>> @check
387 >>> @check
388 ... def phasesetscacheaftercommit(server):
388 ... def phasesetscacheaftercommit(server):
389 ... readchannel(server)
389 ... readchannel(server)
390 ... # load _phasecache._phaserevs and _phasesets
390 ... # load _phasecache._phaserevs and _phasesets
391 ... runcommand(server, ['log', '-qr', 'draft()'])
391 ... runcommand(server, ['log', '-qr', 'draft()'])
392 ... # create draft commits by another process
392 ... # create draft commits by another process
393 ... for i in xrange(5, 7):
393 ... for i in xrange(5, 7):
394 ... f = open('a', 'ab')
394 ... f = open('a', 'ab')
395 ... f.seek(0, os.SEEK_END)
395 ... f.seek(0, os.SEEK_END)
396 ... f.write('a\n')
396 ... f.write('a\n')
397 ... f.close()
397 ... f.close()
398 ... os.system('hg commit -Aqm%d' % i)
398 ... os.system('hg commit -Aqm%d' % i)
399 ... # new commits should be listed as draft revisions
399 ... # new commits should be listed as draft revisions
400 ... runcommand(server, ['log', '-qr', 'draft()'])
400 ... runcommand(server, ['log', '-qr', 'draft()'])
401 *** runcommand log -qr draft()
401 *** runcommand log -qr draft()
402 4:7966c8e3734d
402 4:7966c8e3734d
403 *** runcommand log -qr draft()
403 *** runcommand log -qr draft()
404 4:7966c8e3734d
404 4:7966c8e3734d
405 5:41f6602d1c4f
405 5:41f6602d1c4f
406 6:10501e202c35
406 6:10501e202c35
407
407
408 >>> import os
408 >>> import os
409 >>> from hgclient import readchannel, runcommand, check
409 >>> from hgclient import readchannel, runcommand, check
410 >>> @check
410 >>> @check
411 ... def phasesetscacheafterstrip(server):
411 ... def phasesetscacheafterstrip(server):
412 ... readchannel(server)
412 ... readchannel(server)
413 ... # load _phasecache._phaserevs and _phasesets
413 ... # load _phasecache._phaserevs and _phasesets
414 ... runcommand(server, ['log', '-qr', 'draft()'])
414 ... runcommand(server, ['log', '-qr', 'draft()'])
415 ... # strip cached revisions by another process
415 ... # strip cached revisions by another process
416 ... os.system('hg --config extensions.strip= strip -q 5')
416 ... os.system('hg --config extensions.strip= strip -q 5')
417 ... # shouldn't abort by "unknown revision '6'"
417 ... # shouldn't abort by "unknown revision '6'"
418 ... runcommand(server, ['log', '-qr', 'draft()'])
418 ... runcommand(server, ['log', '-qr', 'draft()'])
419 *** runcommand log -qr draft()
419 *** runcommand log -qr draft()
420 4:7966c8e3734d
420 4:7966c8e3734d
421 5:41f6602d1c4f
421 5:41f6602d1c4f
422 6:10501e202c35
422 6:10501e202c35
423 *** runcommand log -qr draft()
423 *** runcommand log -qr draft()
424 4:7966c8e3734d
424 4:7966c8e3734d
425
425
426 cache of phase roots should be invalidated on strip (issue3827):
426 cache of phase roots should be invalidated on strip (issue3827):
427
427
428 >>> import os
428 >>> import os
429 >>> from hgclient import readchannel, sep, runcommand, check
429 >>> from hgclient import readchannel, sep, runcommand, check
430 >>> @check
430 >>> @check
431 ... def phasecacheafterstrip(server):
431 ... def phasecacheafterstrip(server):
432 ... readchannel(server)
432 ... readchannel(server)
433 ...
433 ...
434 ... # create new head, 5:731265503d86
434 ... # create new head, 5:731265503d86
435 ... runcommand(server, ['update', '-C', '0'])
435 ... runcommand(server, ['update', '-C', '0'])
436 ... f = open('a', 'ab')
436 ... f = open('a', 'ab')
437 ... f.write('a\n')
437 ... f.write('a\n')
438 ... f.close()
438 ... f.close()
439 ... runcommand(server, ['commit', '-Am.', 'a'])
439 ... runcommand(server, ['commit', '-Am.', 'a'])
440 ... runcommand(server, ['log', '-Gq'])
440 ... runcommand(server, ['log', '-Gq'])
441 ...
441 ...
442 ... # make it public; draft marker moves to 4:7966c8e3734d
442 ... # make it public; draft marker moves to 4:7966c8e3734d
443 ... runcommand(server, ['phase', '-p', '.'])
443 ... runcommand(server, ['phase', '-p', '.'])
444 ... # load _phasecache.phaseroots
444 ... # load _phasecache.phaseroots
445 ... runcommand(server, ['phase', '.'], outfilter=sep)
445 ... runcommand(server, ['phase', '.'], outfilter=sep)
446 ...
446 ...
447 ... # strip 1::4 outside server
447 ... # strip 1::4 outside server
448 ... os.system('hg -q --config extensions.mq= strip 1')
448 ... os.system('hg -q --config extensions.mq= strip 1')
449 ...
449 ...
450 ... # shouldn't raise "7966c8e3734d: no node!"
450 ... # shouldn't raise "7966c8e3734d: no node!"
451 ... runcommand(server, ['branches'])
451 ... runcommand(server, ['branches'])
452 *** runcommand update -C 0
452 *** runcommand update -C 0
453 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
453 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
454 (leaving bookmark bm3)
454 (leaving bookmark bm3)
455 *** runcommand commit -Am. a
455 *** runcommand commit -Am. a
456 created new head
456 created new head
457 *** runcommand log -Gq
457 *** runcommand log -Gq
458 @ 5:731265503d86
458 @ 5:731265503d86
459 |
459 |
460 | o 4:7966c8e3734d
460 | o 4:7966c8e3734d
461 | |
461 | |
462 | o 3:b9b85890c400
462 | o 3:b9b85890c400
463 | |
463 | |
464 | o 2:aef17e88f5f0
464 | o 2:aef17e88f5f0
465 | |
465 | |
466 | o 1:d3a0a68be6de
466 | o 1:d3a0a68be6de
467 |/
467 |/
468 o 0:eff892de26ec
468 o 0:eff892de26ec
469
469
470 *** runcommand phase -p .
470 *** runcommand phase -p .
471 *** runcommand phase .
471 *** runcommand phase .
472 5: public
472 5: public
473 *** runcommand branches
473 *** runcommand branches
474 default 1:731265503d86
474 default 1:731265503d86
475
475
476 in-memory cache must be reloaded if transaction is aborted. otherwise
476 in-memory cache must be reloaded if transaction is aborted. otherwise
477 changelog and manifest would have invalid node:
477 changelog and manifest would have invalid node:
478
478
479 $ echo a >> a
479 $ echo a >> a
480 >>> from hgclient import readchannel, runcommand, check
480 >>> from hgclient import readchannel, runcommand, check
481 >>> @check
481 >>> @check
482 ... def txabort(server):
482 ... def txabort(server):
483 ... readchannel(server)
483 ... readchannel(server)
484 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
484 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
485 ... '-mfoo'])
485 ... '-mfoo'])
486 ... runcommand(server, ['verify'])
486 ... runcommand(server, ['verify'])
487 *** runcommand commit --config hooks.pretxncommit=false -mfoo
487 *** runcommand commit --config hooks.pretxncommit=false -mfoo
488 transaction abort!
488 transaction abort!
489 rollback completed
489 rollback completed
490 abort: pretxncommit hook exited with status 1
490 abort: pretxncommit hook exited with status 1
491 [255]
491 [255]
492 *** runcommand verify
492 *** runcommand verify
493 checking changesets
493 checking changesets
494 checking manifests
494 checking manifests
495 crosschecking files in changesets and manifests
495 crosschecking files in changesets and manifests
496 checking files
496 checking files
497 1 files, 2 changesets, 2 total revisions
497 1 files, 2 changesets, 2 total revisions
498 $ hg revert --no-backup -aq
498 $ hg revert --no-backup -aq
499
499
500 $ cat >> .hg/hgrc << EOF
500 $ cat >> .hg/hgrc << EOF
501 > [experimental]
501 > [experimental]
502 > evolution=createmarkers
502 > evolution=createmarkers
503 > EOF
503 > EOF
504
504
505 >>> import os
505 >>> import os
506 >>> from hgclient import readchannel, runcommand, check
506 >>> from hgclient import readchannel, runcommand, check
507 >>> @check
507 >>> @check
508 ... def obsolete(server):
508 ... def obsolete(server):
509 ... readchannel(server)
509 ... readchannel(server)
510 ...
510 ...
511 ... runcommand(server, ['up', 'null'])
511 ... runcommand(server, ['up', 'null'])
512 ... runcommand(server, ['phase', '-df', 'tip'])
512 ... runcommand(server, ['phase', '-df', 'tip'])
513 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
513 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
514 ... if os.name == 'nt':
514 ... if os.name == 'nt':
515 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
515 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
516 ... os.system(cmd)
516 ... os.system(cmd)
517 ... runcommand(server, ['log', '--hidden'])
517 ... runcommand(server, ['log', '--hidden'])
518 ... runcommand(server, ['log'])
518 ... runcommand(server, ['log'])
519 *** runcommand up null
519 *** runcommand up null
520 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
520 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
521 *** runcommand phase -df tip
521 *** runcommand phase -df tip
522 *** runcommand log --hidden
522 *** runcommand log --hidden
523 changeset: 1:731265503d86
523 changeset: 1:731265503d86
524 tag: tip
524 tag: tip
525 user: test
525 user: test
526 date: Thu Jan 01 00:00:00 1970 +0000
526 date: Thu Jan 01 00:00:00 1970 +0000
527 summary: .
527 summary: .
528
528
529 changeset: 0:eff892de26ec
529 changeset: 0:eff892de26ec
530 bookmark: bm1
530 bookmark: bm1
531 bookmark: bm2
531 bookmark: bm2
532 bookmark: bm3
532 bookmark: bm3
533 user: test
533 user: test
534 date: Thu Jan 01 00:00:00 1970 +0000
534 date: Thu Jan 01 00:00:00 1970 +0000
535 summary: 1
535 summary: 1
536
536
537 *** runcommand log
537 *** runcommand log
538 changeset: 0:eff892de26ec
538 changeset: 0:eff892de26ec
539 bookmark: bm1
539 bookmark: bm1
540 bookmark: bm2
540 bookmark: bm2
541 bookmark: bm3
541 bookmark: bm3
542 tag: tip
542 tag: tip
543 user: test
543 user: test
544 date: Thu Jan 01 00:00:00 1970 +0000
544 date: Thu Jan 01 00:00:00 1970 +0000
545 summary: 1
545 summary: 1
546
546
547
547
548 $ cat <<EOF >> .hg/hgrc
548 $ cat <<EOF >> .hg/hgrc
549 > [extensions]
549 > [extensions]
550 > mq =
550 > mq =
551 > EOF
551 > EOF
552
552
553 >>> import os
553 >>> import os
554 >>> from hgclient import readchannel, runcommand, check
554 >>> from hgclient import readchannel, runcommand, check
555 >>> @check
555 >>> @check
556 ... def mqoutsidechanges(server):
556 ... def mqoutsidechanges(server):
557 ... readchannel(server)
557 ... readchannel(server)
558 ...
558 ...
559 ... # load repo.mq
559 ... # load repo.mq
560 ... runcommand(server, ['qapplied'])
560 ... runcommand(server, ['qapplied'])
561 ... os.system('hg qnew 0.diff')
561 ... os.system('hg qnew 0.diff')
562 ... # repo.mq should be invalidated
562 ... # repo.mq should be invalidated
563 ... runcommand(server, ['qapplied'])
563 ... runcommand(server, ['qapplied'])
564 ...
564 ...
565 ... runcommand(server, ['qpop', '--all'])
565 ... runcommand(server, ['qpop', '--all'])
566 ... os.system('hg qqueue --create foo')
566 ... os.system('hg qqueue --create foo')
567 ... # repo.mq should be recreated to point to new queue
567 ... # repo.mq should be recreated to point to new queue
568 ... runcommand(server, ['qqueue', '--active'])
568 ... runcommand(server, ['qqueue', '--active'])
569 *** runcommand qapplied
569 *** runcommand qapplied
570 *** runcommand qapplied
570 *** runcommand qapplied
571 0.diff
571 0.diff
572 *** runcommand qpop --all
572 *** runcommand qpop --all
573 popping 0.diff
573 popping 0.diff
574 patch queue now empty
574 patch queue now empty
575 *** runcommand qqueue --active
575 *** runcommand qqueue --active
576 foo
576 foo
577
577
578 $ cat <<EOF > dbgui.py
578 $ cat <<EOF > dbgui.py
579 > import os, sys
579 > import os, sys
580 > from mercurial import cmdutil, commands
580 > from mercurial import cmdutil, commands
581 > cmdtable = {}
581 > cmdtable = {}
582 > command = cmdutil.command(cmdtable)
582 > command = cmdutil.command(cmdtable)
583 > @command("debuggetpass", norepo=True)
583 > @command("debuggetpass", norepo=True)
584 > def debuggetpass(ui):
584 > def debuggetpass(ui):
585 > ui.write("%s\\n" % ui.getpass())
585 > ui.write("%s\\n" % ui.getpass())
586 > @command("debugprompt", norepo=True)
586 > @command("debugprompt", norepo=True)
587 > def debugprompt(ui):
587 > def debugprompt(ui):
588 > ui.write("%s\\n" % ui.prompt("prompt:"))
588 > ui.write("%s\\n" % ui.prompt("prompt:"))
589 > @command("debugreadstdin", norepo=True)
589 > @command("debugreadstdin", norepo=True)
590 > def debugreadstdin(ui):
590 > def debugreadstdin(ui):
591 > ui.write("read: %r\n" % sys.stdin.read(1))
591 > ui.write("read: %r\n" % sys.stdin.read(1))
592 > @command("debugwritestdout", norepo=True)
592 > @command("debugwritestdout", norepo=True)
593 > def debugwritestdout(ui):
593 > def debugwritestdout(ui):
594 > os.write(1, "low-level stdout fd and\n")
594 > os.write(1, "low-level stdout fd and\n")
595 > sys.stdout.write("stdout should be redirected to /dev/null\n")
595 > sys.stdout.write("stdout should be redirected to /dev/null\n")
596 > sys.stdout.flush()
596 > sys.stdout.flush()
597 > EOF
597 > EOF
598 $ cat <<EOF >> .hg/hgrc
598 $ cat <<EOF >> .hg/hgrc
599 > [extensions]
599 > [extensions]
600 > dbgui = dbgui.py
600 > dbgui = dbgui.py
601 > EOF
601 > EOF
602
602
603 >>> from hgclient import readchannel, runcommand, check, stringio
603 >>> from hgclient import readchannel, runcommand, check, stringio
604 >>> @check
604 >>> @check
605 ... def getpass(server):
605 ... def getpass(server):
606 ... readchannel(server)
606 ... readchannel(server)
607 ... runcommand(server, ['debuggetpass', '--config',
607 ... runcommand(server, ['debuggetpass', '--config',
608 ... 'ui.interactive=True'],
608 ... 'ui.interactive=True'],
609 ... input=stringio('1234\n'))
609 ... input=stringio('1234\n'))
610 ... runcommand(server, ['debuggetpass', '--config',
611 ... 'ui.interactive=True'],
612 ... input=stringio('\n'))
613 ... runcommand(server, ['debuggetpass', '--config',
614 ... 'ui.interactive=True'],
615 ... input=stringio(''))
610 ... runcommand(server, ['debugprompt', '--config',
616 ... runcommand(server, ['debugprompt', '--config',
611 ... 'ui.interactive=True'],
617 ... 'ui.interactive=True'],
612 ... input=stringio('5678\n'))
618 ... input=stringio('5678\n'))
613 ... runcommand(server, ['debugreadstdin'])
619 ... runcommand(server, ['debugreadstdin'])
614 ... runcommand(server, ['debugwritestdout'])
620 ... runcommand(server, ['debugwritestdout'])
615 *** runcommand debuggetpass --config ui.interactive=True
621 *** runcommand debuggetpass --config ui.interactive=True
616 password: 1234
622 password: 1234
623 *** runcommand debuggetpass --config ui.interactive=True
624 password:
625 *** runcommand debuggetpass --config ui.interactive=True
626 password: abort: response expected
627 [255]
617 *** runcommand debugprompt --config ui.interactive=True
628 *** runcommand debugprompt --config ui.interactive=True
618 prompt: 5678
629 prompt: 5678
619 *** runcommand debugreadstdin
630 *** runcommand debugreadstdin
620 read: ''
631 read: ''
621 *** runcommand debugwritestdout
632 *** runcommand debugwritestdout
622
633
623
634
624 run commandserver in commandserver, which is silly but should work:
635 run commandserver in commandserver, which is silly but should work:
625
636
626 >>> from __future__ import print_function
637 >>> from __future__ import print_function
627 >>> from hgclient import readchannel, runcommand, check, stringio
638 >>> from hgclient import readchannel, runcommand, check, stringio
628 >>> @check
639 >>> @check
629 ... def nested(server):
640 ... def nested(server):
630 ... print('%c, %r' % readchannel(server))
641 ... print('%c, %r' % readchannel(server))
631 ... class nestedserver(object):
642 ... class nestedserver(object):
632 ... stdin = stringio('getencoding\n')
643 ... stdin = stringio('getencoding\n')
633 ... stdout = stringio()
644 ... stdout = stringio()
634 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
645 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
635 ... output=nestedserver.stdout, input=nestedserver.stdin)
646 ... output=nestedserver.stdout, input=nestedserver.stdin)
636 ... nestedserver.stdout.seek(0)
647 ... nestedserver.stdout.seek(0)
637 ... print('%c, %r' % readchannel(nestedserver)) # hello
648 ... print('%c, %r' % readchannel(nestedserver)) # hello
638 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
649 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
639 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
650 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
640 *** runcommand serve --cmdserver pipe
651 *** runcommand serve --cmdserver pipe
641 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
652 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
642 r, '*' (glob)
653 r, '*' (glob)
643
654
644
655
645 start without repository:
656 start without repository:
646
657
647 $ cd ..
658 $ cd ..
648
659
649 >>> from __future__ import print_function
660 >>> from __future__ import print_function
650 >>> from hgclient import readchannel, runcommand, check
661 >>> from hgclient import readchannel, runcommand, check
651 >>> @check
662 >>> @check
652 ... def hellomessage(server):
663 ... def hellomessage(server):
653 ... ch, data = readchannel(server)
664 ... ch, data = readchannel(server)
654 ... print('%c, %r' % (ch, data))
665 ... print('%c, %r' % (ch, data))
655 ... # run an arbitrary command to make sure the next thing the server
666 ... # run an arbitrary command to make sure the next thing the server
656 ... # sends isn't part of the hello message
667 ... # sends isn't part of the hello message
657 ... runcommand(server, ['id'])
668 ... runcommand(server, ['id'])
658 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
669 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
659 *** runcommand id
670 *** runcommand id
660 abort: there is no Mercurial repository here (.hg not found)
671 abort: there is no Mercurial repository here (.hg not found)
661 [255]
672 [255]
662
673
663 >>> from hgclient import readchannel, runcommand, check
674 >>> from hgclient import readchannel, runcommand, check
664 >>> @check
675 >>> @check
665 ... def startwithoutrepo(server):
676 ... def startwithoutrepo(server):
666 ... readchannel(server)
677 ... readchannel(server)
667 ... runcommand(server, ['init', 'repo2'])
678 ... runcommand(server, ['init', 'repo2'])
668 ... runcommand(server, ['id', '-R', 'repo2'])
679 ... runcommand(server, ['id', '-R', 'repo2'])
669 *** runcommand init repo2
680 *** runcommand init repo2
670 *** runcommand id -R repo2
681 *** runcommand id -R repo2
671 000000000000 tip
682 000000000000 tip
672
683
673
684
674 don't fall back to cwd if invalid -R path is specified (issue4805):
685 don't fall back to cwd if invalid -R path is specified (issue4805):
675
686
676 $ cd repo
687 $ cd repo
677 $ hg serve --cmdserver pipe -R ../nonexistent
688 $ hg serve --cmdserver pipe -R ../nonexistent
678 abort: repository ../nonexistent not found!
689 abort: repository ../nonexistent not found!
679 [255]
690 [255]
680 $ cd ..
691 $ cd ..
681
692
682
693
683 unix domain socket:
694 unix domain socket:
684
695
685 $ cd repo
696 $ cd repo
686 $ hg update -q
697 $ hg update -q
687
698
688 #if unix-socket unix-permissions
699 #if unix-socket unix-permissions
689
700
690 >>> from __future__ import print_function
701 >>> from __future__ import print_function
691 >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
702 >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
692 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
703 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
693 >>> def hellomessage(conn):
704 >>> def hellomessage(conn):
694 ... ch, data = readchannel(conn)
705 ... ch, data = readchannel(conn)
695 ... print('%c, %r' % (ch, data))
706 ... print('%c, %r' % (ch, data))
696 ... runcommand(conn, ['id'])
707 ... runcommand(conn, ['id'])
697 >>> check(hellomessage, server.connect)
708 >>> check(hellomessage, server.connect)
698 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
709 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
699 *** runcommand id
710 *** runcommand id
700 eff892de26ec tip bm1/bm2/bm3
711 eff892de26ec tip bm1/bm2/bm3
701 >>> def unknowncommand(conn):
712 >>> def unknowncommand(conn):
702 ... readchannel(conn)
713 ... readchannel(conn)
703 ... conn.stdin.write('unknowncommand\n')
714 ... conn.stdin.write('unknowncommand\n')
704 >>> check(unknowncommand, server.connect) # error sent to server.log
715 >>> check(unknowncommand, server.connect) # error sent to server.log
705 >>> def serverinput(conn):
716 >>> def serverinput(conn):
706 ... readchannel(conn)
717 ... readchannel(conn)
707 ... patch = """
718 ... patch = """
708 ... # HG changeset patch
719 ... # HG changeset patch
709 ... # User test
720 ... # User test
710 ... # Date 0 0
721 ... # Date 0 0
711 ... 2
722 ... 2
712 ...
723 ...
713 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
724 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
714 ... --- a/a
725 ... --- a/a
715 ... +++ b/a
726 ... +++ b/a
716 ... @@ -1,1 +1,2 @@
727 ... @@ -1,1 +1,2 @@
717 ... 1
728 ... 1
718 ... +2
729 ... +2
719 ... """
730 ... """
720 ... runcommand(conn, ['import', '-'], input=stringio(patch))
731 ... runcommand(conn, ['import', '-'], input=stringio(patch))
721 ... runcommand(conn, ['log', '-rtip', '-q'])
732 ... runcommand(conn, ['log', '-rtip', '-q'])
722 >>> check(serverinput, server.connect)
733 >>> check(serverinput, server.connect)
723 *** runcommand import -
734 *** runcommand import -
724 applying patch from stdin
735 applying patch from stdin
725 *** runcommand log -rtip -q
736 *** runcommand log -rtip -q
726 2:1ed24be7e7a0
737 2:1ed24be7e7a0
727 >>> server.shutdown()
738 >>> server.shutdown()
728
739
729 $ cat .hg/server.log
740 $ cat .hg/server.log
730 listening at .hg/server.sock
741 listening at .hg/server.sock
731 abort: unknown command unknowncommand
742 abort: unknown command unknowncommand
732 killed!
743 killed!
733 $ rm .hg/server.log
744 $ rm .hg/server.log
734
745
735 if server crashed before hello, traceback will be sent to 'e' channel as
746 if server crashed before hello, traceback will be sent to 'e' channel as
736 last ditch:
747 last ditch:
737
748
738 $ cat <<EOF >> .hg/hgrc
749 $ cat <<EOF >> .hg/hgrc
739 > [cmdserver]
750 > [cmdserver]
740 > log = inexistent/path.log
751 > log = inexistent/path.log
741 > EOF
752 > EOF
742 >>> from __future__ import print_function
753 >>> from __future__ import print_function
743 >>> from hgclient import unixserver, readchannel, check
754 >>> from hgclient import unixserver, readchannel, check
744 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
755 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
745 >>> def earlycrash(conn):
756 >>> def earlycrash(conn):
746 ... while True:
757 ... while True:
747 ... try:
758 ... try:
748 ... ch, data = readchannel(conn)
759 ... ch, data = readchannel(conn)
749 ... if not data.startswith(' '):
760 ... if not data.startswith(' '):
750 ... print('%c, %r' % (ch, data))
761 ... print('%c, %r' % (ch, data))
751 ... except EOFError:
762 ... except EOFError:
752 ... break
763 ... break
753 >>> check(earlycrash, server.connect)
764 >>> check(earlycrash, server.connect)
754 e, 'Traceback (most recent call last):\n'
765 e, 'Traceback (most recent call last):\n'
755 e, "IOError: *" (glob)
766 e, "IOError: *" (glob)
756 >>> server.shutdown()
767 >>> server.shutdown()
757
768
758 $ cat .hg/server.log | grep -v '^ '
769 $ cat .hg/server.log | grep -v '^ '
759 listening at .hg/server.sock
770 listening at .hg/server.sock
760 Traceback (most recent call last):
771 Traceback (most recent call last):
761 IOError: * (glob)
772 IOError: * (glob)
762 killed!
773 killed!
763 #endif
774 #endif
764 #if no-unix-socket
775 #if no-unix-socket
765
776
766 $ hg serve --cmdserver unix -a .hg/server.sock
777 $ hg serve --cmdserver unix -a .hg/server.sock
767 abort: unsupported platform
778 abort: unsupported platform
768 [255]
779 [255]
769
780
770 #endif
781 #endif
771
782
772 $ cd ..
783 $ cd ..
773
784
774 Test that accessing to invalid changelog cache is avoided at
785 Test that accessing to invalid changelog cache is avoided at
775 subsequent operations even if repo object is reused even after failure
786 subsequent operations even if repo object is reused even after failure
776 of transaction (see 0a7610758c42 also)
787 of transaction (see 0a7610758c42 also)
777
788
778 "hg log" after failure of transaction is needed to detect invalid
789 "hg log" after failure of transaction is needed to detect invalid
779 cache in repoview: this can't detect by "hg verify" only.
790 cache in repoview: this can't detect by "hg verify" only.
780
791
781 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
792 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
782 4) are tested, because '00changelog.i' are differently changed in each
793 4) are tested, because '00changelog.i' are differently changed in each
783 cases.
794 cases.
784
795
785 $ cat > $TESTTMP/failafterfinalize.py <<EOF
796 $ cat > $TESTTMP/failafterfinalize.py <<EOF
786 > # extension to abort transaction after finalization forcibly
797 > # extension to abort transaction after finalization forcibly
787 > from mercurial import commands, error, extensions, lock as lockmod
798 > from mercurial import commands, error, extensions, lock as lockmod
788 > def fail(tr):
799 > def fail(tr):
789 > raise error.Abort('fail after finalization')
800 > raise error.Abort('fail after finalization')
790 > def reposetup(ui, repo):
801 > def reposetup(ui, repo):
791 > class failrepo(repo.__class__):
802 > class failrepo(repo.__class__):
792 > def commitctx(self, ctx, error=False):
803 > def commitctx(self, ctx, error=False):
793 > if self.ui.configbool('failafterfinalize', 'fail'):
804 > if self.ui.configbool('failafterfinalize', 'fail'):
794 > # 'sorted()' by ASCII code on category names causes
805 > # 'sorted()' by ASCII code on category names causes
795 > # invoking 'fail' after finalization of changelog
806 > # invoking 'fail' after finalization of changelog
796 > # using "'cl-%i' % id(self)" as category name
807 > # using "'cl-%i' % id(self)" as category name
797 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
808 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
798 > return super(failrepo, self).commitctx(ctx, error)
809 > return super(failrepo, self).commitctx(ctx, error)
799 > repo.__class__ = failrepo
810 > repo.__class__ = failrepo
800 > EOF
811 > EOF
801
812
802 $ hg init repo3
813 $ hg init repo3
803 $ cd repo3
814 $ cd repo3
804
815
805 $ cat <<EOF >> $HGRCPATH
816 $ cat <<EOF >> $HGRCPATH
806 > [ui]
817 > [ui]
807 > logtemplate = {rev} {desc|firstline} ({files})\n
818 > logtemplate = {rev} {desc|firstline} ({files})\n
808 >
819 >
809 > [extensions]
820 > [extensions]
810 > failafterfinalize = $TESTTMP/failafterfinalize.py
821 > failafterfinalize = $TESTTMP/failafterfinalize.py
811 > EOF
822 > EOF
812
823
813 - test failure with "empty changelog"
824 - test failure with "empty changelog"
814
825
815 $ echo foo > foo
826 $ echo foo > foo
816 $ hg add foo
827 $ hg add foo
817
828
818 (failure before finalization)
829 (failure before finalization)
819
830
820 >>> from hgclient import readchannel, runcommand, check
831 >>> from hgclient import readchannel, runcommand, check
821 >>> @check
832 >>> @check
822 ... def abort(server):
833 ... def abort(server):
823 ... readchannel(server)
834 ... readchannel(server)
824 ... runcommand(server, ['commit',
835 ... runcommand(server, ['commit',
825 ... '--config', 'hooks.pretxncommit=false',
836 ... '--config', 'hooks.pretxncommit=false',
826 ... '-mfoo'])
837 ... '-mfoo'])
827 ... runcommand(server, ['log'])
838 ... runcommand(server, ['log'])
828 ... runcommand(server, ['verify', '-q'])
839 ... runcommand(server, ['verify', '-q'])
829 *** runcommand commit --config hooks.pretxncommit=false -mfoo
840 *** runcommand commit --config hooks.pretxncommit=false -mfoo
830 transaction abort!
841 transaction abort!
831 rollback completed
842 rollback completed
832 abort: pretxncommit hook exited with status 1
843 abort: pretxncommit hook exited with status 1
833 [255]
844 [255]
834 *** runcommand log
845 *** runcommand log
835 *** runcommand verify -q
846 *** runcommand verify -q
836
847
837 (failure after finalization)
848 (failure after finalization)
838
849
839 >>> from hgclient import readchannel, runcommand, check
850 >>> from hgclient import readchannel, runcommand, check
840 >>> @check
851 >>> @check
841 ... def abort(server):
852 ... def abort(server):
842 ... readchannel(server)
853 ... readchannel(server)
843 ... runcommand(server, ['commit',
854 ... runcommand(server, ['commit',
844 ... '--config', 'failafterfinalize.fail=true',
855 ... '--config', 'failafterfinalize.fail=true',
845 ... '-mfoo'])
856 ... '-mfoo'])
846 ... runcommand(server, ['log'])
857 ... runcommand(server, ['log'])
847 ... runcommand(server, ['verify', '-q'])
858 ... runcommand(server, ['verify', '-q'])
848 *** runcommand commit --config failafterfinalize.fail=true -mfoo
859 *** runcommand commit --config failafterfinalize.fail=true -mfoo
849 transaction abort!
860 transaction abort!
850 rollback completed
861 rollback completed
851 abort: fail after finalization
862 abort: fail after finalization
852 [255]
863 [255]
853 *** runcommand log
864 *** runcommand log
854 *** runcommand verify -q
865 *** runcommand verify -q
855
866
856 - test failure with "not-empty changelog"
867 - test failure with "not-empty changelog"
857
868
858 $ echo bar > bar
869 $ echo bar > bar
859 $ hg add bar
870 $ hg add bar
860 $ hg commit -mbar bar
871 $ hg commit -mbar bar
861
872
862 (failure before finalization)
873 (failure before finalization)
863
874
864 >>> from hgclient import readchannel, runcommand, check
875 >>> from hgclient import readchannel, runcommand, check
865 >>> @check
876 >>> @check
866 ... def abort(server):
877 ... def abort(server):
867 ... readchannel(server)
878 ... readchannel(server)
868 ... runcommand(server, ['commit',
879 ... runcommand(server, ['commit',
869 ... '--config', 'hooks.pretxncommit=false',
880 ... '--config', 'hooks.pretxncommit=false',
870 ... '-mfoo', 'foo'])
881 ... '-mfoo', 'foo'])
871 ... runcommand(server, ['log'])
882 ... runcommand(server, ['log'])
872 ... runcommand(server, ['verify', '-q'])
883 ... runcommand(server, ['verify', '-q'])
873 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
884 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
874 transaction abort!
885 transaction abort!
875 rollback completed
886 rollback completed
876 abort: pretxncommit hook exited with status 1
887 abort: pretxncommit hook exited with status 1
877 [255]
888 [255]
878 *** runcommand log
889 *** runcommand log
879 0 bar (bar)
890 0 bar (bar)
880 *** runcommand verify -q
891 *** runcommand verify -q
881
892
882 (failure after finalization)
893 (failure after finalization)
883
894
884 >>> from hgclient import readchannel, runcommand, check
895 >>> from hgclient import readchannel, runcommand, check
885 >>> @check
896 >>> @check
886 ... def abort(server):
897 ... def abort(server):
887 ... readchannel(server)
898 ... readchannel(server)
888 ... runcommand(server, ['commit',
899 ... runcommand(server, ['commit',
889 ... '--config', 'failafterfinalize.fail=true',
900 ... '--config', 'failafterfinalize.fail=true',
890 ... '-mfoo', 'foo'])
901 ... '-mfoo', 'foo'])
891 ... runcommand(server, ['log'])
902 ... runcommand(server, ['log'])
892 ... runcommand(server, ['verify', '-q'])
903 ... runcommand(server, ['verify', '-q'])
893 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
904 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
894 transaction abort!
905 transaction abort!
895 rollback completed
906 rollback completed
896 abort: fail after finalization
907 abort: fail after finalization
897 [255]
908 [255]
898 *** runcommand log
909 *** runcommand log
899 0 bar (bar)
910 0 bar (bar)
900 *** runcommand verify -q
911 *** runcommand verify -q
General Comments 0
You need to be logged in to leave comments. Login now