##// END OF EJS Templates
cmdserver: forcibly use L channel to read password input (issue3161)...
Yuya Nishihara -
r21195:9336bc7d stable
parent child Browse files
Show More
@@ -1,245 +1,251 b''
1 1 # commandserver.py - communicate with Mercurial's API over a pipe
2 2 #
3 3 # Copyright Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import struct
10 10 import sys, os
11 11 import dispatch, encoding, util
12 12
13 13 logfile = None
14 14
15 15 def log(*args):
16 16 if not logfile:
17 17 return
18 18
19 19 for a in args:
20 20 logfile.write(str(a))
21 21
22 22 logfile.flush()
23 23
24 24 class channeledoutput(object):
25 25 """
26 26 Write data from in_ to out in the following format:
27 27
28 28 data length (unsigned int),
29 29 data
30 30 """
31 31 def __init__(self, in_, out, channel):
32 32 self.in_ = in_
33 33 self.out = out
34 34 self.channel = channel
35 35
36 36 def write(self, data):
37 37 if not data:
38 38 return
39 39 self.out.write(struct.pack('>cI', self.channel, len(data)))
40 40 self.out.write(data)
41 41 self.out.flush()
42 42
43 43 def __getattr__(self, attr):
44 44 if attr in ('isatty', 'fileno'):
45 45 raise AttributeError(attr)
46 46 return getattr(self.in_, attr)
47 47
48 48 class channeledinput(object):
49 49 """
50 50 Read data from in_.
51 51
52 52 Requests for input are written to out in the following format:
53 53 channel identifier - 'I' for plain input, 'L' line based (1 byte)
54 54 how many bytes to send at most (unsigned int),
55 55
56 56 The client replies with:
57 57 data length (unsigned int), 0 meaning EOF
58 58 data
59 59 """
60 60
61 61 maxchunksize = 4 * 1024
62 62
63 63 def __init__(self, in_, out, channel):
64 64 self.in_ = in_
65 65 self.out = out
66 66 self.channel = channel
67 67
68 68 def read(self, size=-1):
69 69 if size < 0:
70 70 # if we need to consume all the clients input, ask for 4k chunks
71 71 # so the pipe doesn't fill up risking a deadlock
72 72 size = self.maxchunksize
73 73 s = self._read(size, self.channel)
74 74 buf = s
75 75 while s:
76 76 s = self._read(size, self.channel)
77 77 buf += s
78 78
79 79 return buf
80 80 else:
81 81 return self._read(size, self.channel)
82 82
83 83 def _read(self, size, channel):
84 84 if not size:
85 85 return ''
86 86 assert size > 0
87 87
88 88 # tell the client we need at most size bytes
89 89 self.out.write(struct.pack('>cI', channel, size))
90 90 self.out.flush()
91 91
92 92 length = self.in_.read(4)
93 93 length = struct.unpack('>I', length)[0]
94 94 if not length:
95 95 return ''
96 96 else:
97 97 return self.in_.read(length)
98 98
99 99 def readline(self, size=-1):
100 100 if size < 0:
101 101 size = self.maxchunksize
102 102 s = self._read(size, 'L')
103 103 buf = s
104 104 # keep asking for more until there's either no more or
105 105 # we got a full line
106 106 while s and s[-1] != '\n':
107 107 s = self._read(size, 'L')
108 108 buf += s
109 109
110 110 return buf
111 111 else:
112 112 return self._read(size, 'L')
113 113
114 114 def __iter__(self):
115 115 return self
116 116
117 117 def next(self):
118 118 l = self.readline()
119 119 if not l:
120 120 raise StopIteration
121 121 return l
122 122
123 123 def __getattr__(self, attr):
124 124 if attr in ('isatty', 'fileno'):
125 125 raise AttributeError(attr)
126 126 return getattr(self.in_, attr)
127 127
128 128 class server(object):
129 129 """
130 130 Listens for commands on stdin, runs them and writes the output on a channel
131 131 based stream to stdout.
132 132 """
133 133 def __init__(self, ui, repo, mode):
134 134 self.cwd = os.getcwd()
135 135
136 136 logpath = ui.config("cmdserver", "log", None)
137 137 if logpath:
138 138 global logfile
139 139 if logpath == '-':
140 140 # write log on a special 'd' (debug) channel
141 141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
142 142 else:
143 143 logfile = open(logpath, 'a')
144 144
145 145 if repo:
146 146 # the ui here is really the repo ui so take its baseui so we don't
147 147 # end up with its local configuration
148 148 self.ui = repo.baseui
149 149 self.repo = repo
150 150 self.repoui = repo.ui
151 151 else:
152 152 self.ui = ui
153 153 self.repo = self.repoui = None
154 154
155 155 if mode == 'pipe':
156 156 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
157 157 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
158 158 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
159 159 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
160 160
161 161 self.client = sys.stdin
162 162 else:
163 163 raise util.Abort(_('unknown mode %s') % mode)
164 164
165 165 def _read(self, size):
166 166 if not size:
167 167 return ''
168 168
169 169 data = self.client.read(size)
170 170
171 171 # is the other end closed?
172 172 if not data:
173 173 raise EOFError
174 174
175 175 return data
176 176
177 177 def runcommand(self):
178 178 """ reads a list of \0 terminated arguments, executes
179 179 and writes the return code to the result channel """
180 180
181 181 length = struct.unpack('>I', self._read(4))[0]
182 182 if not length:
183 183 args = []
184 184 else:
185 185 args = self._read(length).split('\0')
186 186
187 187 # copy the uis so changes (e.g. --config or --verbose) don't
188 188 # persist between requests
189 189 copiedui = self.ui.copy()
190 uis = [copiedui]
190 191 if self.repo:
191 192 self.repo.baseui = copiedui
192 193 # clone ui without using ui.copy because this is protected
193 194 repoui = self.repoui.__class__(self.repoui)
194 195 repoui.copy = copiedui.copy # redo copy protection
196 uis.append(repoui)
195 197 self.repo.ui = self.repo.dirstate._ui = repoui
196 198 self.repo.invalidateall()
197 199
200 for ui in uis:
201 # any kind of interaction must use server channels
202 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
203
198 204 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
199 205 self.cout, self.cerr)
200 206
201 207 ret = (dispatch.dispatch(req) or 0) & 255 # might return None
202 208
203 209 # restore old cwd
204 210 if '--cwd' in args:
205 211 os.chdir(self.cwd)
206 212
207 213 self.cresult.write(struct.pack('>i', int(ret)))
208 214
209 215 def getencoding(self):
210 216 """ writes the current encoding to the result channel """
211 217 self.cresult.write(encoding.encoding)
212 218
213 219 def serveone(self):
214 220 cmd = self.client.readline()[:-1]
215 221 if cmd:
216 222 handler = self.capabilities.get(cmd)
217 223 if handler:
218 224 handler(self)
219 225 else:
220 226 # clients are expected to check what commands are supported by
221 227 # looking at the servers capabilities
222 228 raise util.Abort(_('unknown command %s') % cmd)
223 229
224 230 return cmd != ''
225 231
226 232 capabilities = {'runcommand' : runcommand,
227 233 'getencoding' : getencoding}
228 234
229 235 def serve(self):
230 236 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
231 237 hellomsg += '\n'
232 238 hellomsg += 'encoding: ' + encoding.encoding
233 239
234 240 # write the hello msg in -one- chunk
235 241 self.cout.write(hellomsg)
236 242
237 243 try:
238 244 while self.serveone():
239 245 pass
240 246 except EOFError:
241 247 # we'll get here if the client disconnected while we were reading
242 248 # its request
243 249 return 1
244 250
245 251 return 0
@@ -1,846 +1,851 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, scmutil, util, error, formatter
11 11 from node import hex
12 12
13 13 class ui(object):
14 14 def __init__(self, src=None):
15 15 # _buffers: used for temporary capture of output
16 16 self._buffers = []
17 17 # _bufferstates: Should the temporary capture includes stderr
18 18 self._bufferstates = []
19 19 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
20 20 self._reportuntrusted = True
21 21 self._ocfg = config.config() # overlay
22 22 self._tcfg = config.config() # trusted
23 23 self._ucfg = config.config() # untrusted
24 24 self._trustusers = set()
25 25 self._trustgroups = set()
26 26 self.callhooks = True
27 27
28 28 if src:
29 29 self.fout = src.fout
30 30 self.ferr = src.ferr
31 31 self.fin = src.fin
32 32
33 33 self._tcfg = src._tcfg.copy()
34 34 self._ucfg = src._ucfg.copy()
35 35 self._ocfg = src._ocfg.copy()
36 36 self._trustusers = src._trustusers.copy()
37 37 self._trustgroups = src._trustgroups.copy()
38 38 self.environ = src.environ
39 39 self.callhooks = src.callhooks
40 40 self.fixconfig()
41 41 else:
42 42 self.fout = sys.stdout
43 43 self.ferr = sys.stderr
44 44 self.fin = sys.stdin
45 45
46 46 # shared read-only environment
47 47 self.environ = os.environ
48 48 # we always trust global config files
49 49 for f in scmutil.rcpath():
50 50 self.readconfig(f, trust=True)
51 51
52 52 def copy(self):
53 53 return self.__class__(self)
54 54
55 55 def formatter(self, topic, opts):
56 56 return formatter.formatter(self, topic, opts)
57 57
58 58 def _trusted(self, fp, f):
59 59 st = util.fstat(fp)
60 60 if util.isowner(st):
61 61 return True
62 62
63 63 tusers, tgroups = self._trustusers, self._trustgroups
64 64 if '*' in tusers or '*' in tgroups:
65 65 return True
66 66
67 67 user = util.username(st.st_uid)
68 68 group = util.groupname(st.st_gid)
69 69 if user in tusers or group in tgroups or user == util.username():
70 70 return True
71 71
72 72 if self._reportuntrusted:
73 73 self.warn(_('not trusting file %s from untrusted '
74 74 'user %s, group %s\n') % (f, user, group))
75 75 return False
76 76
77 77 def readconfig(self, filename, root=None, trust=False,
78 78 sections=None, remap=None):
79 79 try:
80 80 fp = open(filename)
81 81 except IOError:
82 82 if not sections: # ignore unless we were looking for something
83 83 return
84 84 raise
85 85
86 86 cfg = config.config()
87 87 trusted = sections or trust or self._trusted(fp, filename)
88 88
89 89 try:
90 90 cfg.read(filename, fp, sections=sections, remap=remap)
91 91 fp.close()
92 92 except error.ConfigError, inst:
93 93 if trusted:
94 94 raise
95 95 self.warn(_("ignored: %s\n") % str(inst))
96 96
97 97 if self.plain():
98 98 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
99 99 'logtemplate', 'style',
100 100 'traceback', 'verbose'):
101 101 if k in cfg['ui']:
102 102 del cfg['ui'][k]
103 103 for k, v in cfg.items('defaults'):
104 104 del cfg['defaults'][k]
105 105 # Don't remove aliases from the configuration if in the exceptionlist
106 106 if self.plain('alias'):
107 107 for k, v in cfg.items('alias'):
108 108 del cfg['alias'][k]
109 109
110 110 if trusted:
111 111 self._tcfg.update(cfg)
112 112 self._tcfg.update(self._ocfg)
113 113 self._ucfg.update(cfg)
114 114 self._ucfg.update(self._ocfg)
115 115
116 116 if root is None:
117 117 root = os.path.expanduser('~')
118 118 self.fixconfig(root=root)
119 119
120 120 def fixconfig(self, root=None, section=None):
121 121 if section in (None, 'paths'):
122 122 # expand vars and ~
123 123 # translate paths relative to root (or home) into absolute paths
124 124 root = root or os.getcwd()
125 125 for c in self._tcfg, self._ucfg, self._ocfg:
126 126 for n, p in c.items('paths'):
127 127 if not p:
128 128 continue
129 129 if '%%' in p:
130 130 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
131 131 % (n, p, self.configsource('paths', n)))
132 132 p = p.replace('%%', '%')
133 133 p = util.expandpath(p)
134 134 if not util.hasscheme(p) and not os.path.isabs(p):
135 135 p = os.path.normpath(os.path.join(root, p))
136 136 c.set("paths", n, p)
137 137
138 138 if section in (None, 'ui'):
139 139 # update ui options
140 140 self.debugflag = self.configbool('ui', 'debug')
141 141 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
142 142 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
143 143 if self.verbose and self.quiet:
144 144 self.quiet = self.verbose = False
145 145 self._reportuntrusted = self.debugflag or self.configbool("ui",
146 146 "report_untrusted", True)
147 147 self.tracebackflag = self.configbool('ui', 'traceback', False)
148 148
149 149 if section in (None, 'trusted'):
150 150 # update trust information
151 151 self._trustusers.update(self.configlist('trusted', 'users'))
152 152 self._trustgroups.update(self.configlist('trusted', 'groups'))
153 153
154 154 def backupconfig(self, section, item):
155 155 return (self._ocfg.backup(section, item),
156 156 self._tcfg.backup(section, item),
157 157 self._ucfg.backup(section, item),)
158 158 def restoreconfig(self, data):
159 159 self._ocfg.restore(data[0])
160 160 self._tcfg.restore(data[1])
161 161 self._ucfg.restore(data[2])
162 162
163 163 def setconfig(self, section, name, value, source=''):
164 164 for cfg in (self._ocfg, self._tcfg, self._ucfg):
165 165 cfg.set(section, name, value, source)
166 166 self.fixconfig(section=section)
167 167
168 168 def _data(self, untrusted):
169 169 return untrusted and self._ucfg or self._tcfg
170 170
171 171 def configsource(self, section, name, untrusted=False):
172 172 return self._data(untrusted).source(section, name) or 'none'
173 173
174 174 def config(self, section, name, default=None, untrusted=False):
175 175 if isinstance(name, list):
176 176 alternates = name
177 177 else:
178 178 alternates = [name]
179 179
180 180 for n in alternates:
181 181 value = self._data(untrusted).get(section, n, None)
182 182 if value is not None:
183 183 name = n
184 184 break
185 185 else:
186 186 value = default
187 187
188 188 if self.debugflag and not untrusted and self._reportuntrusted:
189 189 for n in alternates:
190 190 uvalue = self._ucfg.get(section, n)
191 191 if uvalue is not None and uvalue != value:
192 192 self.debug("ignoring untrusted configuration option "
193 193 "%s.%s = %s\n" % (section, n, uvalue))
194 194 return value
195 195
196 196 def configpath(self, section, name, default=None, untrusted=False):
197 197 'get a path config item, expanded relative to repo root or config file'
198 198 v = self.config(section, name, default, untrusted)
199 199 if v is None:
200 200 return None
201 201 if not os.path.isabs(v) or "://" not in v:
202 202 src = self.configsource(section, name, untrusted)
203 203 if ':' in src:
204 204 base = os.path.dirname(src.rsplit(':')[0])
205 205 v = os.path.join(base, os.path.expanduser(v))
206 206 return v
207 207
208 208 def configbool(self, section, name, default=False, untrusted=False):
209 209 """parse a configuration element as a boolean
210 210
211 211 >>> u = ui(); s = 'foo'
212 212 >>> u.setconfig(s, 'true', 'yes')
213 213 >>> u.configbool(s, 'true')
214 214 True
215 215 >>> u.setconfig(s, 'false', 'no')
216 216 >>> u.configbool(s, 'false')
217 217 False
218 218 >>> u.configbool(s, 'unknown')
219 219 False
220 220 >>> u.configbool(s, 'unknown', True)
221 221 True
222 222 >>> u.setconfig(s, 'invalid', 'somevalue')
223 223 >>> u.configbool(s, 'invalid')
224 224 Traceback (most recent call last):
225 225 ...
226 226 ConfigError: foo.invalid is not a boolean ('somevalue')
227 227 """
228 228
229 229 v = self.config(section, name, None, untrusted)
230 230 if v is None:
231 231 return default
232 232 if isinstance(v, bool):
233 233 return v
234 234 b = util.parsebool(v)
235 235 if b is None:
236 236 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
237 237 % (section, name, v))
238 238 return b
239 239
240 240 def configint(self, section, name, default=None, untrusted=False):
241 241 """parse a configuration element as an integer
242 242
243 243 >>> u = ui(); s = 'foo'
244 244 >>> u.setconfig(s, 'int1', '42')
245 245 >>> u.configint(s, 'int1')
246 246 42
247 247 >>> u.setconfig(s, 'int2', '-42')
248 248 >>> u.configint(s, 'int2')
249 249 -42
250 250 >>> u.configint(s, 'unknown', 7)
251 251 7
252 252 >>> u.setconfig(s, 'invalid', 'somevalue')
253 253 >>> u.configint(s, 'invalid')
254 254 Traceback (most recent call last):
255 255 ...
256 256 ConfigError: foo.invalid is not an integer ('somevalue')
257 257 """
258 258
259 259 v = self.config(section, name, None, untrusted)
260 260 if v is None:
261 261 return default
262 262 try:
263 263 return int(v)
264 264 except ValueError:
265 265 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
266 266 % (section, name, v))
267 267
268 268 def configbytes(self, section, name, default=0, untrusted=False):
269 269 """parse a configuration element as a quantity in bytes
270 270
271 271 Units can be specified as b (bytes), k or kb (kilobytes), m or
272 272 mb (megabytes), g or gb (gigabytes).
273 273
274 274 >>> u = ui(); s = 'foo'
275 275 >>> u.setconfig(s, 'val1', '42')
276 276 >>> u.configbytes(s, 'val1')
277 277 42
278 278 >>> u.setconfig(s, 'val2', '42.5 kb')
279 279 >>> u.configbytes(s, 'val2')
280 280 43520
281 281 >>> u.configbytes(s, 'unknown', '7 MB')
282 282 7340032
283 283 >>> u.setconfig(s, 'invalid', 'somevalue')
284 284 >>> u.configbytes(s, 'invalid')
285 285 Traceback (most recent call last):
286 286 ...
287 287 ConfigError: foo.invalid is not a byte quantity ('somevalue')
288 288 """
289 289
290 290 value = self.config(section, name)
291 291 if value is None:
292 292 if not isinstance(default, str):
293 293 return default
294 294 value = default
295 295 try:
296 296 return util.sizetoint(value)
297 297 except error.ParseError:
298 298 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
299 299 % (section, name, value))
300 300
301 301 def configlist(self, section, name, default=None, untrusted=False):
302 302 """parse a configuration element as a list of comma/space separated
303 303 strings
304 304
305 305 >>> u = ui(); s = 'foo'
306 306 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
307 307 >>> u.configlist(s, 'list1')
308 308 ['this', 'is', 'a small', 'test']
309 309 """
310 310
311 311 def _parse_plain(parts, s, offset):
312 312 whitespace = False
313 313 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
314 314 whitespace = True
315 315 offset += 1
316 316 if offset >= len(s):
317 317 return None, parts, offset
318 318 if whitespace:
319 319 parts.append('')
320 320 if s[offset] == '"' and not parts[-1]:
321 321 return _parse_quote, parts, offset + 1
322 322 elif s[offset] == '"' and parts[-1][-1] == '\\':
323 323 parts[-1] = parts[-1][:-1] + s[offset]
324 324 return _parse_plain, parts, offset + 1
325 325 parts[-1] += s[offset]
326 326 return _parse_plain, parts, offset + 1
327 327
328 328 def _parse_quote(parts, s, offset):
329 329 if offset < len(s) and s[offset] == '"': # ""
330 330 parts.append('')
331 331 offset += 1
332 332 while offset < len(s) and (s[offset].isspace() or
333 333 s[offset] == ','):
334 334 offset += 1
335 335 return _parse_plain, parts, offset
336 336
337 337 while offset < len(s) and s[offset] != '"':
338 338 if (s[offset] == '\\' and offset + 1 < len(s)
339 339 and s[offset + 1] == '"'):
340 340 offset += 1
341 341 parts[-1] += '"'
342 342 else:
343 343 parts[-1] += s[offset]
344 344 offset += 1
345 345
346 346 if offset >= len(s):
347 347 real_parts = _configlist(parts[-1])
348 348 if not real_parts:
349 349 parts[-1] = '"'
350 350 else:
351 351 real_parts[0] = '"' + real_parts[0]
352 352 parts = parts[:-1]
353 353 parts.extend(real_parts)
354 354 return None, parts, offset
355 355
356 356 offset += 1
357 357 while offset < len(s) and s[offset] in [' ', ',']:
358 358 offset += 1
359 359
360 360 if offset < len(s):
361 361 if offset + 1 == len(s) and s[offset] == '"':
362 362 parts[-1] += '"'
363 363 offset += 1
364 364 else:
365 365 parts.append('')
366 366 else:
367 367 return None, parts, offset
368 368
369 369 return _parse_plain, parts, offset
370 370
371 371 def _configlist(s):
372 372 s = s.rstrip(' ,')
373 373 if not s:
374 374 return []
375 375 parser, parts, offset = _parse_plain, [''], 0
376 376 while parser:
377 377 parser, parts, offset = parser(parts, s, offset)
378 378 return parts
379 379
380 380 result = self.config(section, name, untrusted=untrusted)
381 381 if result is None:
382 382 result = default or []
383 383 if isinstance(result, basestring):
384 384 result = _configlist(result.lstrip(' ,\n'))
385 385 if result is None:
386 386 result = default or []
387 387 return result
388 388
389 389 def has_section(self, section, untrusted=False):
390 390 '''tell whether section exists in config.'''
391 391 return section in self._data(untrusted)
392 392
393 393 def configitems(self, section, untrusted=False):
394 394 items = self._data(untrusted).items(section)
395 395 if self.debugflag and not untrusted and self._reportuntrusted:
396 396 for k, v in self._ucfg.items(section):
397 397 if self._tcfg.get(section, k) != v:
398 398 self.debug("ignoring untrusted configuration option "
399 399 "%s.%s = %s\n" % (section, k, v))
400 400 return items
401 401
402 402 def walkconfig(self, untrusted=False):
403 403 cfg = self._data(untrusted)
404 404 for section in cfg.sections():
405 405 for name, value in self.configitems(section, untrusted):
406 406 yield section, name, value
407 407
408 408 def plain(self, feature=None):
409 409 '''is plain mode active?
410 410
411 411 Plain mode means that all configuration variables which affect
412 412 the behavior and output of Mercurial should be
413 413 ignored. Additionally, the output should be stable,
414 414 reproducible and suitable for use in scripts or applications.
415 415
416 416 The only way to trigger plain mode is by setting either the
417 417 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
418 418
419 419 The return value can either be
420 420 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
421 421 - True otherwise
422 422 '''
423 423 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
424 424 return False
425 425 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
426 426 if feature and exceptions:
427 427 return feature not in exceptions
428 428 return True
429 429
430 430 def username(self):
431 431 """Return default username to be used in commits.
432 432
433 433 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
434 434 and stop searching if one of these is set.
435 435 If not found and ui.askusername is True, ask the user, else use
436 436 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
437 437 """
438 438 user = os.environ.get("HGUSER")
439 439 if user is None:
440 440 user = self.config("ui", "username")
441 441 if user is not None:
442 442 user = os.path.expandvars(user)
443 443 if user is None:
444 444 user = os.environ.get("EMAIL")
445 445 if user is None and self.configbool("ui", "askusername"):
446 446 user = self.prompt(_("enter a commit username:"), default=None)
447 447 if user is None and not self.interactive():
448 448 try:
449 449 user = '%s@%s' % (util.getuser(), socket.getfqdn())
450 450 self.warn(_("no username found, using '%s' instead\n") % user)
451 451 except KeyError:
452 452 pass
453 453 if not user:
454 454 raise util.Abort(_('no username supplied'),
455 455 hint=_('use "hg config --edit" '
456 456 'to set your username'))
457 457 if "\n" in user:
458 458 raise util.Abort(_("username %s contains a newline\n") % repr(user))
459 459 return user
460 460
461 461 def shortuser(self, user):
462 462 """Return a short representation of a user name or email address."""
463 463 if not self.verbose:
464 464 user = util.shortuser(user)
465 465 return user
466 466
467 467 def expandpath(self, loc, default=None):
468 468 """Return repository location relative to cwd or from [paths]"""
469 469 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
470 470 return loc
471 471
472 472 path = self.config('paths', loc)
473 473 if not path and default is not None:
474 474 path = self.config('paths', default)
475 475 return path or loc
476 476
477 477 def pushbuffer(self, error=False):
478 478 """install a buffer to capture standar output of the ui object
479 479
480 480 If error is True, the error output will be captured too."""
481 481 self._buffers.append([])
482 482 self._bufferstates.append(error)
483 483
484 484 def popbuffer(self, labeled=False):
485 485 '''pop the last buffer and return the buffered output
486 486
487 487 If labeled is True, any labels associated with buffered
488 488 output will be handled. By default, this has no effect
489 489 on the output returned, but extensions and GUI tools may
490 490 handle this argument and returned styled output. If output
491 491 is being buffered so it can be captured and parsed or
492 492 processed, labeled should not be set to True.
493 493 '''
494 494 self._bufferstates.pop()
495 495 return "".join(self._buffers.pop())
496 496
497 497 def write(self, *args, **opts):
498 498 '''write args to output
499 499
500 500 By default, this method simply writes to the buffer or stdout,
501 501 but extensions or GUI tools may override this method,
502 502 write_err(), popbuffer(), and label() to style output from
503 503 various parts of hg.
504 504
505 505 An optional keyword argument, "label", can be passed in.
506 506 This should be a string containing label names separated by
507 507 space. Label names take the form of "topic.type". For example,
508 508 ui.debug() issues a label of "ui.debug".
509 509
510 510 When labeling output for a specific command, a label of
511 511 "cmdname.type" is recommended. For example, status issues
512 512 a label of "status.modified" for modified files.
513 513 '''
514 514 if self._buffers:
515 515 self._buffers[-1].extend([str(a) for a in args])
516 516 else:
517 517 for a in args:
518 518 self.fout.write(str(a))
519 519
520 520 def write_err(self, *args, **opts):
521 521 try:
522 522 if self._bufferstates and self._bufferstates[-1]:
523 523 return self.write(*args, **opts)
524 524 if not getattr(self.fout, 'closed', False):
525 525 self.fout.flush()
526 526 for a in args:
527 527 self.ferr.write(str(a))
528 528 # stderr may be buffered under win32 when redirected to files,
529 529 # including stdout.
530 530 if not getattr(self.ferr, 'closed', False):
531 531 self.ferr.flush()
532 532 except IOError, inst:
533 533 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
534 534 raise
535 535
536 536 def flush(self):
537 537 try: self.fout.flush()
538 538 except (IOError, ValueError): pass
539 539 try: self.ferr.flush()
540 540 except (IOError, ValueError): pass
541 541
542 542 def _isatty(self, fh):
543 543 if self.configbool('ui', 'nontty', False):
544 544 return False
545 545 return util.isatty(fh)
546 546
547 547 def interactive(self):
548 548 '''is interactive input allowed?
549 549
550 550 An interactive session is a session where input can be reasonably read
551 551 from `sys.stdin'. If this function returns false, any attempt to read
552 552 from stdin should fail with an error, unless a sensible default has been
553 553 specified.
554 554
555 555 Interactiveness is triggered by the value of the `ui.interactive'
556 556 configuration variable or - if it is unset - when `sys.stdin' points
557 557 to a terminal device.
558 558
559 559 This function refers to input only; for output, see `ui.formatted()'.
560 560 '''
561 561 i = self.configbool("ui", "interactive", None)
562 562 if i is None:
563 563 # some environments replace stdin without implementing isatty
564 564 # usually those are non-interactive
565 565 return self._isatty(self.fin)
566 566
567 567 return i
568 568
569 569 def termwidth(self):
570 570 '''how wide is the terminal in columns?
571 571 '''
572 572 if 'COLUMNS' in os.environ:
573 573 try:
574 574 return int(os.environ['COLUMNS'])
575 575 except ValueError:
576 576 pass
577 577 return util.termwidth()
578 578
579 579 def formatted(self):
580 580 '''should formatted output be used?
581 581
582 582 It is often desirable to format the output to suite the output medium.
583 583 Examples of this are truncating long lines or colorizing messages.
584 584 However, this is not often not desirable when piping output into other
585 585 utilities, e.g. `grep'.
586 586
587 587 Formatted output is triggered by the value of the `ui.formatted'
588 588 configuration variable or - if it is unset - when `sys.stdout' points
589 589 to a terminal device. Please note that `ui.formatted' should be
590 590 considered an implementation detail; it is not intended for use outside
591 591 Mercurial or its extensions.
592 592
593 593 This function refers to output only; for input, see `ui.interactive()'.
594 594 This function always returns false when in plain mode, see `ui.plain()'.
595 595 '''
596 596 if self.plain():
597 597 return False
598 598
599 599 i = self.configbool("ui", "formatted", None)
600 600 if i is None:
601 601 # some environments replace stdout without implementing isatty
602 602 # usually those are non-interactive
603 603 return self._isatty(self.fout)
604 604
605 605 return i
606 606
607 607 def _readline(self, prompt=''):
608 608 if self._isatty(self.fin):
609 609 try:
610 610 # magically add command line editing support, where
611 611 # available
612 612 import readline
613 613 # force demandimport to really load the module
614 614 readline.read_history_file
615 615 # windows sometimes raises something other than ImportError
616 616 except Exception:
617 617 pass
618 618
619 619 # call write() so output goes through subclassed implementation
620 620 # e.g. color extension on Windows
621 621 self.write(prompt)
622 622
623 623 # instead of trying to emulate raw_input, swap (self.fin,
624 624 # self.fout) with (sys.stdin, sys.stdout)
625 625 oldin = sys.stdin
626 626 oldout = sys.stdout
627 627 sys.stdin = self.fin
628 628 sys.stdout = self.fout
629 629 line = raw_input(' ')
630 630 sys.stdin = oldin
631 631 sys.stdout = oldout
632 632
633 633 # When stdin is in binary mode on Windows, it can cause
634 634 # raw_input() to emit an extra trailing carriage return
635 635 if os.linesep == '\r\n' and line and line[-1] == '\r':
636 636 line = line[:-1]
637 637 return line
638 638
639 639 def prompt(self, msg, default="y"):
640 640 """Prompt user with msg, read response.
641 641 If ui is not interactive, the default is returned.
642 642 """
643 643 if not self.interactive():
644 644 self.write(msg, ' ', default, "\n")
645 645 return default
646 646 try:
647 647 r = self._readline(self.label(msg, 'ui.prompt'))
648 648 if not r:
649 649 return default
650 650 return r
651 651 except EOFError:
652 652 raise util.Abort(_('response expected'))
653 653
654 654 @staticmethod
655 655 def extractchoices(prompt):
656 656 """Extract prompt message and list of choices from specified prompt.
657 657
658 658 This returns tuple "(message, choices)", and "choices" is the
659 659 list of tuple "(response character, text without &)".
660 660 """
661 661 parts = prompt.split('$$')
662 662 msg = parts[0].rstrip(' ')
663 663 choices = [p.strip(' ') for p in parts[1:]]
664 664 return (msg,
665 665 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
666 666 for s in choices])
667 667
668 668 def promptchoice(self, prompt, default=0):
669 669 """Prompt user with a message, read response, and ensure it matches
670 670 one of the provided choices. The prompt is formatted as follows:
671 671
672 672 "would you like fries with that (Yn)? $$ &Yes $$ &No"
673 673
674 674 The index of the choice is returned. Responses are case
675 675 insensitive. If ui is not interactive, the default is
676 676 returned.
677 677 """
678 678
679 679 msg, choices = self.extractchoices(prompt)
680 680 resps = [r for r, t in choices]
681 681 while True:
682 682 r = self.prompt(msg, resps[default])
683 683 if r.lower() in resps:
684 684 return resps.index(r.lower())
685 685 self.write(_("unrecognized response\n"))
686 686
687 687 def getpass(self, prompt=None, default=None):
688 688 if not self.interactive():
689 689 return default
690 690 try:
691 691 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
692 return getpass.getpass('')
692 # disable getpass() only if explicitly specified. it's still valid
693 # to interact with tty even if fin is not a tty.
694 if self.configbool('ui', 'nontty'):
695 return self.fin.readline().rstrip('\n')
696 else:
697 return getpass.getpass('')
693 698 except EOFError:
694 699 raise util.Abort(_('response expected'))
695 700 def status(self, *msg, **opts):
696 701 '''write status message to output (if ui.quiet is False)
697 702
698 703 This adds an output label of "ui.status".
699 704 '''
700 705 if not self.quiet:
701 706 opts['label'] = opts.get('label', '') + ' ui.status'
702 707 self.write(*msg, **opts)
703 708 def warn(self, *msg, **opts):
704 709 '''write warning message to output (stderr)
705 710
706 711 This adds an output label of "ui.warning".
707 712 '''
708 713 opts['label'] = opts.get('label', '') + ' ui.warning'
709 714 self.write_err(*msg, **opts)
710 715 def note(self, *msg, **opts):
711 716 '''write note to output (if ui.verbose is True)
712 717
713 718 This adds an output label of "ui.note".
714 719 '''
715 720 if self.verbose:
716 721 opts['label'] = opts.get('label', '') + ' ui.note'
717 722 self.write(*msg, **opts)
718 723 def debug(self, *msg, **opts):
719 724 '''write debug message to output (if ui.debugflag is True)
720 725
721 726 This adds an output label of "ui.debug".
722 727 '''
723 728 if self.debugflag:
724 729 opts['label'] = opts.get('label', '') + ' ui.debug'
725 730 self.write(*msg, **opts)
726 731 def edit(self, text, user, extra={}):
727 732 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
728 733 text=True)
729 734 try:
730 735 f = os.fdopen(fd, "w")
731 736 f.write(text)
732 737 f.close()
733 738
734 739 environ = {'HGUSER': user}
735 740 if 'transplant_source' in extra:
736 741 environ.update({'HGREVISION': hex(extra['transplant_source'])})
737 742 for label in ('source', 'rebase_source'):
738 743 if label in extra:
739 744 environ.update({'HGREVISION': extra[label]})
740 745 break
741 746
742 747 editor = self.geteditor()
743 748
744 749 util.system("%s \"%s\"" % (editor, name),
745 750 environ=environ,
746 751 onerr=util.Abort, errprefix=_("edit failed"),
747 752 out=self.fout)
748 753
749 754 f = open(name)
750 755 t = f.read()
751 756 f.close()
752 757 finally:
753 758 os.unlink(name)
754 759
755 760 return t
756 761
757 762 def traceback(self, exc=None, force=False):
758 763 '''print exception traceback if traceback printing enabled or forced.
759 764 only to call in exception handler. returns true if traceback
760 765 printed.'''
761 766 if self.tracebackflag or force:
762 767 if exc is None:
763 768 exc = sys.exc_info()
764 769 cause = getattr(exc[1], 'cause', None)
765 770
766 771 if cause is not None:
767 772 causetb = traceback.format_tb(cause[2])
768 773 exctb = traceback.format_tb(exc[2])
769 774 exconly = traceback.format_exception_only(cause[0], cause[1])
770 775
771 776 # exclude frame where 'exc' was chained and rethrown from exctb
772 777 self.write_err('Traceback (most recent call last):\n',
773 778 ''.join(exctb[:-1]),
774 779 ''.join(causetb),
775 780 ''.join(exconly))
776 781 else:
777 782 traceback.print_exception(exc[0], exc[1], exc[2],
778 783 file=self.ferr)
779 784 return self.tracebackflag or force
780 785
781 786 def geteditor(self):
782 787 '''return editor to use'''
783 788 if sys.platform == 'plan9':
784 789 # vi is the MIPS instruction simulator on Plan 9. We
785 790 # instead default to E to plumb commit messages to
786 791 # avoid confusion.
787 792 editor = 'E'
788 793 else:
789 794 editor = 'vi'
790 795 return (os.environ.get("HGEDITOR") or
791 796 self.config("ui", "editor") or
792 797 os.environ.get("VISUAL") or
793 798 os.environ.get("EDITOR", editor))
794 799
795 800 def progress(self, topic, pos, item="", unit="", total=None):
796 801 '''show a progress message
797 802
798 803 With stock hg, this is simply a debug message that is hidden
799 804 by default, but with extensions or GUI tools it may be
800 805 visible. 'topic' is the current operation, 'item' is a
801 806 non-numeric marker of the current position (i.e. the currently
802 807 in-process file), 'pos' is the current numeric position (i.e.
803 808 revision, bytes, etc.), unit is a corresponding unit label,
804 809 and total is the highest expected pos.
805 810
806 811 Multiple nested topics may be active at a time.
807 812
808 813 All topics should be marked closed by setting pos to None at
809 814 termination.
810 815 '''
811 816
812 817 if pos is None or not self.debugflag:
813 818 return
814 819
815 820 if unit:
816 821 unit = ' ' + unit
817 822 if item:
818 823 item = ' ' + item
819 824
820 825 if total:
821 826 pct = 100.0 * pos / total
822 827 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
823 828 % (topic, item, pos, total, unit, pct))
824 829 else:
825 830 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
826 831
827 832 def log(self, service, *msg, **opts):
828 833 '''hook for logging facility extensions
829 834
830 835 service should be a readily-identifiable subsystem, which will
831 836 allow filtering.
832 837 message should be a newline-terminated string to log.
833 838 '''
834 839 pass
835 840
836 841 def label(self, msg, label):
837 842 '''style msg based on supplied label
838 843
839 844 Like ui.write(), this just returns msg unchanged, but extensions
840 845 and GUI tools can override it to allow styling output without
841 846 writing it.
842 847
843 848 ui.write(s, 'label') is equivalent to
844 849 ui.write(ui.label(s, 'label')).
845 850 '''
846 851 return msg
@@ -1,340 +1,358 b''
1 1 import sys, os, struct, subprocess, cStringIO, re, shutil
2 2
3 3 def connect(path=None):
4 4 cmdline = ['hg', 'serve', '--cmdserver', 'pipe']
5 5 if path:
6 6 cmdline += ['-R', path]
7 7
8 8 server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
9 9 stdout=subprocess.PIPE)
10 10
11 11 return server
12 12
13 13 def writeblock(server, data):
14 14 server.stdin.write(struct.pack('>I', len(data)))
15 15 server.stdin.write(data)
16 16 server.stdin.flush()
17 17
18 18 def readchannel(server):
19 19 data = server.stdout.read(5)
20 20 if not data:
21 21 raise EOFError
22 22 channel, length = struct.unpack('>cI', data)
23 23 if channel in 'IL':
24 24 return channel, length
25 25 else:
26 26 return channel, server.stdout.read(length)
27 27
28 28 def sep(text):
29 29 return text.replace('\\', '/')
30 30
31 31 def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None,
32 32 outfilter=lambda x: x):
33 33 print ' runcommand', ' '.join(args)
34 34 sys.stdout.flush()
35 35 server.stdin.write('runcommand\n')
36 36 writeblock(server, '\0'.join(args))
37 37
38 38 if not input:
39 39 input = cStringIO.StringIO()
40 40
41 41 while True:
42 42 ch, data = readchannel(server)
43 43 if ch == 'o':
44 44 output.write(outfilter(data))
45 45 output.flush()
46 46 elif ch == 'e':
47 47 error.write(data)
48 48 error.flush()
49 49 elif ch == 'I':
50 50 writeblock(server, input.read(data))
51 51 elif ch == 'L':
52 52 writeblock(server, input.readline(data))
53 53 elif ch == 'r':
54 54 ret, = struct.unpack('>i', data)
55 55 if ret != 0:
56 56 print ' [%d]' % ret
57 57 return ret
58 58 else:
59 59 print "unexpected channel %c: %r" % (ch, data)
60 60 if ch.isupper():
61 61 return
62 62
63 63 def check(func, repopath=None):
64 64 print
65 65 print 'testing %s:' % func.__name__
66 66 print
67 67 sys.stdout.flush()
68 68 server = connect(repopath)
69 69 try:
70 70 return func(server)
71 71 finally:
72 72 server.stdin.close()
73 73 server.wait()
74 74
75 75 def unknowncommand(server):
76 76 server.stdin.write('unknowncommand\n')
77 77
78 78 def hellomessage(server):
79 79 ch, data = readchannel(server)
80 80 # escaping python tests output not supported
81 81 print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***',
82 82 data))
83 83
84 84 # run an arbitrary command to make sure the next thing the server sends
85 85 # isn't part of the hello message
86 86 runcommand(server, ['id'])
87 87
88 88 def checkruncommand(server):
89 89 # hello block
90 90 readchannel(server)
91 91
92 92 # no args
93 93 runcommand(server, [])
94 94
95 95 # global options
96 96 runcommand(server, ['id', '--quiet'])
97 97
98 98 # make sure global options don't stick through requests
99 99 runcommand(server, ['id'])
100 100
101 101 # --config
102 102 runcommand(server, ['id', '--config', 'ui.quiet=True'])
103 103
104 104 # make sure --config doesn't stick
105 105 runcommand(server, ['id'])
106 106
107 107 # negative return code should be masked
108 108 runcommand(server, ['id', '-runknown'])
109 109
110 110 def inputeof(server):
111 111 readchannel(server)
112 112 server.stdin.write('runcommand\n')
113 113 # close stdin while server is waiting for input
114 114 server.stdin.close()
115 115
116 116 # server exits with 1 if the pipe closed while reading the command
117 117 print 'server exit code =', server.wait()
118 118
119 119 def serverinput(server):
120 120 readchannel(server)
121 121
122 122 patch = """
123 123 # HG changeset patch
124 124 # User test
125 125 # Date 0 0
126 126 # Node ID c103a3dec114d882c98382d684d8af798d09d857
127 127 # Parent 0000000000000000000000000000000000000000
128 128 1
129 129
130 130 diff -r 000000000000 -r c103a3dec114 a
131 131 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
132 132 +++ b/a Thu Jan 01 00:00:00 1970 +0000
133 133 @@ -0,0 +1,1 @@
134 134 +1
135 135 """
136 136
137 137 runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
138 138 runcommand(server, ['log'])
139 139
140 140 def cwd(server):
141 141 """ check that --cwd doesn't persist between requests """
142 142 readchannel(server)
143 143 os.mkdir('foo')
144 144 f = open('foo/bar', 'wb')
145 145 f.write('a')
146 146 f.close()
147 147 runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
148 148 runcommand(server, ['st', 'foo/bar'])
149 149 os.remove('foo/bar')
150 150
151 151 def localhgrc(server):
152 152 """ check that local configs for the cached repo aren't inherited when -R
153 153 is used """
154 154 readchannel(server)
155 155
156 156 # the cached repo local hgrc contains ui.foo=bar, so showconfig should
157 157 # show it
158 158 runcommand(server, ['showconfig'])
159 159
160 160 # but not for this repo
161 161 runcommand(server, ['init', 'foo'])
162 162 runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
163 163 shutil.rmtree('foo')
164 164
165 165 def hook(**args):
166 166 print 'hook talking'
167 167 print 'now try to read something: %r' % sys.stdin.read()
168 168
169 169 def hookoutput(server):
170 170 readchannel(server)
171 171 runcommand(server, ['--config',
172 172 'hooks.pre-identify=python:test-commandserver.hook',
173 173 'id'],
174 174 input=cStringIO.StringIO('some input'))
175 175
176 176 def outsidechanges(server):
177 177 readchannel(server)
178 178 f = open('a', 'ab')
179 179 f.write('a\n')
180 180 f.close()
181 181 runcommand(server, ['status'])
182 182 os.system('hg ci -Am2')
183 183 runcommand(server, ['tip'])
184 184 runcommand(server, ['status'])
185 185
186 186 def bookmarks(server):
187 187 readchannel(server)
188 188 runcommand(server, ['bookmarks'])
189 189
190 190 # changes .hg/bookmarks
191 191 os.system('hg bookmark -i bm1')
192 192 os.system('hg bookmark -i bm2')
193 193 runcommand(server, ['bookmarks'])
194 194
195 195 # changes .hg/bookmarks.current
196 196 os.system('hg upd bm1 -q')
197 197 runcommand(server, ['bookmarks'])
198 198
199 199 runcommand(server, ['bookmarks', 'bm3'])
200 200 f = open('a', 'ab')
201 201 f.write('a\n')
202 202 f.close()
203 203 runcommand(server, ['commit', '-Amm'])
204 204 runcommand(server, ['bookmarks'])
205 205
206 206 def tagscache(server):
207 207 readchannel(server)
208 208 runcommand(server, ['id', '-t', '-r', '0'])
209 209 os.system('hg tag -r 0 foo')
210 210 runcommand(server, ['id', '-t', '-r', '0'])
211 211
212 212 def setphase(server):
213 213 readchannel(server)
214 214 runcommand(server, ['phase', '-r', '.'])
215 215 os.system('hg phase -r . -p')
216 216 runcommand(server, ['phase', '-r', '.'])
217 217
218 218 def rollback(server):
219 219 readchannel(server)
220 220 runcommand(server, ['phase', '-r', '.', '-p'])
221 221 f = open('a', 'ab')
222 222 f.write('a\n')
223 223 f.close()
224 224 runcommand(server, ['commit', '-Am.'])
225 225 runcommand(server, ['rollback'])
226 226 runcommand(server, ['phase', '-r', '.'])
227 227
228 228 def branch(server):
229 229 readchannel(server)
230 230 runcommand(server, ['branch'])
231 231 os.system('hg branch foo')
232 232 runcommand(server, ['branch'])
233 233 os.system('hg branch default')
234 234
235 235 def hgignore(server):
236 236 readchannel(server)
237 237 f = open('.hgignore', 'ab')
238 238 f.write('')
239 239 f.close()
240 240 runcommand(server, ['commit', '-Am.'])
241 241 f = open('ignored-file', 'ab')
242 242 f.write('')
243 243 f.close()
244 244 f = open('.hgignore', 'ab')
245 245 f.write('ignored-file')
246 246 f.close()
247 247 runcommand(server, ['status', '-i', '-u'])
248 248
249 249 def phasecacheafterstrip(server):
250 250 readchannel(server)
251 251
252 252 # create new head, 5:731265503d86
253 253 runcommand(server, ['update', '-C', '0'])
254 254 f = open('a', 'ab')
255 255 f.write('a\n')
256 256 f.close()
257 257 runcommand(server, ['commit', '-Am.', 'a'])
258 258 runcommand(server, ['log', '-Gq'])
259 259
260 260 # make it public; draft marker moves to 4:7966c8e3734d
261 261 runcommand(server, ['phase', '-p', '.'])
262 262 # load _phasecache.phaseroots
263 263 runcommand(server, ['phase', '.'], outfilter=sep)
264 264
265 265 # strip 1::4 outside server
266 266 os.system('hg -q --config extensions.mq= strip 1')
267 267
268 268 # shouldn't raise "7966c8e3734d: no node!"
269 269 runcommand(server, ['branches'])
270 270
271 271 def obsolete(server):
272 272 readchannel(server)
273 273
274 274 runcommand(server, ['up', 'null'])
275 275 runcommand(server, ['phase', '-df', 'tip'])
276 276 cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
277 277 if os.name == 'nt':
278 278 cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
279 279 os.system(cmd)
280 280 runcommand(server, ['log', '--hidden'])
281 281 runcommand(server, ['log'])
282 282
283 283 def mqoutsidechanges(server):
284 284 readchannel(server)
285 285
286 286 # load repo.mq
287 287 runcommand(server, ['qapplied'])
288 288 os.system('hg qnew 0.diff')
289 289 # repo.mq should be invalidated
290 290 runcommand(server, ['qapplied'])
291 291
292 292 runcommand(server, ['qpop', '--all'])
293 293 os.system('hg qqueue --create foo')
294 294 # repo.mq should be recreated to point to new queue
295 295 runcommand(server, ['qqueue', '--active'])
296 296
297 def getpass(server):
298 readchannel(server)
299 runcommand(server, ['debuggetpass', '--config', 'ui.interactive=True'],
300 input=cStringIO.StringIO('1234\n'))
301
297 302 def startwithoutrepo(server):
298 303 readchannel(server)
299 304 runcommand(server, ['init', 'repo2'])
300 305 runcommand(server, ['id', '-R', 'repo2'])
301 306
302 307 if __name__ == '__main__':
303 308 os.system('hg init repo')
304 309 os.chdir('repo')
305 310
306 311 check(hellomessage)
307 312 check(unknowncommand)
308 313 check(checkruncommand)
309 314 check(inputeof)
310 315 check(serverinput)
311 316 check(cwd)
312 317
313 318 hgrc = open('.hg/hgrc', 'a')
314 319 hgrc.write('[ui]\nfoo=bar\n')
315 320 hgrc.close()
316 321 check(localhgrc)
317 322 check(hookoutput)
318 323 check(outsidechanges)
319 324 check(bookmarks)
320 325 check(tagscache)
321 326 check(setphase)
322 327 check(rollback)
323 328 check(branch)
324 329 check(hgignore)
325 330 check(phasecacheafterstrip)
326 331 obs = open('obs.py', 'w')
327 332 obs.write('import mercurial.obsolete\nmercurial.obsolete._enabled = True\n')
328 333 obs.close()
329 334 hgrc = open('.hg/hgrc', 'a')
330 335 hgrc.write('[extensions]\nobs=obs.py\n')
331 336 hgrc.close()
332 337 check(obsolete)
333 338 hgrc = open('.hg/hgrc', 'a')
334 339 hgrc.write('[extensions]\nmq=\n')
335 340 hgrc.close()
336 341 check(mqoutsidechanges)
342 dbg = open('dbgui.py', 'w')
343 dbg.write('from mercurial import cmdutil, commands\n'
344 'commands.norepo += " debuggetpass"\n'
345 'cmdtable = {}\n'
346 'command = cmdutil.command(cmdtable)\n'
347 '@command("debuggetpass")\n'
348 'def debuggetpass(ui):\n'
349 ' ui.write("%s\\n" % ui.getpass())\n')
350 dbg.close()
351 hgrc = open('.hg/hgrc', 'a')
352 hgrc.write('[extensions]\ndbgui=dbgui.py\n')
353 hgrc.close()
354 check(getpass)
337 355
338 356 os.chdir('..')
339 357 check(hellomessage)
340 358 check(startwithoutrepo)
@@ -1,252 +1,259 b''
1 1
2 2 testing hellomessage:
3 3
4 4 o, 'capabilities: getencoding runcommand\nencoding: ***'
5 5 runcommand id
6 6 000000000000 tip
7 7
8 8 testing unknowncommand:
9 9
10 10 abort: unknown command unknowncommand
11 11
12 12 testing checkruncommand:
13 13
14 14 runcommand
15 15 Mercurial Distributed SCM
16 16
17 17 basic commands:
18 18
19 19 add add the specified files on the next commit
20 20 annotate show changeset information by line for each file
21 21 clone make a copy of an existing repository
22 22 commit commit the specified files or all outstanding changes
23 23 diff diff repository (or selected files)
24 24 export dump the header and diffs for one or more changesets
25 25 forget forget the specified files on the next commit
26 26 init create a new repository in the given directory
27 27 log show revision history of entire repository or files
28 28 merge merge working directory with another revision
29 29 pull pull changes from the specified source
30 30 push push changes to the specified destination
31 31 remove remove the specified files on the next commit
32 32 serve start stand-alone webserver
33 33 status show changed files in the working directory
34 34 summary summarize working directory state
35 35 update update working directory (or switch revisions)
36 36
37 37 use "hg help" for the full list of commands or "hg -v" for details
38 38 runcommand id --quiet
39 39 000000000000
40 40 runcommand id
41 41 000000000000 tip
42 42 runcommand id --config ui.quiet=True
43 43 000000000000
44 44 runcommand id
45 45 000000000000 tip
46 46 runcommand id -runknown
47 47 abort: unknown revision 'unknown'!
48 48 [255]
49 49
50 50 testing inputeof:
51 51
52 52 server exit code = 1
53 53
54 54 testing serverinput:
55 55
56 56 runcommand import -
57 57 applying patch from stdin
58 58 runcommand log
59 59 changeset: 0:eff892de26ec
60 60 tag: tip
61 61 user: test
62 62 date: Thu Jan 01 00:00:00 1970 +0000
63 63 summary: 1
64 64
65 65
66 66 testing cwd:
67 67
68 68 runcommand --cwd foo st bar
69 69 ? bar
70 70 runcommand st foo/bar
71 71 ? foo/bar
72 72
73 73 testing localhgrc:
74 74
75 75 runcommand showconfig
76 76 bundle.mainreporoot=$TESTTMP/repo
77 77 defaults.backout=-d "0 0"
78 78 defaults.commit=-d "0 0"
79 79 defaults.shelve=--date "0 0"
80 80 defaults.tag=-d "0 0"
81 81 ui.slash=True
82 82 ui.interactive=False
83 83 ui.foo=bar
84 ui.nontty=true
84 85 runcommand init foo
85 86 runcommand -R foo showconfig ui defaults
86 87 defaults.backout=-d "0 0"
87 88 defaults.commit=-d "0 0"
88 89 defaults.shelve=--date "0 0"
89 90 defaults.tag=-d "0 0"
90 91 ui.slash=True
91 92 ui.interactive=False
93 ui.nontty=true
92 94
93 95 testing hookoutput:
94 96
95 97 runcommand --config hooks.pre-identify=python:test-commandserver.hook id
96 98 hook talking
97 99 now try to read something: 'some input'
98 100 eff892de26ec tip
99 101
100 102 testing outsidechanges:
101 103
102 104 runcommand status
103 105 M a
104 106 runcommand tip
105 107 changeset: 1:d3a0a68be6de
106 108 tag: tip
107 109 user: test
108 110 date: Thu Jan 01 00:00:00 1970 +0000
109 111 summary: 2
110 112
111 113 runcommand status
112 114
113 115 testing bookmarks:
114 116
115 117 runcommand bookmarks
116 118 no bookmarks set
117 119 runcommand bookmarks
118 120 bm1 1:d3a0a68be6de
119 121 bm2 1:d3a0a68be6de
120 122 runcommand bookmarks
121 123 * bm1 1:d3a0a68be6de
122 124 bm2 1:d3a0a68be6de
123 125 runcommand bookmarks bm3
124 126 runcommand commit -Amm
125 127 runcommand bookmarks
126 128 bm1 1:d3a0a68be6de
127 129 bm2 1:d3a0a68be6de
128 130 * bm3 2:aef17e88f5f0
129 131
130 132 testing tagscache:
131 133
132 134 runcommand id -t -r 0
133 135
134 136 runcommand id -t -r 0
135 137 foo
136 138
137 139 testing setphase:
138 140
139 141 runcommand phase -r .
140 142 3: draft
141 143 runcommand phase -r .
142 144 3: public
143 145
144 146 testing rollback:
145 147
146 148 runcommand phase -r . -p
147 149 no phases changed
148 150 [1]
149 151 runcommand commit -Am.
150 152 runcommand rollback
151 153 repository tip rolled back to revision 3 (undo commit)
152 154 working directory now based on revision 3
153 155 runcommand phase -r .
154 156 3: public
155 157
156 158 testing branch:
157 159
158 160 runcommand branch
159 161 default
160 162 marked working directory as branch foo
161 163 (branches are permanent and global, did you want a bookmark?)
162 164 runcommand branch
163 165 foo
164 166 marked working directory as branch default
165 167 (branches are permanent and global, did you want a bookmark?)
166 168
167 169 testing hgignore:
168 170
169 171 runcommand commit -Am.
170 172 adding .hgignore
171 173 runcommand status -i -u
172 174 I ignored-file
173 175
174 176 testing phasecacheafterstrip:
175 177
176 178 runcommand update -C 0
177 179 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
178 180 runcommand commit -Am. a
179 181 created new head
180 182 runcommand log -Gq
181 183 @ 5:731265503d86
182 184 |
183 185 | o 4:7966c8e3734d
184 186 | |
185 187 | o 3:b9b85890c400
186 188 | |
187 189 | o 2:aef17e88f5f0
188 190 | |
189 191 | o 1:d3a0a68be6de
190 192 |/
191 193 o 0:eff892de26ec
192 194
193 195 runcommand phase -p .
194 196 runcommand phase .
195 197 5: public
196 198 runcommand branches
197 199 default 1:731265503d86
198 200
199 201 testing obsolete:
200 202
201 203 runcommand up null
202 204 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
203 205 runcommand phase -df tip
204 206 runcommand log --hidden
205 207 changeset: 1:731265503d86
206 208 tag: tip
207 209 user: test
208 210 date: Thu Jan 01 00:00:00 1970 +0000
209 211 summary: .
210 212
211 213 changeset: 0:eff892de26ec
212 214 bookmark: bm1
213 215 bookmark: bm2
214 216 bookmark: bm3
215 217 user: test
216 218 date: Thu Jan 01 00:00:00 1970 +0000
217 219 summary: 1
218 220
219 221 runcommand log
220 222 changeset: 0:eff892de26ec
221 223 bookmark: bm1
222 224 bookmark: bm2
223 225 bookmark: bm3
224 226 tag: tip
225 227 user: test
226 228 date: Thu Jan 01 00:00:00 1970 +0000
227 229 summary: 1
228 230
229 231
230 232 testing mqoutsidechanges:
231 233
232 234 runcommand qapplied
233 235 runcommand qapplied
234 236 0.diff
235 237 runcommand qpop --all
236 238 popping 0.diff
237 239 patch queue now empty
238 240 runcommand qqueue --active
239 241 foo
240 242
243 testing getpass:
244
245 runcommand debuggetpass --config ui.interactive=True
246 password: 1234
247
241 248 testing hellomessage:
242 249
243 250 o, 'capabilities: getencoding runcommand\nencoding: ***'
244 251 runcommand id
245 252 abort: there is no Mercurial repository here (.hg not found)
246 253 [255]
247 254
248 255 testing startwithoutrepo:
249 256
250 257 runcommand init repo2
251 258 runcommand id -R repo2
252 259 000000000000 tip
General Comments 0
You need to be logged in to leave comments. Login now