##// END OF EJS Templates
ssl: set explicit symbol "!" to web.cacerts to disable SSL verification (BC)...
Yuya Nishihara -
r24290:b76d8c64 default
parent child Browse files
Show More
@@ -1,976 +1,976
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching 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 i18n import _
8 from i18n import _
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 import difflib
10 import difflib
11 import util, commands, hg, fancyopts, extensions, hook, error
11 import util, commands, hg, fancyopts, extensions, hook, error
12 import cmdutil, encoding
12 import cmdutil, encoding
13 import ui as uimod
13 import ui as uimod
14
14
15 class request(object):
15 class request(object):
16 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
16 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
17 ferr=None):
17 ferr=None):
18 self.args = args
18 self.args = args
19 self.ui = ui
19 self.ui = ui
20 self.repo = repo
20 self.repo = repo
21
21
22 # input/output/error streams
22 # input/output/error streams
23 self.fin = fin
23 self.fin = fin
24 self.fout = fout
24 self.fout = fout
25 self.ferr = ferr
25 self.ferr = ferr
26
26
27 def run():
27 def run():
28 "run the command in sys.argv"
28 "run the command in sys.argv"
29 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
29 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
30
30
31 def _getsimilar(symbols, value):
31 def _getsimilar(symbols, value):
32 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
32 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
33 # The cutoff for similarity here is pretty arbitrary. It should
33 # The cutoff for similarity here is pretty arbitrary. It should
34 # probably be investigated and tweaked.
34 # probably be investigated and tweaked.
35 return [s for s in symbols if sim(s) > 0.6]
35 return [s for s in symbols if sim(s) > 0.6]
36
36
37 def _formatparse(write, inst):
37 def _formatparse(write, inst):
38 similar = []
38 similar = []
39 if isinstance(inst, error.UnknownIdentifier):
39 if isinstance(inst, error.UnknownIdentifier):
40 # make sure to check fileset first, as revset can invoke fileset
40 # make sure to check fileset first, as revset can invoke fileset
41 similar = _getsimilar(inst.symbols, inst.function)
41 similar = _getsimilar(inst.symbols, inst.function)
42 if len(inst.args) > 1:
42 if len(inst.args) > 1:
43 write(_("hg: parse error at %s: %s\n") %
43 write(_("hg: parse error at %s: %s\n") %
44 (inst.args[1], inst.args[0]))
44 (inst.args[1], inst.args[0]))
45 if (inst.args[0][0] == ' '):
45 if (inst.args[0][0] == ' '):
46 write(_("unexpected leading whitespace\n"))
46 write(_("unexpected leading whitespace\n"))
47 else:
47 else:
48 write(_("hg: parse error: %s\n") % inst.args[0])
48 write(_("hg: parse error: %s\n") % inst.args[0])
49 if similar:
49 if similar:
50 if len(similar) == 1:
50 if len(similar) == 1:
51 write(_("(did you mean %r?)\n") % similar[0])
51 write(_("(did you mean %r?)\n") % similar[0])
52 else:
52 else:
53 ss = ", ".join(sorted(similar))
53 ss = ", ".join(sorted(similar))
54 write(_("(did you mean one of %s?)\n") % ss)
54 write(_("(did you mean one of %s?)\n") % ss)
55
55
56 def dispatch(req):
56 def dispatch(req):
57 "run the command specified in req.args"
57 "run the command specified in req.args"
58 if req.ferr:
58 if req.ferr:
59 ferr = req.ferr
59 ferr = req.ferr
60 elif req.ui:
60 elif req.ui:
61 ferr = req.ui.ferr
61 ferr = req.ui.ferr
62 else:
62 else:
63 ferr = sys.stderr
63 ferr = sys.stderr
64
64
65 try:
65 try:
66 if not req.ui:
66 if not req.ui:
67 req.ui = uimod.ui()
67 req.ui = uimod.ui()
68 if '--traceback' in req.args:
68 if '--traceback' in req.args:
69 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
69 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
70
70
71 # set ui streams from the request
71 # set ui streams from the request
72 if req.fin:
72 if req.fin:
73 req.ui.fin = req.fin
73 req.ui.fin = req.fin
74 if req.fout:
74 if req.fout:
75 req.ui.fout = req.fout
75 req.ui.fout = req.fout
76 if req.ferr:
76 if req.ferr:
77 req.ui.ferr = req.ferr
77 req.ui.ferr = req.ferr
78 except util.Abort, inst:
78 except util.Abort, inst:
79 ferr.write(_("abort: %s\n") % inst)
79 ferr.write(_("abort: %s\n") % inst)
80 if inst.hint:
80 if inst.hint:
81 ferr.write(_("(%s)\n") % inst.hint)
81 ferr.write(_("(%s)\n") % inst.hint)
82 return -1
82 return -1
83 except error.ParseError, inst:
83 except error.ParseError, inst:
84 _formatparse(ferr.write, inst)
84 _formatparse(ferr.write, inst)
85 return -1
85 return -1
86
86
87 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
87 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
88 starttime = time.time()
88 starttime = time.time()
89 ret = None
89 ret = None
90 try:
90 try:
91 ret = _runcatch(req)
91 ret = _runcatch(req)
92 return ret
92 return ret
93 finally:
93 finally:
94 duration = time.time() - starttime
94 duration = time.time() - starttime
95 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
95 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
96 msg, ret or 0, duration)
96 msg, ret or 0, duration)
97
97
98 def _runcatch(req):
98 def _runcatch(req):
99 def catchterm(*args):
99 def catchterm(*args):
100 raise error.SignalInterrupt
100 raise error.SignalInterrupt
101
101
102 ui = req.ui
102 ui = req.ui
103 try:
103 try:
104 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
104 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
105 num = getattr(signal, name, None)
105 num = getattr(signal, name, None)
106 if num:
106 if num:
107 signal.signal(num, catchterm)
107 signal.signal(num, catchterm)
108 except ValueError:
108 except ValueError:
109 pass # happens if called in a thread
109 pass # happens if called in a thread
110
110
111 try:
111 try:
112 try:
112 try:
113 debugger = 'pdb'
113 debugger = 'pdb'
114 debugtrace = {
114 debugtrace = {
115 'pdb' : pdb.set_trace
115 'pdb' : pdb.set_trace
116 }
116 }
117 debugmortem = {
117 debugmortem = {
118 'pdb' : pdb.post_mortem
118 'pdb' : pdb.post_mortem
119 }
119 }
120
120
121 # read --config before doing anything else
121 # read --config before doing anything else
122 # (e.g. to change trust settings for reading .hg/hgrc)
122 # (e.g. to change trust settings for reading .hg/hgrc)
123 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
123 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
124
124
125 if req.repo:
125 if req.repo:
126 # copy configs that were passed on the cmdline (--config) to
126 # copy configs that were passed on the cmdline (--config) to
127 # the repo ui
127 # the repo ui
128 for sec, name, val in cfgs:
128 for sec, name, val in cfgs:
129 req.repo.ui.setconfig(sec, name, val, source='--config')
129 req.repo.ui.setconfig(sec, name, val, source='--config')
130
130
131 # if we are in HGPLAIN mode, then disable custom debugging
131 # if we are in HGPLAIN mode, then disable custom debugging
132 debugger = ui.config("ui", "debugger")
132 debugger = ui.config("ui", "debugger")
133 debugmod = pdb
133 debugmod = pdb
134 if not debugger or ui.plain():
134 if not debugger or ui.plain():
135 debugger = 'pdb'
135 debugger = 'pdb'
136 elif '--debugger' in req.args:
136 elif '--debugger' in req.args:
137 # This import can be slow for fancy debuggers, so only
137 # This import can be slow for fancy debuggers, so only
138 # do it when absolutely necessary, i.e. when actual
138 # do it when absolutely necessary, i.e. when actual
139 # debugging has been requested
139 # debugging has been requested
140 try:
140 try:
141 debugmod = __import__(debugger)
141 debugmod = __import__(debugger)
142 except ImportError:
142 except ImportError:
143 pass # Leave debugmod = pdb
143 pass # Leave debugmod = pdb
144
144
145 debugtrace[debugger] = debugmod.set_trace
145 debugtrace[debugger] = debugmod.set_trace
146 debugmortem[debugger] = debugmod.post_mortem
146 debugmortem[debugger] = debugmod.post_mortem
147
147
148 # enter the debugger before command execution
148 # enter the debugger before command execution
149 if '--debugger' in req.args:
149 if '--debugger' in req.args:
150 ui.warn(_("entering debugger - "
150 ui.warn(_("entering debugger - "
151 "type c to continue starting hg or h for help\n"))
151 "type c to continue starting hg or h for help\n"))
152
152
153 if (debugger != 'pdb' and
153 if (debugger != 'pdb' and
154 debugtrace[debugger] == debugtrace['pdb']):
154 debugtrace[debugger] == debugtrace['pdb']):
155 ui.warn(_("%s debugger specified "
155 ui.warn(_("%s debugger specified "
156 "but its module was not found\n") % debugger)
156 "but its module was not found\n") % debugger)
157
157
158 debugtrace[debugger]()
158 debugtrace[debugger]()
159 try:
159 try:
160 return _dispatch(req)
160 return _dispatch(req)
161 finally:
161 finally:
162 ui.flush()
162 ui.flush()
163 except: # re-raises
163 except: # re-raises
164 # enter the debugger when we hit an exception
164 # enter the debugger when we hit an exception
165 if '--debugger' in req.args:
165 if '--debugger' in req.args:
166 traceback.print_exc()
166 traceback.print_exc()
167 debugmortem[debugger](sys.exc_info()[2])
167 debugmortem[debugger](sys.exc_info()[2])
168 ui.traceback()
168 ui.traceback()
169 raise
169 raise
170
170
171 # Global exception handling, alphabetically
171 # Global exception handling, alphabetically
172 # Mercurial-specific first, followed by built-in and library exceptions
172 # Mercurial-specific first, followed by built-in and library exceptions
173 except error.AmbiguousCommand, inst:
173 except error.AmbiguousCommand, inst:
174 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
174 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
175 (inst.args[0], " ".join(inst.args[1])))
175 (inst.args[0], " ".join(inst.args[1])))
176 except error.ParseError, inst:
176 except error.ParseError, inst:
177 _formatparse(ui.warn, inst)
177 _formatparse(ui.warn, inst)
178 return -1
178 return -1
179 except error.LockHeld, inst:
179 except error.LockHeld, inst:
180 if inst.errno == errno.ETIMEDOUT:
180 if inst.errno == errno.ETIMEDOUT:
181 reason = _('timed out waiting for lock held by %s') % inst.locker
181 reason = _('timed out waiting for lock held by %s') % inst.locker
182 else:
182 else:
183 reason = _('lock held by %s') % inst.locker
183 reason = _('lock held by %s') % inst.locker
184 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
184 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
185 except error.LockUnavailable, inst:
185 except error.LockUnavailable, inst:
186 ui.warn(_("abort: could not lock %s: %s\n") %
186 ui.warn(_("abort: could not lock %s: %s\n") %
187 (inst.desc or inst.filename, inst.strerror))
187 (inst.desc or inst.filename, inst.strerror))
188 except error.CommandError, inst:
188 except error.CommandError, inst:
189 if inst.args[0]:
189 if inst.args[0]:
190 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
190 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
191 commands.help_(ui, inst.args[0], full=False, command=True)
191 commands.help_(ui, inst.args[0], full=False, command=True)
192 else:
192 else:
193 ui.warn(_("hg: %s\n") % inst.args[1])
193 ui.warn(_("hg: %s\n") % inst.args[1])
194 commands.help_(ui, 'shortlist')
194 commands.help_(ui, 'shortlist')
195 except error.OutOfBandError, inst:
195 except error.OutOfBandError, inst:
196 ui.warn(_("abort: remote error:\n"))
196 ui.warn(_("abort: remote error:\n"))
197 ui.warn(''.join(inst.args))
197 ui.warn(''.join(inst.args))
198 except error.RepoError, inst:
198 except error.RepoError, inst:
199 ui.warn(_("abort: %s!\n") % inst)
199 ui.warn(_("abort: %s!\n") % inst)
200 if inst.hint:
200 if inst.hint:
201 ui.warn(_("(%s)\n") % inst.hint)
201 ui.warn(_("(%s)\n") % inst.hint)
202 except error.ResponseError, inst:
202 except error.ResponseError, inst:
203 ui.warn(_("abort: %s") % inst.args[0])
203 ui.warn(_("abort: %s") % inst.args[0])
204 if not isinstance(inst.args[1], basestring):
204 if not isinstance(inst.args[1], basestring):
205 ui.warn(" %r\n" % (inst.args[1],))
205 ui.warn(" %r\n" % (inst.args[1],))
206 elif not inst.args[1]:
206 elif not inst.args[1]:
207 ui.warn(_(" empty string\n"))
207 ui.warn(_(" empty string\n"))
208 else:
208 else:
209 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
209 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
210 except error.CensoredNodeError, inst:
210 except error.CensoredNodeError, inst:
211 ui.warn(_("abort: file censored %s!\n") % inst)
211 ui.warn(_("abort: file censored %s!\n") % inst)
212 except error.RevlogError, inst:
212 except error.RevlogError, inst:
213 ui.warn(_("abort: %s!\n") % inst)
213 ui.warn(_("abort: %s!\n") % inst)
214 except error.SignalInterrupt:
214 except error.SignalInterrupt:
215 ui.warn(_("killed!\n"))
215 ui.warn(_("killed!\n"))
216 except error.UnknownCommand, inst:
216 except error.UnknownCommand, inst:
217 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
217 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
218 try:
218 try:
219 # check if the command is in a disabled extension
219 # check if the command is in a disabled extension
220 # (but don't check for extensions themselves)
220 # (but don't check for extensions themselves)
221 commands.help_(ui, inst.args[0], unknowncmd=True)
221 commands.help_(ui, inst.args[0], unknowncmd=True)
222 except error.UnknownCommand:
222 except error.UnknownCommand:
223 suggested = False
223 suggested = False
224 if len(inst.args) == 2:
224 if len(inst.args) == 2:
225 sim = _getsimilar(inst.args[1], inst.args[0])
225 sim = _getsimilar(inst.args[1], inst.args[0])
226 if sim:
226 if sim:
227 ui.warn(_('(did you mean one of %s?)\n') %
227 ui.warn(_('(did you mean one of %s?)\n') %
228 ', '.join(sorted(sim)))
228 ', '.join(sorted(sim)))
229 suggested = True
229 suggested = True
230 if not suggested:
230 if not suggested:
231 commands.help_(ui, 'shortlist')
231 commands.help_(ui, 'shortlist')
232 except error.InterventionRequired, inst:
232 except error.InterventionRequired, inst:
233 ui.warn("%s\n" % inst)
233 ui.warn("%s\n" % inst)
234 return 1
234 return 1
235 except util.Abort, inst:
235 except util.Abort, inst:
236 ui.warn(_("abort: %s\n") % inst)
236 ui.warn(_("abort: %s\n") % inst)
237 if inst.hint:
237 if inst.hint:
238 ui.warn(_("(%s)\n") % inst.hint)
238 ui.warn(_("(%s)\n") % inst.hint)
239 except ImportError, inst:
239 except ImportError, inst:
240 ui.warn(_("abort: %s!\n") % inst)
240 ui.warn(_("abort: %s!\n") % inst)
241 m = str(inst).split()[-1]
241 m = str(inst).split()[-1]
242 if m in "mpatch bdiff".split():
242 if m in "mpatch bdiff".split():
243 ui.warn(_("(did you forget to compile extensions?)\n"))
243 ui.warn(_("(did you forget to compile extensions?)\n"))
244 elif m in "zlib".split():
244 elif m in "zlib".split():
245 ui.warn(_("(is your Python install correct?)\n"))
245 ui.warn(_("(is your Python install correct?)\n"))
246 except IOError, inst:
246 except IOError, inst:
247 if util.safehasattr(inst, "code"):
247 if util.safehasattr(inst, "code"):
248 ui.warn(_("abort: %s\n") % inst)
248 ui.warn(_("abort: %s\n") % inst)
249 elif util.safehasattr(inst, "reason"):
249 elif util.safehasattr(inst, "reason"):
250 try: # usually it is in the form (errno, strerror)
250 try: # usually it is in the form (errno, strerror)
251 reason = inst.reason.args[1]
251 reason = inst.reason.args[1]
252 except (AttributeError, IndexError):
252 except (AttributeError, IndexError):
253 # it might be anything, for example a string
253 # it might be anything, for example a string
254 reason = inst.reason
254 reason = inst.reason
255 if isinstance(reason, unicode):
255 if isinstance(reason, unicode):
256 # SSLError of Python 2.7.9 contains a unicode
256 # SSLError of Python 2.7.9 contains a unicode
257 reason = reason.encode(encoding.encoding, 'replace')
257 reason = reason.encode(encoding.encoding, 'replace')
258 ui.warn(_("abort: error: %s\n") % reason)
258 ui.warn(_("abort: error: %s\n") % reason)
259 elif (util.safehasattr(inst, "args")
259 elif (util.safehasattr(inst, "args")
260 and inst.args and inst.args[0] == errno.EPIPE):
260 and inst.args and inst.args[0] == errno.EPIPE):
261 if ui.debugflag:
261 if ui.debugflag:
262 ui.warn(_("broken pipe\n"))
262 ui.warn(_("broken pipe\n"))
263 elif getattr(inst, "strerror", None):
263 elif getattr(inst, "strerror", None):
264 if getattr(inst, "filename", None):
264 if getattr(inst, "filename", None):
265 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
265 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
266 else:
266 else:
267 ui.warn(_("abort: %s\n") % inst.strerror)
267 ui.warn(_("abort: %s\n") % inst.strerror)
268 else:
268 else:
269 raise
269 raise
270 except OSError, inst:
270 except OSError, inst:
271 if getattr(inst, "filename", None) is not None:
271 if getattr(inst, "filename", None) is not None:
272 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
272 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
273 else:
273 else:
274 ui.warn(_("abort: %s\n") % inst.strerror)
274 ui.warn(_("abort: %s\n") % inst.strerror)
275 except KeyboardInterrupt:
275 except KeyboardInterrupt:
276 try:
276 try:
277 ui.warn(_("interrupted!\n"))
277 ui.warn(_("interrupted!\n"))
278 except IOError, inst:
278 except IOError, inst:
279 if inst.errno == errno.EPIPE:
279 if inst.errno == errno.EPIPE:
280 if ui.debugflag:
280 if ui.debugflag:
281 ui.warn(_("\nbroken pipe\n"))
281 ui.warn(_("\nbroken pipe\n"))
282 else:
282 else:
283 raise
283 raise
284 except MemoryError:
284 except MemoryError:
285 ui.warn(_("abort: out of memory\n"))
285 ui.warn(_("abort: out of memory\n"))
286 except SystemExit, inst:
286 except SystemExit, inst:
287 # Commands shouldn't sys.exit directly, but give a return code.
287 # Commands shouldn't sys.exit directly, but give a return code.
288 # Just in case catch this and and pass exit code to caller.
288 # Just in case catch this and and pass exit code to caller.
289 return inst.code
289 return inst.code
290 except socket.error, inst:
290 except socket.error, inst:
291 ui.warn(_("abort: %s\n") % inst.args[-1])
291 ui.warn(_("abort: %s\n") % inst.args[-1])
292 except: # re-raises
292 except: # re-raises
293 myver = util.version()
293 myver = util.version()
294 # For compatibility checking, we discard the portion of the hg
294 # For compatibility checking, we discard the portion of the hg
295 # version after the + on the assumption that if a "normal
295 # version after the + on the assumption that if a "normal
296 # user" is running a build with a + in it the packager
296 # user" is running a build with a + in it the packager
297 # probably built from fairly close to a tag and anyone with a
297 # probably built from fairly close to a tag and anyone with a
298 # 'make local' copy of hg (where the version number can be out
298 # 'make local' copy of hg (where the version number can be out
299 # of date) will be clueful enough to notice the implausible
299 # of date) will be clueful enough to notice the implausible
300 # version number and try updating.
300 # version number and try updating.
301 compare = myver.split('+')[0]
301 compare = myver.split('+')[0]
302 ct = tuplever(compare)
302 ct = tuplever(compare)
303 worst = None, ct, ''
303 worst = None, ct, ''
304 for name, mod in extensions.extensions():
304 for name, mod in extensions.extensions():
305 testedwith = getattr(mod, 'testedwith', '')
305 testedwith = getattr(mod, 'testedwith', '')
306 report = getattr(mod, 'buglink', _('the extension author.'))
306 report = getattr(mod, 'buglink', _('the extension author.'))
307 if not testedwith.strip():
307 if not testedwith.strip():
308 # We found an untested extension. It's likely the culprit.
308 # We found an untested extension. It's likely the culprit.
309 worst = name, 'unknown', report
309 worst = name, 'unknown', report
310 break
310 break
311
311
312 # Never blame on extensions bundled with Mercurial.
312 # Never blame on extensions bundled with Mercurial.
313 if testedwith == 'internal':
313 if testedwith == 'internal':
314 continue
314 continue
315
315
316 tested = [tuplever(t) for t in testedwith.split()]
316 tested = [tuplever(t) for t in testedwith.split()]
317 if ct in tested:
317 if ct in tested:
318 continue
318 continue
319
319
320 lower = [t for t in tested if t < ct]
320 lower = [t for t in tested if t < ct]
321 nearest = max(lower or tested)
321 nearest = max(lower or tested)
322 if worst[0] is None or nearest < worst[1]:
322 if worst[0] is None or nearest < worst[1]:
323 worst = name, nearest, report
323 worst = name, nearest, report
324 if worst[0] is not None:
324 if worst[0] is not None:
325 name, testedwith, report = worst
325 name, testedwith, report = worst
326 if not isinstance(testedwith, str):
326 if not isinstance(testedwith, str):
327 testedwith = '.'.join([str(c) for c in testedwith])
327 testedwith = '.'.join([str(c) for c in testedwith])
328 warning = (_('** Unknown exception encountered with '
328 warning = (_('** Unknown exception encountered with '
329 'possibly-broken third-party extension %s\n'
329 'possibly-broken third-party extension %s\n'
330 '** which supports versions %s of Mercurial.\n'
330 '** which supports versions %s of Mercurial.\n'
331 '** Please disable %s and try your action again.\n'
331 '** Please disable %s and try your action again.\n'
332 '** If that fixes the bug please report it to %s\n')
332 '** If that fixes the bug please report it to %s\n')
333 % (name, testedwith, name, report))
333 % (name, testedwith, name, report))
334 else:
334 else:
335 warning = (_("** unknown exception encountered, "
335 warning = (_("** unknown exception encountered, "
336 "please report by visiting\n") +
336 "please report by visiting\n") +
337 _("** http://mercurial.selenic.com/wiki/BugTracker\n"))
337 _("** http://mercurial.selenic.com/wiki/BugTracker\n"))
338 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
338 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
339 (_("** Mercurial Distributed SCM (version %s)\n") % myver) +
339 (_("** Mercurial Distributed SCM (version %s)\n") % myver) +
340 (_("** Extensions loaded: %s\n") %
340 (_("** Extensions loaded: %s\n") %
341 ", ".join([x[0] for x in extensions.extensions()])))
341 ", ".join([x[0] for x in extensions.extensions()])))
342 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
342 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
343 ui.warn(warning)
343 ui.warn(warning)
344 raise
344 raise
345
345
346 return -1
346 return -1
347
347
348 def tuplever(v):
348 def tuplever(v):
349 try:
349 try:
350 # Assertion: tuplever is only used for extension compatibility
350 # Assertion: tuplever is only used for extension compatibility
351 # checking. Otherwise, the discarding of extra version fields is
351 # checking. Otherwise, the discarding of extra version fields is
352 # incorrect.
352 # incorrect.
353 return tuple([int(i) for i in v.split('.')[0:2]])
353 return tuple([int(i) for i in v.split('.')[0:2]])
354 except ValueError:
354 except ValueError:
355 return tuple()
355 return tuple()
356
356
357 def aliasargs(fn, givenargs):
357 def aliasargs(fn, givenargs):
358 args = getattr(fn, 'args', [])
358 args = getattr(fn, 'args', [])
359 if args:
359 if args:
360 cmd = ' '.join(map(util.shellquote, args))
360 cmd = ' '.join(map(util.shellquote, args))
361
361
362 nums = []
362 nums = []
363 def replacer(m):
363 def replacer(m):
364 num = int(m.group(1)) - 1
364 num = int(m.group(1)) - 1
365 nums.append(num)
365 nums.append(num)
366 if num < len(givenargs):
366 if num < len(givenargs):
367 return givenargs[num]
367 return givenargs[num]
368 raise util.Abort(_('too few arguments for command alias'))
368 raise util.Abort(_('too few arguments for command alias'))
369 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
369 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
370 givenargs = [x for i, x in enumerate(givenargs)
370 givenargs = [x for i, x in enumerate(givenargs)
371 if i not in nums]
371 if i not in nums]
372 args = shlex.split(cmd)
372 args = shlex.split(cmd)
373 return args + givenargs
373 return args + givenargs
374
374
375 def aliasinterpolate(name, args, cmd):
375 def aliasinterpolate(name, args, cmd):
376 '''interpolate args into cmd for shell aliases
376 '''interpolate args into cmd for shell aliases
377
377
378 This also handles $0, $@ and "$@".
378 This also handles $0, $@ and "$@".
379 '''
379 '''
380 # util.interpolate can't deal with "$@" (with quotes) because it's only
380 # util.interpolate can't deal with "$@" (with quotes) because it's only
381 # built to match prefix + patterns.
381 # built to match prefix + patterns.
382 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
382 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
383 replacemap['$0'] = name
383 replacemap['$0'] = name
384 replacemap['$$'] = '$'
384 replacemap['$$'] = '$'
385 replacemap['$@'] = ' '.join(args)
385 replacemap['$@'] = ' '.join(args)
386 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
386 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
387 # parameters, separated out into words. Emulate the same behavior here by
387 # parameters, separated out into words. Emulate the same behavior here by
388 # quoting the arguments individually. POSIX shells will then typically
388 # quoting the arguments individually. POSIX shells will then typically
389 # tokenize each argument into exactly one word.
389 # tokenize each argument into exactly one word.
390 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
390 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
391 # escape '\$' for regex
391 # escape '\$' for regex
392 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
392 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
393 r = re.compile(regex)
393 r = re.compile(regex)
394 return r.sub(lambda x: replacemap[x.group()], cmd)
394 return r.sub(lambda x: replacemap[x.group()], cmd)
395
395
396 class cmdalias(object):
396 class cmdalias(object):
397 def __init__(self, name, definition, cmdtable):
397 def __init__(self, name, definition, cmdtable):
398 self.name = self.cmd = name
398 self.name = self.cmd = name
399 self.cmdname = ''
399 self.cmdname = ''
400 self.definition = definition
400 self.definition = definition
401 self.fn = None
401 self.fn = None
402 self.args = []
402 self.args = []
403 self.opts = []
403 self.opts = []
404 self.help = ''
404 self.help = ''
405 self.norepo = True
405 self.norepo = True
406 self.optionalrepo = False
406 self.optionalrepo = False
407 self.badalias = None
407 self.badalias = None
408 self.unknowncmd = False
408 self.unknowncmd = False
409
409
410 try:
410 try:
411 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
411 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
412 for alias, e in cmdtable.iteritems():
412 for alias, e in cmdtable.iteritems():
413 if e is entry:
413 if e is entry:
414 self.cmd = alias
414 self.cmd = alias
415 break
415 break
416 self.shadows = True
416 self.shadows = True
417 except error.UnknownCommand:
417 except error.UnknownCommand:
418 self.shadows = False
418 self.shadows = False
419
419
420 if not self.definition:
420 if not self.definition:
421 self.badalias = _("no definition for alias '%s'") % self.name
421 self.badalias = _("no definition for alias '%s'") % self.name
422 return
422 return
423
423
424 if self.definition.startswith('!'):
424 if self.definition.startswith('!'):
425 self.shell = True
425 self.shell = True
426 def fn(ui, *args):
426 def fn(ui, *args):
427 env = {'HG_ARGS': ' '.join((self.name,) + args)}
427 env = {'HG_ARGS': ' '.join((self.name,) + args)}
428 def _checkvar(m):
428 def _checkvar(m):
429 if m.groups()[0] == '$':
429 if m.groups()[0] == '$':
430 return m.group()
430 return m.group()
431 elif int(m.groups()[0]) <= len(args):
431 elif int(m.groups()[0]) <= len(args):
432 return m.group()
432 return m.group()
433 else:
433 else:
434 ui.debug("No argument found for substitution "
434 ui.debug("No argument found for substitution "
435 "of %i variable in alias '%s' definition."
435 "of %i variable in alias '%s' definition."
436 % (int(m.groups()[0]), self.name))
436 % (int(m.groups()[0]), self.name))
437 return ''
437 return ''
438 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
438 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
439 cmd = aliasinterpolate(self.name, args, cmd)
439 cmd = aliasinterpolate(self.name, args, cmd)
440 return ui.system(cmd, environ=env)
440 return ui.system(cmd, environ=env)
441 self.fn = fn
441 self.fn = fn
442 return
442 return
443
443
444 try:
444 try:
445 args = shlex.split(self.definition)
445 args = shlex.split(self.definition)
446 except ValueError, inst:
446 except ValueError, inst:
447 self.badalias = (_("error in definition for alias '%s': %s")
447 self.badalias = (_("error in definition for alias '%s': %s")
448 % (self.name, inst))
448 % (self.name, inst))
449 return
449 return
450 self.cmdname = cmd = args.pop(0)
450 self.cmdname = cmd = args.pop(0)
451 args = map(util.expandpath, args)
451 args = map(util.expandpath, args)
452
452
453 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
453 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
454 if _earlygetopt([invalidarg], args):
454 if _earlygetopt([invalidarg], args):
455 self.badalias = (_("error in definition for alias '%s': %s may "
455 self.badalias = (_("error in definition for alias '%s': %s may "
456 "only be given on the command line")
456 "only be given on the command line")
457 % (self.name, invalidarg))
457 % (self.name, invalidarg))
458 return
458 return
459
459
460 try:
460 try:
461 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
461 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
462 if len(tableentry) > 2:
462 if len(tableentry) > 2:
463 self.fn, self.opts, self.help = tableentry
463 self.fn, self.opts, self.help = tableentry
464 else:
464 else:
465 self.fn, self.opts = tableentry
465 self.fn, self.opts = tableentry
466
466
467 self.args = aliasargs(self.fn, args)
467 self.args = aliasargs(self.fn, args)
468 if cmd not in commands.norepo.split(' '):
468 if cmd not in commands.norepo.split(' '):
469 self.norepo = False
469 self.norepo = False
470 if cmd in commands.optionalrepo.split(' '):
470 if cmd in commands.optionalrepo.split(' '):
471 self.optionalrepo = True
471 self.optionalrepo = True
472 if self.help.startswith("hg " + cmd):
472 if self.help.startswith("hg " + cmd):
473 # drop prefix in old-style help lines so hg shows the alias
473 # drop prefix in old-style help lines so hg shows the alias
474 self.help = self.help[4 + len(cmd):]
474 self.help = self.help[4 + len(cmd):]
475 self.__doc__ = self.fn.__doc__
475 self.__doc__ = self.fn.__doc__
476
476
477 except error.UnknownCommand:
477 except error.UnknownCommand:
478 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
478 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
479 % (self.name, cmd))
479 % (self.name, cmd))
480 self.unknowncmd = True
480 self.unknowncmd = True
481 except error.AmbiguousCommand:
481 except error.AmbiguousCommand:
482 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
482 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
483 % (self.name, cmd))
483 % (self.name, cmd))
484
484
485 def __call__(self, ui, *args, **opts):
485 def __call__(self, ui, *args, **opts):
486 if self.badalias:
486 if self.badalias:
487 hint = None
487 hint = None
488 if self.unknowncmd:
488 if self.unknowncmd:
489 try:
489 try:
490 # check if the command is in a disabled extension
490 # check if the command is in a disabled extension
491 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
491 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
492 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
492 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
493 except error.UnknownCommand:
493 except error.UnknownCommand:
494 pass
494 pass
495 raise util.Abort(self.badalias, hint=hint)
495 raise util.Abort(self.badalias, hint=hint)
496 if self.shadows:
496 if self.shadows:
497 ui.debug("alias '%s' shadows command '%s'\n" %
497 ui.debug("alias '%s' shadows command '%s'\n" %
498 (self.name, self.cmdname))
498 (self.name, self.cmdname))
499
499
500 if util.safehasattr(self, 'shell'):
500 if util.safehasattr(self, 'shell'):
501 return self.fn(ui, *args, **opts)
501 return self.fn(ui, *args, **opts)
502 else:
502 else:
503 try:
503 try:
504 return util.checksignature(self.fn)(ui, *args, **opts)
504 return util.checksignature(self.fn)(ui, *args, **opts)
505 except error.SignatureError:
505 except error.SignatureError:
506 args = ' '.join([self.cmdname] + self.args)
506 args = ' '.join([self.cmdname] + self.args)
507 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
507 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
508 raise
508 raise
509
509
510 def addaliases(ui, cmdtable):
510 def addaliases(ui, cmdtable):
511 # aliases are processed after extensions have been loaded, so they
511 # aliases are processed after extensions have been loaded, so they
512 # may use extension commands. Aliases can also use other alias definitions,
512 # may use extension commands. Aliases can also use other alias definitions,
513 # but only if they have been defined prior to the current definition.
513 # but only if they have been defined prior to the current definition.
514 for alias, definition in ui.configitems('alias'):
514 for alias, definition in ui.configitems('alias'):
515 aliasdef = cmdalias(alias, definition, cmdtable)
515 aliasdef = cmdalias(alias, definition, cmdtable)
516
516
517 try:
517 try:
518 olddef = cmdtable[aliasdef.cmd][0]
518 olddef = cmdtable[aliasdef.cmd][0]
519 if olddef.definition == aliasdef.definition:
519 if olddef.definition == aliasdef.definition:
520 continue
520 continue
521 except (KeyError, AttributeError):
521 except (KeyError, AttributeError):
522 # definition might not exist or it might not be a cmdalias
522 # definition might not exist or it might not be a cmdalias
523 pass
523 pass
524
524
525 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
525 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
526 if aliasdef.norepo:
526 if aliasdef.norepo:
527 commands.norepo += ' %s' % alias
527 commands.norepo += ' %s' % alias
528 if aliasdef.optionalrepo:
528 if aliasdef.optionalrepo:
529 commands.optionalrepo += ' %s' % alias
529 commands.optionalrepo += ' %s' % alias
530
530
531 def _parse(ui, args):
531 def _parse(ui, args):
532 options = {}
532 options = {}
533 cmdoptions = {}
533 cmdoptions = {}
534
534
535 try:
535 try:
536 args = fancyopts.fancyopts(args, commands.globalopts, options)
536 args = fancyopts.fancyopts(args, commands.globalopts, options)
537 except fancyopts.getopt.GetoptError, inst:
537 except fancyopts.getopt.GetoptError, inst:
538 raise error.CommandError(None, inst)
538 raise error.CommandError(None, inst)
539
539
540 if args:
540 if args:
541 cmd, args = args[0], args[1:]
541 cmd, args = args[0], args[1:]
542 aliases, entry = cmdutil.findcmd(cmd, commands.table,
542 aliases, entry = cmdutil.findcmd(cmd, commands.table,
543 ui.configbool("ui", "strict"))
543 ui.configbool("ui", "strict"))
544 cmd = aliases[0]
544 cmd = aliases[0]
545 args = aliasargs(entry[0], args)
545 args = aliasargs(entry[0], args)
546 defaults = ui.config("defaults", cmd)
546 defaults = ui.config("defaults", cmd)
547 if defaults:
547 if defaults:
548 args = map(util.expandpath, shlex.split(defaults)) + args
548 args = map(util.expandpath, shlex.split(defaults)) + args
549 c = list(entry[1])
549 c = list(entry[1])
550 else:
550 else:
551 cmd = None
551 cmd = None
552 c = []
552 c = []
553
553
554 # combine global options into local
554 # combine global options into local
555 for o in commands.globalopts:
555 for o in commands.globalopts:
556 c.append((o[0], o[1], options[o[1]], o[3]))
556 c.append((o[0], o[1], options[o[1]], o[3]))
557
557
558 try:
558 try:
559 args = fancyopts.fancyopts(args, c, cmdoptions, True)
559 args = fancyopts.fancyopts(args, c, cmdoptions, True)
560 except fancyopts.getopt.GetoptError, inst:
560 except fancyopts.getopt.GetoptError, inst:
561 raise error.CommandError(cmd, inst)
561 raise error.CommandError(cmd, inst)
562
562
563 # separate global options back out
563 # separate global options back out
564 for o in commands.globalopts:
564 for o in commands.globalopts:
565 n = o[1]
565 n = o[1]
566 options[n] = cmdoptions[n]
566 options[n] = cmdoptions[n]
567 del cmdoptions[n]
567 del cmdoptions[n]
568
568
569 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
569 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
570
570
571 def _parseconfig(ui, config):
571 def _parseconfig(ui, config):
572 """parse the --config options from the command line"""
572 """parse the --config options from the command line"""
573 configs = []
573 configs = []
574
574
575 for cfg in config:
575 for cfg in config:
576 try:
576 try:
577 name, value = cfg.split('=', 1)
577 name, value = cfg.split('=', 1)
578 section, name = name.split('.', 1)
578 section, name = name.split('.', 1)
579 if not section or not name:
579 if not section or not name:
580 raise IndexError
580 raise IndexError
581 ui.setconfig(section, name, value, '--config')
581 ui.setconfig(section, name, value, '--config')
582 configs.append((section, name, value))
582 configs.append((section, name, value))
583 except (IndexError, ValueError):
583 except (IndexError, ValueError):
584 raise util.Abort(_('malformed --config option: %r '
584 raise util.Abort(_('malformed --config option: %r '
585 '(use --config section.name=value)') % cfg)
585 '(use --config section.name=value)') % cfg)
586
586
587 return configs
587 return configs
588
588
589 def _earlygetopt(aliases, args):
589 def _earlygetopt(aliases, args):
590 """Return list of values for an option (or aliases).
590 """Return list of values for an option (or aliases).
591
591
592 The values are listed in the order they appear in args.
592 The values are listed in the order they appear in args.
593 The options and values are removed from args.
593 The options and values are removed from args.
594
594
595 >>> args = ['x', '--cwd', 'foo', 'y']
595 >>> args = ['x', '--cwd', 'foo', 'y']
596 >>> _earlygetopt(['--cwd'], args), args
596 >>> _earlygetopt(['--cwd'], args), args
597 (['foo'], ['x', 'y'])
597 (['foo'], ['x', 'y'])
598
598
599 >>> args = ['x', '--cwd=bar', 'y']
599 >>> args = ['x', '--cwd=bar', 'y']
600 >>> _earlygetopt(['--cwd'], args), args
600 >>> _earlygetopt(['--cwd'], args), args
601 (['bar'], ['x', 'y'])
601 (['bar'], ['x', 'y'])
602
602
603 >>> args = ['x', '-R', 'foo', 'y']
603 >>> args = ['x', '-R', 'foo', 'y']
604 >>> _earlygetopt(['-R'], args), args
604 >>> _earlygetopt(['-R'], args), args
605 (['foo'], ['x', 'y'])
605 (['foo'], ['x', 'y'])
606
606
607 >>> args = ['x', '-Rbar', 'y']
607 >>> args = ['x', '-Rbar', 'y']
608 >>> _earlygetopt(['-R'], args), args
608 >>> _earlygetopt(['-R'], args), args
609 (['bar'], ['x', 'y'])
609 (['bar'], ['x', 'y'])
610 """
610 """
611 try:
611 try:
612 argcount = args.index("--")
612 argcount = args.index("--")
613 except ValueError:
613 except ValueError:
614 argcount = len(args)
614 argcount = len(args)
615 shortopts = [opt for opt in aliases if len(opt) == 2]
615 shortopts = [opt for opt in aliases if len(opt) == 2]
616 values = []
616 values = []
617 pos = 0
617 pos = 0
618 while pos < argcount:
618 while pos < argcount:
619 fullarg = arg = args[pos]
619 fullarg = arg = args[pos]
620 equals = arg.find('=')
620 equals = arg.find('=')
621 if equals > -1:
621 if equals > -1:
622 arg = arg[:equals]
622 arg = arg[:equals]
623 if arg in aliases:
623 if arg in aliases:
624 del args[pos]
624 del args[pos]
625 if equals > -1:
625 if equals > -1:
626 values.append(fullarg[equals + 1:])
626 values.append(fullarg[equals + 1:])
627 argcount -= 1
627 argcount -= 1
628 else:
628 else:
629 if pos + 1 >= argcount:
629 if pos + 1 >= argcount:
630 # ignore and let getopt report an error if there is no value
630 # ignore and let getopt report an error if there is no value
631 break
631 break
632 values.append(args.pop(pos))
632 values.append(args.pop(pos))
633 argcount -= 2
633 argcount -= 2
634 elif arg[:2] in shortopts:
634 elif arg[:2] in shortopts:
635 # short option can have no following space, e.g. hg log -Rfoo
635 # short option can have no following space, e.g. hg log -Rfoo
636 values.append(args.pop(pos)[2:])
636 values.append(args.pop(pos)[2:])
637 argcount -= 1
637 argcount -= 1
638 else:
638 else:
639 pos += 1
639 pos += 1
640 return values
640 return values
641
641
642 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
642 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
643 # run pre-hook, and abort if it fails
643 # run pre-hook, and abort if it fails
644 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
644 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
645 pats=cmdpats, opts=cmdoptions)
645 pats=cmdpats, opts=cmdoptions)
646 ret = _runcommand(ui, options, cmd, d)
646 ret = _runcommand(ui, options, cmd, d)
647 # run post-hook, passing command result
647 # run post-hook, passing command result
648 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
648 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
649 result=ret, pats=cmdpats, opts=cmdoptions)
649 result=ret, pats=cmdpats, opts=cmdoptions)
650 return ret
650 return ret
651
651
652 def _getlocal(ui, rpath):
652 def _getlocal(ui, rpath):
653 """Return (path, local ui object) for the given target path.
653 """Return (path, local ui object) for the given target path.
654
654
655 Takes paths in [cwd]/.hg/hgrc into account."
655 Takes paths in [cwd]/.hg/hgrc into account."
656 """
656 """
657 try:
657 try:
658 wd = os.getcwd()
658 wd = os.getcwd()
659 except OSError, e:
659 except OSError, e:
660 raise util.Abort(_("error getting current working directory: %s") %
660 raise util.Abort(_("error getting current working directory: %s") %
661 e.strerror)
661 e.strerror)
662 path = cmdutil.findrepo(wd) or ""
662 path = cmdutil.findrepo(wd) or ""
663 if not path:
663 if not path:
664 lui = ui
664 lui = ui
665 else:
665 else:
666 lui = ui.copy()
666 lui = ui.copy()
667 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
667 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
668
668
669 if rpath and rpath[-1]:
669 if rpath and rpath[-1]:
670 path = lui.expandpath(rpath[-1])
670 path = lui.expandpath(rpath[-1])
671 lui = ui.copy()
671 lui = ui.copy()
672 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
672 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
673
673
674 return path, lui
674 return path, lui
675
675
676 def _checkshellalias(lui, ui, args, precheck=True):
676 def _checkshellalias(lui, ui, args, precheck=True):
677 """Return the function to run the shell alias, if it is required
677 """Return the function to run the shell alias, if it is required
678
678
679 'precheck' is whether this function is invoked before adding
679 'precheck' is whether this function is invoked before adding
680 aliases or not.
680 aliases or not.
681 """
681 """
682 options = {}
682 options = {}
683
683
684 try:
684 try:
685 args = fancyopts.fancyopts(args, commands.globalopts, options)
685 args = fancyopts.fancyopts(args, commands.globalopts, options)
686 except fancyopts.getopt.GetoptError:
686 except fancyopts.getopt.GetoptError:
687 return
687 return
688
688
689 if not args:
689 if not args:
690 return
690 return
691
691
692 if precheck:
692 if precheck:
693 strict = True
693 strict = True
694 norepo = commands.norepo
694 norepo = commands.norepo
695 optionalrepo = commands.optionalrepo
695 optionalrepo = commands.optionalrepo
696 def restorecommands():
696 def restorecommands():
697 commands.norepo = norepo
697 commands.norepo = norepo
698 commands.optionalrepo = optionalrepo
698 commands.optionalrepo = optionalrepo
699 cmdtable = commands.table.copy()
699 cmdtable = commands.table.copy()
700 addaliases(lui, cmdtable)
700 addaliases(lui, cmdtable)
701 else:
701 else:
702 strict = False
702 strict = False
703 def restorecommands():
703 def restorecommands():
704 pass
704 pass
705 cmdtable = commands.table
705 cmdtable = commands.table
706
706
707 cmd = args[0]
707 cmd = args[0]
708 try:
708 try:
709 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
709 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
710 except (error.AmbiguousCommand, error.UnknownCommand):
710 except (error.AmbiguousCommand, error.UnknownCommand):
711 restorecommands()
711 restorecommands()
712 return
712 return
713
713
714 cmd = aliases[0]
714 cmd = aliases[0]
715 fn = entry[0]
715 fn = entry[0]
716
716
717 if cmd and util.safehasattr(fn, 'shell'):
717 if cmd and util.safehasattr(fn, 'shell'):
718 d = lambda: fn(ui, *args[1:])
718 d = lambda: fn(ui, *args[1:])
719 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
719 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
720 [], {})
720 [], {})
721
721
722 restorecommands()
722 restorecommands()
723
723
724 _loaded = set()
724 _loaded = set()
725 def _dispatch(req):
725 def _dispatch(req):
726 args = req.args
726 args = req.args
727 ui = req.ui
727 ui = req.ui
728
728
729 # check for cwd
729 # check for cwd
730 cwd = _earlygetopt(['--cwd'], args)
730 cwd = _earlygetopt(['--cwd'], args)
731 if cwd:
731 if cwd:
732 os.chdir(cwd[-1])
732 os.chdir(cwd[-1])
733
733
734 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
734 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
735 path, lui = _getlocal(ui, rpath)
735 path, lui = _getlocal(ui, rpath)
736
736
737 # Now that we're operating in the right directory/repository with
737 # Now that we're operating in the right directory/repository with
738 # the right config settings, check for shell aliases
738 # the right config settings, check for shell aliases
739 shellaliasfn = _checkshellalias(lui, ui, args)
739 shellaliasfn = _checkshellalias(lui, ui, args)
740 if shellaliasfn:
740 if shellaliasfn:
741 return shellaliasfn()
741 return shellaliasfn()
742
742
743 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
743 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
744 # reposetup. Programs like TortoiseHg will call _dispatch several
744 # reposetup. Programs like TortoiseHg will call _dispatch several
745 # times so we keep track of configured extensions in _loaded.
745 # times so we keep track of configured extensions in _loaded.
746 extensions.loadall(lui)
746 extensions.loadall(lui)
747 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
747 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
748 # Propagate any changes to lui.__class__ by extensions
748 # Propagate any changes to lui.__class__ by extensions
749 ui.__class__ = lui.__class__
749 ui.__class__ = lui.__class__
750
750
751 # (uisetup and extsetup are handled in extensions.loadall)
751 # (uisetup and extsetup are handled in extensions.loadall)
752
752
753 for name, module in exts:
753 for name, module in exts:
754 cmdtable = getattr(module, 'cmdtable', {})
754 cmdtable = getattr(module, 'cmdtable', {})
755 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
755 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
756 if overrides:
756 if overrides:
757 ui.warn(_("extension '%s' overrides commands: %s\n")
757 ui.warn(_("extension '%s' overrides commands: %s\n")
758 % (name, " ".join(overrides)))
758 % (name, " ".join(overrides)))
759 commands.table.update(cmdtable)
759 commands.table.update(cmdtable)
760 _loaded.add(name)
760 _loaded.add(name)
761
761
762 # (reposetup is handled in hg.repository)
762 # (reposetup is handled in hg.repository)
763
763
764 addaliases(lui, commands.table)
764 addaliases(lui, commands.table)
765
765
766 if not lui.configbool("ui", "strict"):
766 if not lui.configbool("ui", "strict"):
767 # All aliases and commands are completely defined, now.
767 # All aliases and commands are completely defined, now.
768 # Check abbreviation/ambiguity of shell alias again, because shell
768 # Check abbreviation/ambiguity of shell alias again, because shell
769 # alias may cause failure of "_parse" (see issue4355)
769 # alias may cause failure of "_parse" (see issue4355)
770 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
770 shellaliasfn = _checkshellalias(lui, ui, args, precheck=False)
771 if shellaliasfn:
771 if shellaliasfn:
772 return shellaliasfn()
772 return shellaliasfn()
773
773
774 # check for fallback encoding
774 # check for fallback encoding
775 fallback = lui.config('ui', 'fallbackencoding')
775 fallback = lui.config('ui', 'fallbackencoding')
776 if fallback:
776 if fallback:
777 encoding.fallbackencoding = fallback
777 encoding.fallbackencoding = fallback
778
778
779 fullargs = args
779 fullargs = args
780 cmd, func, args, options, cmdoptions = _parse(lui, args)
780 cmd, func, args, options, cmdoptions = _parse(lui, args)
781
781
782 if options["config"]:
782 if options["config"]:
783 raise util.Abort(_("option --config may not be abbreviated!"))
783 raise util.Abort(_("option --config may not be abbreviated!"))
784 if options["cwd"]:
784 if options["cwd"]:
785 raise util.Abort(_("option --cwd may not be abbreviated!"))
785 raise util.Abort(_("option --cwd may not be abbreviated!"))
786 if options["repository"]:
786 if options["repository"]:
787 raise util.Abort(_(
787 raise util.Abort(_(
788 "option -R has to be separated from other options (e.g. not -qR) "
788 "option -R has to be separated from other options (e.g. not -qR) "
789 "and --repository may only be abbreviated as --repo!"))
789 "and --repository may only be abbreviated as --repo!"))
790
790
791 if options["encoding"]:
791 if options["encoding"]:
792 encoding.encoding = options["encoding"]
792 encoding.encoding = options["encoding"]
793 if options["encodingmode"]:
793 if options["encodingmode"]:
794 encoding.encodingmode = options["encodingmode"]
794 encoding.encodingmode = options["encodingmode"]
795 if options["time"]:
795 if options["time"]:
796 def get_times():
796 def get_times():
797 t = os.times()
797 t = os.times()
798 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
798 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
799 t = (t[0], t[1], t[2], t[3], time.clock())
799 t = (t[0], t[1], t[2], t[3], time.clock())
800 return t
800 return t
801 s = get_times()
801 s = get_times()
802 def print_time():
802 def print_time():
803 t = get_times()
803 t = get_times()
804 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
804 ui.warn(_("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
805 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
805 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
806 atexit.register(print_time)
806 atexit.register(print_time)
807
807
808 uis = set([ui, lui])
808 uis = set([ui, lui])
809
809
810 if req.repo:
810 if req.repo:
811 uis.add(req.repo.ui)
811 uis.add(req.repo.ui)
812
812
813 if options['verbose'] or options['debug'] or options['quiet']:
813 if options['verbose'] or options['debug'] or options['quiet']:
814 for opt in ('verbose', 'debug', 'quiet'):
814 for opt in ('verbose', 'debug', 'quiet'):
815 val = str(bool(options[opt]))
815 val = str(bool(options[opt]))
816 for ui_ in uis:
816 for ui_ in uis:
817 ui_.setconfig('ui', opt, val, '--' + opt)
817 ui_.setconfig('ui', opt, val, '--' + opt)
818
818
819 if options['traceback']:
819 if options['traceback']:
820 for ui_ in uis:
820 for ui_ in uis:
821 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
821 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
822
822
823 if options['noninteractive']:
823 if options['noninteractive']:
824 for ui_ in uis:
824 for ui_ in uis:
825 ui_.setconfig('ui', 'interactive', 'off', '-y')
825 ui_.setconfig('ui', 'interactive', 'off', '-y')
826
826
827 if cmdoptions.get('insecure', False):
827 if cmdoptions.get('insecure', False):
828 for ui_ in uis:
828 for ui_ in uis:
829 ui_.setconfig('web', 'cacerts', '', '--insecure')
829 ui_.setconfig('web', 'cacerts', '!', '--insecure')
830
830
831 if options['version']:
831 if options['version']:
832 return commands.version_(ui)
832 return commands.version_(ui)
833 if options['help']:
833 if options['help']:
834 return commands.help_(ui, cmd, command=True)
834 return commands.help_(ui, cmd, command=True)
835 elif not cmd:
835 elif not cmd:
836 return commands.help_(ui, 'shortlist')
836 return commands.help_(ui, 'shortlist')
837
837
838 repo = None
838 repo = None
839 cmdpats = args[:]
839 cmdpats = args[:]
840 if cmd not in commands.norepo.split():
840 if cmd not in commands.norepo.split():
841 # use the repo from the request only if we don't have -R
841 # use the repo from the request only if we don't have -R
842 if not rpath and not cwd:
842 if not rpath and not cwd:
843 repo = req.repo
843 repo = req.repo
844
844
845 if repo:
845 if repo:
846 # set the descriptors of the repo ui to those of ui
846 # set the descriptors of the repo ui to those of ui
847 repo.ui.fin = ui.fin
847 repo.ui.fin = ui.fin
848 repo.ui.fout = ui.fout
848 repo.ui.fout = ui.fout
849 repo.ui.ferr = ui.ferr
849 repo.ui.ferr = ui.ferr
850 else:
850 else:
851 try:
851 try:
852 repo = hg.repository(ui, path=path)
852 repo = hg.repository(ui, path=path)
853 if not repo.local():
853 if not repo.local():
854 raise util.Abort(_("repository '%s' is not local") % path)
854 raise util.Abort(_("repository '%s' is not local") % path)
855 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
855 repo.ui.setconfig("bundle", "mainreporoot", repo.root, 'repo')
856 except error.RequirementError:
856 except error.RequirementError:
857 raise
857 raise
858 except error.RepoError:
858 except error.RepoError:
859 if cmd not in commands.optionalrepo.split():
859 if cmd not in commands.optionalrepo.split():
860 if (cmd in commands.inferrepo.split() and
860 if (cmd in commands.inferrepo.split() and
861 args and not path): # try to infer -R from command args
861 args and not path): # try to infer -R from command args
862 repos = map(cmdutil.findrepo, args)
862 repos = map(cmdutil.findrepo, args)
863 guess = repos[0]
863 guess = repos[0]
864 if guess and repos.count(guess) == len(repos):
864 if guess and repos.count(guess) == len(repos):
865 req.args = ['--repository', guess] + fullargs
865 req.args = ['--repository', guess] + fullargs
866 return _dispatch(req)
866 return _dispatch(req)
867 if not path:
867 if not path:
868 raise error.RepoError(_("no repository found in '%s'"
868 raise error.RepoError(_("no repository found in '%s'"
869 " (.hg not found)")
869 " (.hg not found)")
870 % os.getcwd())
870 % os.getcwd())
871 raise
871 raise
872 if repo:
872 if repo:
873 ui = repo.ui
873 ui = repo.ui
874 if options['hidden']:
874 if options['hidden']:
875 repo = repo.unfiltered()
875 repo = repo.unfiltered()
876 args.insert(0, repo)
876 args.insert(0, repo)
877 elif rpath:
877 elif rpath:
878 ui.warn(_("warning: --repository ignored\n"))
878 ui.warn(_("warning: --repository ignored\n"))
879
879
880 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
880 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
881 ui.log("command", '%s\n', msg)
881 ui.log("command", '%s\n', msg)
882 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
882 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
883 try:
883 try:
884 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
884 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
885 cmdpats, cmdoptions)
885 cmdpats, cmdoptions)
886 finally:
886 finally:
887 if repo and repo != req.repo:
887 if repo and repo != req.repo:
888 repo.close()
888 repo.close()
889
889
890 def lsprofile(ui, func, fp):
890 def lsprofile(ui, func, fp):
891 format = ui.config('profiling', 'format', default='text')
891 format = ui.config('profiling', 'format', default='text')
892 field = ui.config('profiling', 'sort', default='inlinetime')
892 field = ui.config('profiling', 'sort', default='inlinetime')
893 limit = ui.configint('profiling', 'limit', default=30)
893 limit = ui.configint('profiling', 'limit', default=30)
894 climit = ui.configint('profiling', 'nested', default=5)
894 climit = ui.configint('profiling', 'nested', default=5)
895
895
896 if format not in ['text', 'kcachegrind']:
896 if format not in ['text', 'kcachegrind']:
897 ui.warn(_("unrecognized profiling format '%s'"
897 ui.warn(_("unrecognized profiling format '%s'"
898 " - Ignored\n") % format)
898 " - Ignored\n") % format)
899 format = 'text'
899 format = 'text'
900
900
901 try:
901 try:
902 from mercurial import lsprof
902 from mercurial import lsprof
903 except ImportError:
903 except ImportError:
904 raise util.Abort(_(
904 raise util.Abort(_(
905 'lsprof not available - install from '
905 'lsprof not available - install from '
906 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
906 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
907 p = lsprof.Profiler()
907 p = lsprof.Profiler()
908 p.enable(subcalls=True)
908 p.enable(subcalls=True)
909 try:
909 try:
910 return func()
910 return func()
911 finally:
911 finally:
912 p.disable()
912 p.disable()
913
913
914 if format == 'kcachegrind':
914 if format == 'kcachegrind':
915 import lsprofcalltree
915 import lsprofcalltree
916 calltree = lsprofcalltree.KCacheGrind(p)
916 calltree = lsprofcalltree.KCacheGrind(p)
917 calltree.output(fp)
917 calltree.output(fp)
918 else:
918 else:
919 # format == 'text'
919 # format == 'text'
920 stats = lsprof.Stats(p.getstats())
920 stats = lsprof.Stats(p.getstats())
921 stats.sort(field)
921 stats.sort(field)
922 stats.pprint(limit=limit, file=fp, climit=climit)
922 stats.pprint(limit=limit, file=fp, climit=climit)
923
923
924 def statprofile(ui, func, fp):
924 def statprofile(ui, func, fp):
925 try:
925 try:
926 import statprof
926 import statprof
927 except ImportError:
927 except ImportError:
928 raise util.Abort(_(
928 raise util.Abort(_(
929 'statprof not available - install using "easy_install statprof"'))
929 'statprof not available - install using "easy_install statprof"'))
930
930
931 freq = ui.configint('profiling', 'freq', default=1000)
931 freq = ui.configint('profiling', 'freq', default=1000)
932 if freq > 0:
932 if freq > 0:
933 statprof.reset(freq)
933 statprof.reset(freq)
934 else:
934 else:
935 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
935 ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
936
936
937 statprof.start()
937 statprof.start()
938 try:
938 try:
939 return func()
939 return func()
940 finally:
940 finally:
941 statprof.stop()
941 statprof.stop()
942 statprof.display(fp)
942 statprof.display(fp)
943
943
944 def _runcommand(ui, options, cmd, cmdfunc):
944 def _runcommand(ui, options, cmd, cmdfunc):
945 def checkargs():
945 def checkargs():
946 try:
946 try:
947 return cmdfunc()
947 return cmdfunc()
948 except error.SignatureError:
948 except error.SignatureError:
949 raise error.CommandError(cmd, _("invalid arguments"))
949 raise error.CommandError(cmd, _("invalid arguments"))
950
950
951 if options['profile']:
951 if options['profile']:
952 profiler = os.getenv('HGPROF')
952 profiler = os.getenv('HGPROF')
953 if profiler is None:
953 if profiler is None:
954 profiler = ui.config('profiling', 'type', default='ls')
954 profiler = ui.config('profiling', 'type', default='ls')
955 if profiler not in ('ls', 'stat'):
955 if profiler not in ('ls', 'stat'):
956 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
956 ui.warn(_("unrecognized profiler '%s' - ignored\n") % profiler)
957 profiler = 'ls'
957 profiler = 'ls'
958
958
959 output = ui.config('profiling', 'output')
959 output = ui.config('profiling', 'output')
960
960
961 if output:
961 if output:
962 path = ui.expandpath(output)
962 path = ui.expandpath(output)
963 fp = open(path, 'wb')
963 fp = open(path, 'wb')
964 else:
964 else:
965 fp = sys.stderr
965 fp = sys.stderr
966
966
967 try:
967 try:
968 if profiler == 'ls':
968 if profiler == 'ls':
969 return lsprofile(ui, checkargs, fp)
969 return lsprofile(ui, checkargs, fp)
970 else:
970 else:
971 return statprofile(ui, checkargs, fp)
971 return statprofile(ui, checkargs, fp)
972 finally:
972 finally:
973 if output:
973 if output:
974 fp.close()
974 fp.close()
975 else:
975 else:
976 return checkargs()
976 return checkargs()
@@ -1,678 +1,680
1 # hg.py - repository classes for mercurial
1 # hg.py - repository classes for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from i18n import _
9 from i18n import _
10 from lock import release
10 from lock import release
11 from node import nullid
11 from node import nullid
12
12
13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
13 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
14 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
15 import cmdutil, discovery, repoview, exchange
15 import cmdutil, discovery, repoview, exchange
16 import ui as uimod
16 import ui as uimod
17 import merge as mergemod
17 import merge as mergemod
18 import verify as verifymod
18 import verify as verifymod
19 import errno, os, shutil
19 import errno, os, shutil
20
20
21 def _local(path):
21 def _local(path):
22 path = util.expandpath(util.urllocalpath(path))
22 path = util.expandpath(util.urllocalpath(path))
23 return (os.path.isfile(path) and bundlerepo or localrepo)
23 return (os.path.isfile(path) and bundlerepo or localrepo)
24
24
25 def addbranchrevs(lrepo, other, branches, revs):
25 def addbranchrevs(lrepo, other, branches, revs):
26 peer = other.peer() # a courtesy to callers using a localrepo for other
26 peer = other.peer() # a courtesy to callers using a localrepo for other
27 hashbranch, branches = branches
27 hashbranch, branches = branches
28 if not hashbranch and not branches:
28 if not hashbranch and not branches:
29 x = revs or None
29 x = revs or None
30 if util.safehasattr(revs, 'first'):
30 if util.safehasattr(revs, 'first'):
31 y = revs.first()
31 y = revs.first()
32 elif revs:
32 elif revs:
33 y = revs[0]
33 y = revs[0]
34 else:
34 else:
35 y = None
35 y = None
36 return x, y
36 return x, y
37 revs = revs and list(revs) or []
37 revs = revs and list(revs) or []
38 if not peer.capable('branchmap'):
38 if not peer.capable('branchmap'):
39 if branches:
39 if branches:
40 raise util.Abort(_("remote branch lookup not supported"))
40 raise util.Abort(_("remote branch lookup not supported"))
41 revs.append(hashbranch)
41 revs.append(hashbranch)
42 return revs, revs[0]
42 return revs, revs[0]
43 branchmap = peer.branchmap()
43 branchmap = peer.branchmap()
44
44
45 def primary(branch):
45 def primary(branch):
46 if branch == '.':
46 if branch == '.':
47 if not lrepo:
47 if not lrepo:
48 raise util.Abort(_("dirstate branch not accessible"))
48 raise util.Abort(_("dirstate branch not accessible"))
49 branch = lrepo.dirstate.branch()
49 branch = lrepo.dirstate.branch()
50 if branch in branchmap:
50 if branch in branchmap:
51 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
51 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
52 return True
52 return True
53 else:
53 else:
54 return False
54 return False
55
55
56 for branch in branches:
56 for branch in branches:
57 if not primary(branch):
57 if not primary(branch):
58 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
58 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
59 if hashbranch:
59 if hashbranch:
60 if not primary(hashbranch):
60 if not primary(hashbranch):
61 revs.append(hashbranch)
61 revs.append(hashbranch)
62 return revs, revs[0]
62 return revs, revs[0]
63
63
64 def parseurl(path, branches=None):
64 def parseurl(path, branches=None):
65 '''parse url#branch, returning (url, (branch, branches))'''
65 '''parse url#branch, returning (url, (branch, branches))'''
66
66
67 u = util.url(path)
67 u = util.url(path)
68 branch = None
68 branch = None
69 if u.fragment:
69 if u.fragment:
70 branch = u.fragment
70 branch = u.fragment
71 u.fragment = None
71 u.fragment = None
72 return str(u), (branch, branches or [])
72 return str(u), (branch, branches or [])
73
73
74 schemes = {
74 schemes = {
75 'bundle': bundlerepo,
75 'bundle': bundlerepo,
76 'union': unionrepo,
76 'union': unionrepo,
77 'file': _local,
77 'file': _local,
78 'http': httppeer,
78 'http': httppeer,
79 'https': httppeer,
79 'https': httppeer,
80 'ssh': sshpeer,
80 'ssh': sshpeer,
81 'static-http': statichttprepo,
81 'static-http': statichttprepo,
82 }
82 }
83
83
84 def _peerlookup(path):
84 def _peerlookup(path):
85 u = util.url(path)
85 u = util.url(path)
86 scheme = u.scheme or 'file'
86 scheme = u.scheme or 'file'
87 thing = schemes.get(scheme) or schemes['file']
87 thing = schemes.get(scheme) or schemes['file']
88 try:
88 try:
89 return thing(path)
89 return thing(path)
90 except TypeError:
90 except TypeError:
91 return thing
91 return thing
92
92
93 def islocal(repo):
93 def islocal(repo):
94 '''return true if repo (or path pointing to repo) is local'''
94 '''return true if repo (or path pointing to repo) is local'''
95 if isinstance(repo, str):
95 if isinstance(repo, str):
96 try:
96 try:
97 return _peerlookup(repo).islocal(repo)
97 return _peerlookup(repo).islocal(repo)
98 except AttributeError:
98 except AttributeError:
99 return False
99 return False
100 return repo.local()
100 return repo.local()
101
101
102 def openpath(ui, path):
102 def openpath(ui, path):
103 '''open path with open if local, url.open if remote'''
103 '''open path with open if local, url.open if remote'''
104 pathurl = util.url(path, parsequery=False, parsefragment=False)
104 pathurl = util.url(path, parsequery=False, parsefragment=False)
105 if pathurl.islocal():
105 if pathurl.islocal():
106 return util.posixfile(pathurl.localpath(), 'rb')
106 return util.posixfile(pathurl.localpath(), 'rb')
107 else:
107 else:
108 return url.open(ui, path)
108 return url.open(ui, path)
109
109
110 # a list of (ui, repo) functions called for wire peer initialization
110 # a list of (ui, repo) functions called for wire peer initialization
111 wirepeersetupfuncs = []
111 wirepeersetupfuncs = []
112
112
113 def _peerorrepo(ui, path, create=False):
113 def _peerorrepo(ui, path, create=False):
114 """return a repository object for the specified path"""
114 """return a repository object for the specified path"""
115 obj = _peerlookup(path).instance(ui, path, create)
115 obj = _peerlookup(path).instance(ui, path, create)
116 ui = getattr(obj, "ui", ui)
116 ui = getattr(obj, "ui", ui)
117 for name, module in extensions.extensions(ui):
117 for name, module in extensions.extensions(ui):
118 hook = getattr(module, 'reposetup', None)
118 hook = getattr(module, 'reposetup', None)
119 if hook:
119 if hook:
120 hook(ui, obj)
120 hook(ui, obj)
121 if not obj.local():
121 if not obj.local():
122 for f in wirepeersetupfuncs:
122 for f in wirepeersetupfuncs:
123 f(ui, obj)
123 f(ui, obj)
124 return obj
124 return obj
125
125
126 def repository(ui, path='', create=False):
126 def repository(ui, path='', create=False):
127 """return a repository object for the specified path"""
127 """return a repository object for the specified path"""
128 peer = _peerorrepo(ui, path, create)
128 peer = _peerorrepo(ui, path, create)
129 repo = peer.local()
129 repo = peer.local()
130 if not repo:
130 if not repo:
131 raise util.Abort(_("repository '%s' is not local") %
131 raise util.Abort(_("repository '%s' is not local") %
132 (path or peer.url()))
132 (path or peer.url()))
133 return repo.filtered('visible')
133 return repo.filtered('visible')
134
134
135 def peer(uiorrepo, opts, path, create=False):
135 def peer(uiorrepo, opts, path, create=False):
136 '''return a repository peer for the specified path'''
136 '''return a repository peer for the specified path'''
137 rui = remoteui(uiorrepo, opts)
137 rui = remoteui(uiorrepo, opts)
138 return _peerorrepo(rui, path, create).peer()
138 return _peerorrepo(rui, path, create).peer()
139
139
140 def defaultdest(source):
140 def defaultdest(source):
141 '''return default destination of clone if none is given
141 '''return default destination of clone if none is given
142
142
143 >>> defaultdest('foo')
143 >>> defaultdest('foo')
144 'foo'
144 'foo'
145 >>> defaultdest('/foo/bar')
145 >>> defaultdest('/foo/bar')
146 'bar'
146 'bar'
147 >>> defaultdest('/')
147 >>> defaultdest('/')
148 ''
148 ''
149 >>> defaultdest('')
149 >>> defaultdest('')
150 ''
150 ''
151 >>> defaultdest('http://example.org/')
151 >>> defaultdest('http://example.org/')
152 ''
152 ''
153 >>> defaultdest('http://example.org/foo/')
153 >>> defaultdest('http://example.org/foo/')
154 'foo'
154 'foo'
155 '''
155 '''
156 path = util.url(source).path
156 path = util.url(source).path
157 if not path:
157 if not path:
158 return ''
158 return ''
159 return os.path.basename(os.path.normpath(path))
159 return os.path.basename(os.path.normpath(path))
160
160
161 def share(ui, source, dest=None, update=True, bookmarks=True):
161 def share(ui, source, dest=None, update=True, bookmarks=True):
162 '''create a shared repository'''
162 '''create a shared repository'''
163
163
164 if not islocal(source):
164 if not islocal(source):
165 raise util.Abort(_('can only share local repositories'))
165 raise util.Abort(_('can only share local repositories'))
166
166
167 if not dest:
167 if not dest:
168 dest = defaultdest(source)
168 dest = defaultdest(source)
169 else:
169 else:
170 dest = ui.expandpath(dest)
170 dest = ui.expandpath(dest)
171
171
172 if isinstance(source, str):
172 if isinstance(source, str):
173 origsource = ui.expandpath(source)
173 origsource = ui.expandpath(source)
174 source, branches = parseurl(origsource)
174 source, branches = parseurl(origsource)
175 srcrepo = repository(ui, source)
175 srcrepo = repository(ui, source)
176 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
176 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
177 else:
177 else:
178 srcrepo = source.local()
178 srcrepo = source.local()
179 origsource = source = srcrepo.url()
179 origsource = source = srcrepo.url()
180 checkout = None
180 checkout = None
181
181
182 sharedpath = srcrepo.sharedpath # if our source is already sharing
182 sharedpath = srcrepo.sharedpath # if our source is already sharing
183
183
184 destwvfs = scmutil.vfs(dest, realpath=True)
184 destwvfs = scmutil.vfs(dest, realpath=True)
185 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
185 destvfs = scmutil.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
186
186
187 if destvfs.lexists():
187 if destvfs.lexists():
188 raise util.Abort(_('destination already exists'))
188 raise util.Abort(_('destination already exists'))
189
189
190 if not destwvfs.isdir():
190 if not destwvfs.isdir():
191 destwvfs.mkdir()
191 destwvfs.mkdir()
192 destvfs.makedir()
192 destvfs.makedir()
193
193
194 requirements = ''
194 requirements = ''
195 try:
195 try:
196 requirements = srcrepo.vfs.read('requires')
196 requirements = srcrepo.vfs.read('requires')
197 except IOError, inst:
197 except IOError, inst:
198 if inst.errno != errno.ENOENT:
198 if inst.errno != errno.ENOENT:
199 raise
199 raise
200
200
201 requirements += 'shared\n'
201 requirements += 'shared\n'
202 destvfs.write('requires', requirements)
202 destvfs.write('requires', requirements)
203 destvfs.write('sharedpath', sharedpath)
203 destvfs.write('sharedpath', sharedpath)
204
204
205 r = repository(ui, destwvfs.base)
205 r = repository(ui, destwvfs.base)
206
206
207 default = srcrepo.ui.config('paths', 'default')
207 default = srcrepo.ui.config('paths', 'default')
208 if default:
208 if default:
209 fp = r.vfs("hgrc", "w", text=True)
209 fp = r.vfs("hgrc", "w", text=True)
210 fp.write("[paths]\n")
210 fp.write("[paths]\n")
211 fp.write("default = %s\n" % default)
211 fp.write("default = %s\n" % default)
212 fp.close()
212 fp.close()
213
213
214 if update:
214 if update:
215 r.ui.status(_("updating working directory\n"))
215 r.ui.status(_("updating working directory\n"))
216 if update is not True:
216 if update is not True:
217 checkout = update
217 checkout = update
218 for test in (checkout, 'default', 'tip'):
218 for test in (checkout, 'default', 'tip'):
219 if test is None:
219 if test is None:
220 continue
220 continue
221 try:
221 try:
222 uprev = r.lookup(test)
222 uprev = r.lookup(test)
223 break
223 break
224 except error.RepoLookupError:
224 except error.RepoLookupError:
225 continue
225 continue
226 _update(r, uprev)
226 _update(r, uprev)
227
227
228 if bookmarks:
228 if bookmarks:
229 fp = r.vfs('shared', 'w')
229 fp = r.vfs('shared', 'w')
230 fp.write('bookmarks\n')
230 fp.write('bookmarks\n')
231 fp.close()
231 fp.close()
232
232
233 def copystore(ui, srcrepo, destpath):
233 def copystore(ui, srcrepo, destpath):
234 '''copy files from store of srcrepo in destpath
234 '''copy files from store of srcrepo in destpath
235
235
236 returns destlock
236 returns destlock
237 '''
237 '''
238 destlock = None
238 destlock = None
239 try:
239 try:
240 hardlink = None
240 hardlink = None
241 num = 0
241 num = 0
242 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
242 srcpublishing = srcrepo.ui.configbool('phases', 'publish', True)
243 srcvfs = scmutil.vfs(srcrepo.sharedpath)
243 srcvfs = scmutil.vfs(srcrepo.sharedpath)
244 dstvfs = scmutil.vfs(destpath)
244 dstvfs = scmutil.vfs(destpath)
245 for f in srcrepo.store.copylist():
245 for f in srcrepo.store.copylist():
246 if srcpublishing and f.endswith('phaseroots'):
246 if srcpublishing and f.endswith('phaseroots'):
247 continue
247 continue
248 dstbase = os.path.dirname(f)
248 dstbase = os.path.dirname(f)
249 if dstbase and not dstvfs.exists(dstbase):
249 if dstbase and not dstvfs.exists(dstbase):
250 dstvfs.mkdir(dstbase)
250 dstvfs.mkdir(dstbase)
251 if srcvfs.exists(f):
251 if srcvfs.exists(f):
252 if f.endswith('data'):
252 if f.endswith('data'):
253 # 'dstbase' may be empty (e.g. revlog format 0)
253 # 'dstbase' may be empty (e.g. revlog format 0)
254 lockfile = os.path.join(dstbase, "lock")
254 lockfile = os.path.join(dstbase, "lock")
255 # lock to avoid premature writing to the target
255 # lock to avoid premature writing to the target
256 destlock = lock.lock(dstvfs, lockfile)
256 destlock = lock.lock(dstvfs, lockfile)
257 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
257 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
258 hardlink)
258 hardlink)
259 num += n
259 num += n
260 if hardlink:
260 if hardlink:
261 ui.debug("linked %d files\n" % num)
261 ui.debug("linked %d files\n" % num)
262 else:
262 else:
263 ui.debug("copied %d files\n" % num)
263 ui.debug("copied %d files\n" % num)
264 return destlock
264 return destlock
265 except: # re-raises
265 except: # re-raises
266 release(destlock)
266 release(destlock)
267 raise
267 raise
268
268
269 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
269 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
270 update=True, stream=False, branch=None):
270 update=True, stream=False, branch=None):
271 """Make a copy of an existing repository.
271 """Make a copy of an existing repository.
272
272
273 Create a copy of an existing repository in a new directory. The
273 Create a copy of an existing repository in a new directory. The
274 source and destination are URLs, as passed to the repository
274 source and destination are URLs, as passed to the repository
275 function. Returns a pair of repository peers, the source and
275 function. Returns a pair of repository peers, the source and
276 newly created destination.
276 newly created destination.
277
277
278 The location of the source is added to the new repository's
278 The location of the source is added to the new repository's
279 .hg/hgrc file, as the default to be used for future pulls and
279 .hg/hgrc file, as the default to be used for future pulls and
280 pushes.
280 pushes.
281
281
282 If an exception is raised, the partly cloned/updated destination
282 If an exception is raised, the partly cloned/updated destination
283 repository will be deleted.
283 repository will be deleted.
284
284
285 Arguments:
285 Arguments:
286
286
287 source: repository object or URL
287 source: repository object or URL
288
288
289 dest: URL of destination repository to create (defaults to base
289 dest: URL of destination repository to create (defaults to base
290 name of source repository)
290 name of source repository)
291
291
292 pull: always pull from source repository, even in local case or if the
292 pull: always pull from source repository, even in local case or if the
293 server prefers streaming
293 server prefers streaming
294
294
295 stream: stream raw data uncompressed from repository (fast over
295 stream: stream raw data uncompressed from repository (fast over
296 LAN, slow over WAN)
296 LAN, slow over WAN)
297
297
298 rev: revision to clone up to (implies pull=True)
298 rev: revision to clone up to (implies pull=True)
299
299
300 update: update working directory after clone completes, if
300 update: update working directory after clone completes, if
301 destination is local repository (True means update to default rev,
301 destination is local repository (True means update to default rev,
302 anything else is treated as a revision)
302 anything else is treated as a revision)
303
303
304 branch: branches to clone
304 branch: branches to clone
305 """
305 """
306
306
307 if isinstance(source, str):
307 if isinstance(source, str):
308 origsource = ui.expandpath(source)
308 origsource = ui.expandpath(source)
309 source, branch = parseurl(origsource, branch)
309 source, branch = parseurl(origsource, branch)
310 srcpeer = peer(ui, peeropts, source)
310 srcpeer = peer(ui, peeropts, source)
311 else:
311 else:
312 srcpeer = source.peer() # in case we were called with a localrepo
312 srcpeer = source.peer() # in case we were called with a localrepo
313 branch = (None, branch or [])
313 branch = (None, branch or [])
314 origsource = source = srcpeer.url()
314 origsource = source = srcpeer.url()
315 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
315 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
316
316
317 if dest is None:
317 if dest is None:
318 dest = defaultdest(source)
318 dest = defaultdest(source)
319 if dest:
319 if dest:
320 ui.status(_("destination directory: %s\n") % dest)
320 ui.status(_("destination directory: %s\n") % dest)
321 else:
321 else:
322 dest = ui.expandpath(dest)
322 dest = ui.expandpath(dest)
323
323
324 dest = util.urllocalpath(dest)
324 dest = util.urllocalpath(dest)
325 source = util.urllocalpath(source)
325 source = util.urllocalpath(source)
326
326
327 if not dest:
327 if not dest:
328 raise util.Abort(_("empty destination path is not valid"))
328 raise util.Abort(_("empty destination path is not valid"))
329
329
330 destvfs = scmutil.vfs(dest, expandpath=True)
330 destvfs = scmutil.vfs(dest, expandpath=True)
331 if destvfs.lexists():
331 if destvfs.lexists():
332 if not destvfs.isdir():
332 if not destvfs.isdir():
333 raise util.Abort(_("destination '%s' already exists") % dest)
333 raise util.Abort(_("destination '%s' already exists") % dest)
334 elif destvfs.listdir():
334 elif destvfs.listdir():
335 raise util.Abort(_("destination '%s' is not empty") % dest)
335 raise util.Abort(_("destination '%s' is not empty") % dest)
336
336
337 srclock = destlock = cleandir = None
337 srclock = destlock = cleandir = None
338 srcrepo = srcpeer.local()
338 srcrepo = srcpeer.local()
339 try:
339 try:
340 abspath = origsource
340 abspath = origsource
341 if islocal(origsource):
341 if islocal(origsource):
342 abspath = os.path.abspath(util.urllocalpath(origsource))
342 abspath = os.path.abspath(util.urllocalpath(origsource))
343
343
344 if islocal(dest):
344 if islocal(dest):
345 cleandir = dest
345 cleandir = dest
346
346
347 copy = False
347 copy = False
348 if (srcrepo and srcrepo.cancopy() and islocal(dest)
348 if (srcrepo and srcrepo.cancopy() and islocal(dest)
349 and not phases.hassecret(srcrepo)):
349 and not phases.hassecret(srcrepo)):
350 copy = not pull and not rev
350 copy = not pull and not rev
351
351
352 if copy:
352 if copy:
353 try:
353 try:
354 # we use a lock here because if we race with commit, we
354 # we use a lock here because if we race with commit, we
355 # can end up with extra data in the cloned revlogs that's
355 # can end up with extra data in the cloned revlogs that's
356 # not pointed to by changesets, thus causing verify to
356 # not pointed to by changesets, thus causing verify to
357 # fail
357 # fail
358 srclock = srcrepo.lock(wait=False)
358 srclock = srcrepo.lock(wait=False)
359 except error.LockError:
359 except error.LockError:
360 copy = False
360 copy = False
361
361
362 if copy:
362 if copy:
363 srcrepo.hook('preoutgoing', throw=True, source='clone')
363 srcrepo.hook('preoutgoing', throw=True, source='clone')
364 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
364 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
365 if not os.path.exists(dest):
365 if not os.path.exists(dest):
366 os.mkdir(dest)
366 os.mkdir(dest)
367 else:
367 else:
368 # only clean up directories we create ourselves
368 # only clean up directories we create ourselves
369 cleandir = hgdir
369 cleandir = hgdir
370 try:
370 try:
371 destpath = hgdir
371 destpath = hgdir
372 util.makedir(destpath, notindexed=True)
372 util.makedir(destpath, notindexed=True)
373 except OSError, inst:
373 except OSError, inst:
374 if inst.errno == errno.EEXIST:
374 if inst.errno == errno.EEXIST:
375 cleandir = None
375 cleandir = None
376 raise util.Abort(_("destination '%s' already exists")
376 raise util.Abort(_("destination '%s' already exists")
377 % dest)
377 % dest)
378 raise
378 raise
379
379
380 destlock = copystore(ui, srcrepo, destpath)
380 destlock = copystore(ui, srcrepo, destpath)
381 # copy bookmarks over
381 # copy bookmarks over
382 srcbookmarks = srcrepo.join('bookmarks')
382 srcbookmarks = srcrepo.join('bookmarks')
383 dstbookmarks = os.path.join(destpath, 'bookmarks')
383 dstbookmarks = os.path.join(destpath, 'bookmarks')
384 if os.path.exists(srcbookmarks):
384 if os.path.exists(srcbookmarks):
385 util.copyfile(srcbookmarks, dstbookmarks)
385 util.copyfile(srcbookmarks, dstbookmarks)
386
386
387 # Recomputing branch cache might be slow on big repos,
387 # Recomputing branch cache might be slow on big repos,
388 # so just copy it
388 # so just copy it
389 def copybranchcache(fname):
389 def copybranchcache(fname):
390 srcbranchcache = srcrepo.join('cache/%s' % fname)
390 srcbranchcache = srcrepo.join('cache/%s' % fname)
391 dstbranchcache = os.path.join(dstcachedir, fname)
391 dstbranchcache = os.path.join(dstcachedir, fname)
392 if os.path.exists(srcbranchcache):
392 if os.path.exists(srcbranchcache):
393 if not os.path.exists(dstcachedir):
393 if not os.path.exists(dstcachedir):
394 os.mkdir(dstcachedir)
394 os.mkdir(dstcachedir)
395 util.copyfile(srcbranchcache, dstbranchcache)
395 util.copyfile(srcbranchcache, dstbranchcache)
396
396
397 dstcachedir = os.path.join(destpath, 'cache')
397 dstcachedir = os.path.join(destpath, 'cache')
398 # In local clones we're copying all nodes, not just served
398 # In local clones we're copying all nodes, not just served
399 # ones. Therefore copy all branch caches over.
399 # ones. Therefore copy all branch caches over.
400 copybranchcache('branch2')
400 copybranchcache('branch2')
401 for cachename in repoview.filtertable:
401 for cachename in repoview.filtertable:
402 copybranchcache('branch2-%s' % cachename)
402 copybranchcache('branch2-%s' % cachename)
403
403
404 # we need to re-init the repo after manually copying the data
404 # we need to re-init the repo after manually copying the data
405 # into it
405 # into it
406 destpeer = peer(srcrepo, peeropts, dest)
406 destpeer = peer(srcrepo, peeropts, dest)
407 srcrepo.hook('outgoing', source='clone',
407 srcrepo.hook('outgoing', source='clone',
408 node=node.hex(node.nullid))
408 node=node.hex(node.nullid))
409 else:
409 else:
410 try:
410 try:
411 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
411 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
412 # only pass ui when no srcrepo
412 # only pass ui when no srcrepo
413 except OSError, inst:
413 except OSError, inst:
414 if inst.errno == errno.EEXIST:
414 if inst.errno == errno.EEXIST:
415 cleandir = None
415 cleandir = None
416 raise util.Abort(_("destination '%s' already exists")
416 raise util.Abort(_("destination '%s' already exists")
417 % dest)
417 % dest)
418 raise
418 raise
419
419
420 revs = None
420 revs = None
421 if rev:
421 if rev:
422 if not srcpeer.capable('lookup'):
422 if not srcpeer.capable('lookup'):
423 raise util.Abort(_("src repository does not support "
423 raise util.Abort(_("src repository does not support "
424 "revision lookup and so doesn't "
424 "revision lookup and so doesn't "
425 "support clone by revision"))
425 "support clone by revision"))
426 revs = [srcpeer.lookup(r) for r in rev]
426 revs = [srcpeer.lookup(r) for r in rev]
427 checkout = revs[0]
427 checkout = revs[0]
428 if destpeer.local():
428 if destpeer.local():
429 if not stream:
429 if not stream:
430 if pull:
430 if pull:
431 stream = False
431 stream = False
432 else:
432 else:
433 stream = None
433 stream = None
434 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
434 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
435 elif srcrepo:
435 elif srcrepo:
436 exchange.push(srcrepo, destpeer, revs=revs,
436 exchange.push(srcrepo, destpeer, revs=revs,
437 bookmarks=srcrepo._bookmarks.keys())
437 bookmarks=srcrepo._bookmarks.keys())
438 else:
438 else:
439 raise util.Abort(_("clone from remote to remote not supported"))
439 raise util.Abort(_("clone from remote to remote not supported"))
440
440
441 cleandir = None
441 cleandir = None
442
442
443 destrepo = destpeer.local()
443 destrepo = destpeer.local()
444 if destrepo:
444 if destrepo:
445 template = uimod.samplehgrcs['cloned']
445 template = uimod.samplehgrcs['cloned']
446 fp = destrepo.vfs("hgrc", "w", text=True)
446 fp = destrepo.vfs("hgrc", "w", text=True)
447 u = util.url(abspath)
447 u = util.url(abspath)
448 u.passwd = None
448 u.passwd = None
449 defaulturl = str(u)
449 defaulturl = str(u)
450 fp.write(template % defaulturl)
450 fp.write(template % defaulturl)
451 fp.close()
451 fp.close()
452
452
453 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
453 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
454
454
455 if update:
455 if update:
456 if update is not True:
456 if update is not True:
457 checkout = srcpeer.lookup(update)
457 checkout = srcpeer.lookup(update)
458 uprev = None
458 uprev = None
459 status = None
459 status = None
460 if checkout is not None:
460 if checkout is not None:
461 try:
461 try:
462 uprev = destrepo.lookup(checkout)
462 uprev = destrepo.lookup(checkout)
463 except error.RepoLookupError:
463 except error.RepoLookupError:
464 pass
464 pass
465 if uprev is None:
465 if uprev is None:
466 try:
466 try:
467 uprev = destrepo._bookmarks['@']
467 uprev = destrepo._bookmarks['@']
468 update = '@'
468 update = '@'
469 bn = destrepo[uprev].branch()
469 bn = destrepo[uprev].branch()
470 if bn == 'default':
470 if bn == 'default':
471 status = _("updating to bookmark @\n")
471 status = _("updating to bookmark @\n")
472 else:
472 else:
473 status = (_("updating to bookmark @ on branch %s\n")
473 status = (_("updating to bookmark @ on branch %s\n")
474 % bn)
474 % bn)
475 except KeyError:
475 except KeyError:
476 try:
476 try:
477 uprev = destrepo.branchtip('default')
477 uprev = destrepo.branchtip('default')
478 except error.RepoLookupError:
478 except error.RepoLookupError:
479 uprev = destrepo.lookup('tip')
479 uprev = destrepo.lookup('tip')
480 if not status:
480 if not status:
481 bn = destrepo[uprev].branch()
481 bn = destrepo[uprev].branch()
482 status = _("updating to branch %s\n") % bn
482 status = _("updating to branch %s\n") % bn
483 destrepo.ui.status(status)
483 destrepo.ui.status(status)
484 _update(destrepo, uprev)
484 _update(destrepo, uprev)
485 if update in destrepo._bookmarks:
485 if update in destrepo._bookmarks:
486 bookmarks.setcurrent(destrepo, update)
486 bookmarks.setcurrent(destrepo, update)
487 finally:
487 finally:
488 release(srclock, destlock)
488 release(srclock, destlock)
489 if cleandir is not None:
489 if cleandir is not None:
490 shutil.rmtree(cleandir, True)
490 shutil.rmtree(cleandir, True)
491 if srcpeer is not None:
491 if srcpeer is not None:
492 srcpeer.close()
492 srcpeer.close()
493 return srcpeer, destpeer
493 return srcpeer, destpeer
494
494
495 def _showstats(repo, stats):
495 def _showstats(repo, stats):
496 repo.ui.status(_("%d files updated, %d files merged, "
496 repo.ui.status(_("%d files updated, %d files merged, "
497 "%d files removed, %d files unresolved\n") % stats)
497 "%d files removed, %d files unresolved\n") % stats)
498
498
499 def updaterepo(repo, node, overwrite):
499 def updaterepo(repo, node, overwrite):
500 """Update the working directory to node.
500 """Update the working directory to node.
501
501
502 When overwrite is set, changes are clobbered, merged else
502 When overwrite is set, changes are clobbered, merged else
503
503
504 returns stats (see pydoc mercurial.merge.applyupdates)"""
504 returns stats (see pydoc mercurial.merge.applyupdates)"""
505 return mergemod.update(repo, node, False, overwrite, None,
505 return mergemod.update(repo, node, False, overwrite, None,
506 labels=['working copy', 'destination'])
506 labels=['working copy', 'destination'])
507
507
508 def update(repo, node):
508 def update(repo, node):
509 """update the working directory to node, merging linear changes"""
509 """update the working directory to node, merging linear changes"""
510 stats = updaterepo(repo, node, False)
510 stats = updaterepo(repo, node, False)
511 _showstats(repo, stats)
511 _showstats(repo, stats)
512 if stats[3]:
512 if stats[3]:
513 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
513 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
514 return stats[3] > 0
514 return stats[3] > 0
515
515
516 # naming conflict in clone()
516 # naming conflict in clone()
517 _update = update
517 _update = update
518
518
519 def clean(repo, node, show_stats=True):
519 def clean(repo, node, show_stats=True):
520 """forcibly switch the working directory to node, clobbering changes"""
520 """forcibly switch the working directory to node, clobbering changes"""
521 stats = updaterepo(repo, node, True)
521 stats = updaterepo(repo, node, True)
522 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
522 util.unlinkpath(repo.join('graftstate'), ignoremissing=True)
523 if show_stats:
523 if show_stats:
524 _showstats(repo, stats)
524 _showstats(repo, stats)
525 return stats[3] > 0
525 return stats[3] > 0
526
526
527 def merge(repo, node, force=None, remind=True):
527 def merge(repo, node, force=None, remind=True):
528 """Branch merge with node, resolving changes. Return true if any
528 """Branch merge with node, resolving changes. Return true if any
529 unresolved conflicts."""
529 unresolved conflicts."""
530 stats = mergemod.update(repo, node, True, force, False)
530 stats = mergemod.update(repo, node, True, force, False)
531 _showstats(repo, stats)
531 _showstats(repo, stats)
532 if stats[3]:
532 if stats[3]:
533 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
533 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
534 "or 'hg update -C .' to abandon\n"))
534 "or 'hg update -C .' to abandon\n"))
535 elif remind:
535 elif remind:
536 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
536 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
537 return stats[3] > 0
537 return stats[3] > 0
538
538
539 def _incoming(displaychlist, subreporecurse, ui, repo, source,
539 def _incoming(displaychlist, subreporecurse, ui, repo, source,
540 opts, buffered=False):
540 opts, buffered=False):
541 """
541 """
542 Helper for incoming / gincoming.
542 Helper for incoming / gincoming.
543 displaychlist gets called with
543 displaychlist gets called with
544 (remoterepo, incomingchangesetlist, displayer) parameters,
544 (remoterepo, incomingchangesetlist, displayer) parameters,
545 and is supposed to contain only code that can't be unified.
545 and is supposed to contain only code that can't be unified.
546 """
546 """
547 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
547 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
548 other = peer(repo, opts, source)
548 other = peer(repo, opts, source)
549 ui.status(_('comparing with %s\n') % util.hidepassword(source))
549 ui.status(_('comparing with %s\n') % util.hidepassword(source))
550 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
550 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
551
551
552 if revs:
552 if revs:
553 revs = [other.lookup(rev) for rev in revs]
553 revs = [other.lookup(rev) for rev in revs]
554 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
554 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
555 revs, opts["bundle"], opts["force"])
555 revs, opts["bundle"], opts["force"])
556 try:
556 try:
557 if not chlist:
557 if not chlist:
558 ui.status(_("no changes found\n"))
558 ui.status(_("no changes found\n"))
559 return subreporecurse()
559 return subreporecurse()
560
560
561 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
561 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
562 displaychlist(other, chlist, displayer)
562 displaychlist(other, chlist, displayer)
563 displayer.close()
563 displayer.close()
564 finally:
564 finally:
565 cleanupfn()
565 cleanupfn()
566 subreporecurse()
566 subreporecurse()
567 return 0 # exit code is zero since we found incoming changes
567 return 0 # exit code is zero since we found incoming changes
568
568
569 def incoming(ui, repo, source, opts):
569 def incoming(ui, repo, source, opts):
570 def subreporecurse():
570 def subreporecurse():
571 ret = 1
571 ret = 1
572 if opts.get('subrepos'):
572 if opts.get('subrepos'):
573 ctx = repo[None]
573 ctx = repo[None]
574 for subpath in sorted(ctx.substate):
574 for subpath in sorted(ctx.substate):
575 sub = ctx.sub(subpath)
575 sub = ctx.sub(subpath)
576 ret = min(ret, sub.incoming(ui, source, opts))
576 ret = min(ret, sub.incoming(ui, source, opts))
577 return ret
577 return ret
578
578
579 def display(other, chlist, displayer):
579 def display(other, chlist, displayer):
580 limit = cmdutil.loglimit(opts)
580 limit = cmdutil.loglimit(opts)
581 if opts.get('newest_first'):
581 if opts.get('newest_first'):
582 chlist.reverse()
582 chlist.reverse()
583 count = 0
583 count = 0
584 for n in chlist:
584 for n in chlist:
585 if limit is not None and count >= limit:
585 if limit is not None and count >= limit:
586 break
586 break
587 parents = [p for p in other.changelog.parents(n) if p != nullid]
587 parents = [p for p in other.changelog.parents(n) if p != nullid]
588 if opts.get('no_merges') and len(parents) == 2:
588 if opts.get('no_merges') and len(parents) == 2:
589 continue
589 continue
590 count += 1
590 count += 1
591 displayer.show(other[n])
591 displayer.show(other[n])
592 return _incoming(display, subreporecurse, ui, repo, source, opts)
592 return _incoming(display, subreporecurse, ui, repo, source, opts)
593
593
594 def _outgoing(ui, repo, dest, opts):
594 def _outgoing(ui, repo, dest, opts):
595 dest = ui.expandpath(dest or 'default-push', dest or 'default')
595 dest = ui.expandpath(dest or 'default-push', dest or 'default')
596 dest, branches = parseurl(dest, opts.get('branch'))
596 dest, branches = parseurl(dest, opts.get('branch'))
597 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
597 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
598 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
598 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
599 if revs:
599 if revs:
600 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
600 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
601
601
602 other = peer(repo, opts, dest)
602 other = peer(repo, opts, dest)
603 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
603 outgoing = discovery.findcommonoutgoing(repo.unfiltered(), other, revs,
604 force=opts.get('force'))
604 force=opts.get('force'))
605 o = outgoing.missing
605 o = outgoing.missing
606 if not o:
606 if not o:
607 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
607 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
608 return o, other
608 return o, other
609
609
610 def outgoing(ui, repo, dest, opts):
610 def outgoing(ui, repo, dest, opts):
611 def recurse():
611 def recurse():
612 ret = 1
612 ret = 1
613 if opts.get('subrepos'):
613 if opts.get('subrepos'):
614 ctx = repo[None]
614 ctx = repo[None]
615 for subpath in sorted(ctx.substate):
615 for subpath in sorted(ctx.substate):
616 sub = ctx.sub(subpath)
616 sub = ctx.sub(subpath)
617 ret = min(ret, sub.outgoing(ui, dest, opts))
617 ret = min(ret, sub.outgoing(ui, dest, opts))
618 return ret
618 return ret
619
619
620 limit = cmdutil.loglimit(opts)
620 limit = cmdutil.loglimit(opts)
621 o, other = _outgoing(ui, repo, dest, opts)
621 o, other = _outgoing(ui, repo, dest, opts)
622 if not o:
622 if not o:
623 cmdutil.outgoinghooks(ui, repo, other, opts, o)
623 cmdutil.outgoinghooks(ui, repo, other, opts, o)
624 return recurse()
624 return recurse()
625
625
626 if opts.get('newest_first'):
626 if opts.get('newest_first'):
627 o.reverse()
627 o.reverse()
628 displayer = cmdutil.show_changeset(ui, repo, opts)
628 displayer = cmdutil.show_changeset(ui, repo, opts)
629 count = 0
629 count = 0
630 for n in o:
630 for n in o:
631 if limit is not None and count >= limit:
631 if limit is not None and count >= limit:
632 break
632 break
633 parents = [p for p in repo.changelog.parents(n) if p != nullid]
633 parents = [p for p in repo.changelog.parents(n) if p != nullid]
634 if opts.get('no_merges') and len(parents) == 2:
634 if opts.get('no_merges') and len(parents) == 2:
635 continue
635 continue
636 count += 1
636 count += 1
637 displayer.show(repo[n])
637 displayer.show(repo[n])
638 displayer.close()
638 displayer.close()
639 cmdutil.outgoinghooks(ui, repo, other, opts, o)
639 cmdutil.outgoinghooks(ui, repo, other, opts, o)
640 recurse()
640 recurse()
641 return 0 # exit code is zero since we found outgoing changes
641 return 0 # exit code is zero since we found outgoing changes
642
642
643 def revert(repo, node, choose):
643 def revert(repo, node, choose):
644 """revert changes to revision in node without updating dirstate"""
644 """revert changes to revision in node without updating dirstate"""
645 return mergemod.update(repo, node, False, True, choose)[3] > 0
645 return mergemod.update(repo, node, False, True, choose)[3] > 0
646
646
647 def verify(repo):
647 def verify(repo):
648 """verify the consistency of a repository"""
648 """verify the consistency of a repository"""
649 return verifymod.verify(repo)
649 return verifymod.verify(repo)
650
650
651 def remoteui(src, opts):
651 def remoteui(src, opts):
652 'build a remote ui from ui or repo and opts'
652 'build a remote ui from ui or repo and opts'
653 if util.safehasattr(src, 'baseui'): # looks like a repository
653 if util.safehasattr(src, 'baseui'): # looks like a repository
654 dst = src.baseui.copy() # drop repo-specific config
654 dst = src.baseui.copy() # drop repo-specific config
655 src = src.ui # copy target options from repo
655 src = src.ui # copy target options from repo
656 else: # assume it's a global ui object
656 else: # assume it's a global ui object
657 dst = src.copy() # keep all global options
657 dst = src.copy() # keep all global options
658
658
659 # copy ssh-specific options
659 # copy ssh-specific options
660 for o in 'ssh', 'remotecmd':
660 for o in 'ssh', 'remotecmd':
661 v = opts.get(o) or src.config('ui', o)
661 v = opts.get(o) or src.config('ui', o)
662 if v:
662 if v:
663 dst.setconfig("ui", o, v, 'copied')
663 dst.setconfig("ui", o, v, 'copied')
664
664
665 # copy bundle-specific options
665 # copy bundle-specific options
666 r = src.config('bundle', 'mainreporoot')
666 r = src.config('bundle', 'mainreporoot')
667 if r:
667 if r:
668 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
668 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
669
669
670 # copy selected local settings to the remote ui
670 # copy selected local settings to the remote ui
671 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
671 for sect in ('auth', 'hostfingerprints', 'http_proxy'):
672 for key, val in src.configitems(sect):
672 for key, val in src.configitems(sect):
673 dst.setconfig(sect, key, val, 'copied')
673 dst.setconfig(sect, key, val, 'copied')
674 v = src.config('web', 'cacerts')
674 v = src.config('web', 'cacerts')
675 if v:
675 if v == '!':
676 dst.setconfig('web', 'cacerts', v, 'copied')
677 elif v:
676 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
678 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
677
679
678 return dst
680 return dst
@@ -1,221 +1,222
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9 import os, sys
9 import os, sys
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 try:
13 try:
14 # avoid using deprecated/broken FakeSocket in python 2.6
14 # avoid using deprecated/broken FakeSocket in python 2.6
15 import ssl
15 import ssl
16 CERT_REQUIRED = ssl.CERT_REQUIRED
16 CERT_REQUIRED = ssl.CERT_REQUIRED
17 try:
17 try:
18 ssl_context = ssl.SSLContext
18 ssl_context = ssl.SSLContext
19
19
20 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
20 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
21 ca_certs=None, serverhostname=None):
21 ca_certs=None, serverhostname=None):
22 # Allow any version of SSL starting with TLSv1 and
22 # Allow any version of SSL starting with TLSv1 and
23 # up. Note that specifying TLSv1 here prohibits use of
23 # up. Note that specifying TLSv1 here prohibits use of
24 # newer standards (like TLSv1_2), so this is the right way
24 # newer standards (like TLSv1_2), so this is the right way
25 # to do this. Note that in the future it'd be better to
25 # to do this. Note that in the future it'd be better to
26 # support using ssl.create_default_context(), which sets
26 # support using ssl.create_default_context(), which sets
27 # up a bunch of things in smart ways (strong ciphers,
27 # up a bunch of things in smart ways (strong ciphers,
28 # protocol versions, etc) and is upgraded by Python
28 # protocol versions, etc) and is upgraded by Python
29 # maintainers for us, but that breaks too many things to
29 # maintainers for us, but that breaks too many things to
30 # do it in a hurry.
30 # do it in a hurry.
31 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
31 sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
32 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
32 sslcontext.options &= ssl.OP_NO_SSLv2 & ssl.OP_NO_SSLv3
33 if certfile is not None:
33 if certfile is not None:
34 sslcontext.load_cert_chain(certfile, keyfile)
34 sslcontext.load_cert_chain(certfile, keyfile)
35 sslcontext.verify_mode = cert_reqs
35 sslcontext.verify_mode = cert_reqs
36 if ca_certs is not None:
36 if ca_certs is not None:
37 sslcontext.load_verify_locations(cafile=ca_certs)
37 sslcontext.load_verify_locations(cafile=ca_certs)
38
38
39 sslsocket = sslcontext.wrap_socket(sock,
39 sslsocket = sslcontext.wrap_socket(sock,
40 server_hostname=serverhostname)
40 server_hostname=serverhostname)
41 # check if wrap_socket failed silently because socket had been
41 # check if wrap_socket failed silently because socket had been
42 # closed
42 # closed
43 # - see http://bugs.python.org/issue13721
43 # - see http://bugs.python.org/issue13721
44 if not sslsocket.cipher():
44 if not sslsocket.cipher():
45 raise util.Abort(_('ssl connection failed'))
45 raise util.Abort(_('ssl connection failed'))
46 return sslsocket
46 return sslsocket
47 except AttributeError:
47 except AttributeError:
48 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
48 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=ssl.CERT_NONE,
49 ca_certs=None, serverhostname=None):
49 ca_certs=None, serverhostname=None):
50 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
50 sslsocket = ssl.wrap_socket(sock, keyfile, certfile,
51 cert_reqs=cert_reqs, ca_certs=ca_certs,
51 cert_reqs=cert_reqs, ca_certs=ca_certs,
52 ssl_version=ssl.PROTOCOL_TLSv1)
52 ssl_version=ssl.PROTOCOL_TLSv1)
53 # check if wrap_socket failed silently because socket had been
53 # check if wrap_socket failed silently because socket had been
54 # closed
54 # closed
55 # - see http://bugs.python.org/issue13721
55 # - see http://bugs.python.org/issue13721
56 if not sslsocket.cipher():
56 if not sslsocket.cipher():
57 raise util.Abort(_('ssl connection failed'))
57 raise util.Abort(_('ssl connection failed'))
58 return sslsocket
58 return sslsocket
59 except ImportError:
59 except ImportError:
60 CERT_REQUIRED = 2
60 CERT_REQUIRED = 2
61
61
62 import socket, httplib
62 import socket, httplib
63
63
64 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
64 def ssl_wrap_socket(sock, keyfile, certfile, cert_reqs=CERT_REQUIRED,
65 ca_certs=None, serverhostname=None):
65 ca_certs=None, serverhostname=None):
66 if not util.safehasattr(socket, 'ssl'):
66 if not util.safehasattr(socket, 'ssl'):
67 raise util.Abort(_('Python SSL support not found'))
67 raise util.Abort(_('Python SSL support not found'))
68 if ca_certs:
68 if ca_certs:
69 raise util.Abort(_(
69 raise util.Abort(_(
70 'certificate checking requires Python 2.6'))
70 'certificate checking requires Python 2.6'))
71
71
72 ssl = socket.ssl(sock, keyfile, certfile)
72 ssl = socket.ssl(sock, keyfile, certfile)
73 return httplib.FakeSocket(sock, ssl)
73 return httplib.FakeSocket(sock, ssl)
74
74
75 def _verifycert(cert, hostname):
75 def _verifycert(cert, hostname):
76 '''Verify that cert (in socket.getpeercert() format) matches hostname.
76 '''Verify that cert (in socket.getpeercert() format) matches hostname.
77 CRLs is not handled.
77 CRLs is not handled.
78
78
79 Returns error message if any problems are found and None on success.
79 Returns error message if any problems are found and None on success.
80 '''
80 '''
81 if not cert:
81 if not cert:
82 return _('no certificate received')
82 return _('no certificate received')
83 dnsname = hostname.lower()
83 dnsname = hostname.lower()
84 def matchdnsname(certname):
84 def matchdnsname(certname):
85 return (certname == dnsname or
85 return (certname == dnsname or
86 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
86 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
87
87
88 san = cert.get('subjectAltName', [])
88 san = cert.get('subjectAltName', [])
89 if san:
89 if san:
90 certnames = [value.lower() for key, value in san if key == 'DNS']
90 certnames = [value.lower() for key, value in san if key == 'DNS']
91 for name in certnames:
91 for name in certnames:
92 if matchdnsname(name):
92 if matchdnsname(name):
93 return None
93 return None
94 if certnames:
94 if certnames:
95 return _('certificate is for %s') % ', '.join(certnames)
95 return _('certificate is for %s') % ', '.join(certnames)
96
96
97 # subject is only checked when subjectAltName is empty
97 # subject is only checked when subjectAltName is empty
98 for s in cert.get('subject', []):
98 for s in cert.get('subject', []):
99 key, value = s[0]
99 key, value = s[0]
100 if key == 'commonName':
100 if key == 'commonName':
101 try:
101 try:
102 # 'subject' entries are unicode
102 # 'subject' entries are unicode
103 certname = value.lower().encode('ascii')
103 certname = value.lower().encode('ascii')
104 except UnicodeEncodeError:
104 except UnicodeEncodeError:
105 return _('IDN in certificate not supported')
105 return _('IDN in certificate not supported')
106 if matchdnsname(certname):
106 if matchdnsname(certname):
107 return None
107 return None
108 return _('certificate is for %s') % certname
108 return _('certificate is for %s') % certname
109 return _('no commonName or subjectAltName found in certificate')
109 return _('no commonName or subjectAltName found in certificate')
110
110
111
111
112 # CERT_REQUIRED means fetch the cert from the server all the time AND
112 # CERT_REQUIRED means fetch the cert from the server all the time AND
113 # validate it against the CA store provided in web.cacerts.
113 # validate it against the CA store provided in web.cacerts.
114 #
114 #
115 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
115 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
116 # busted on those versions.
116 # busted on those versions.
117
117
118 def _plainapplepython():
118 def _plainapplepython():
119 """return true if this seems to be a pure Apple Python that
119 """return true if this seems to be a pure Apple Python that
120 * is unfrozen and presumably has the whole mercurial module in the file
120 * is unfrozen and presumably has the whole mercurial module in the file
121 system
121 system
122 * presumably is an Apple Python that uses Apple OpenSSL which has patches
122 * presumably is an Apple Python that uses Apple OpenSSL which has patches
123 for using system certificate store CAs in addition to the provided
123 for using system certificate store CAs in addition to the provided
124 cacerts file
124 cacerts file
125 """
125 """
126 if sys.platform != 'darwin' or util.mainfrozen():
126 if sys.platform != 'darwin' or util.mainfrozen():
127 return False
127 return False
128 exe = (sys.executable or '').lower()
128 exe = (sys.executable or '').lower()
129 return (exe.startswith('/usr/bin/python') or
129 return (exe.startswith('/usr/bin/python') or
130 exe.startswith('/system/library/frameworks/python.framework/'))
130 exe.startswith('/system/library/frameworks/python.framework/'))
131
131
132 def _defaultcacerts():
132 def _defaultcacerts():
133 if _plainapplepython():
133 if _plainapplepython():
134 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
134 dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
135 if os.path.exists(dummycert):
135 if os.path.exists(dummycert):
136 return dummycert
136 return dummycert
137 return None
137 return '!'
138
138
139 def sslkwargs(ui, host):
139 def sslkwargs(ui, host):
140 kws = {}
140 kws = {}
141 hostfingerprint = ui.config('hostfingerprints', host)
141 hostfingerprint = ui.config('hostfingerprints', host)
142 if hostfingerprint:
142 if hostfingerprint:
143 return kws
143 return kws
144 cacerts = ui.config('web', 'cacerts')
144 cacerts = ui.config('web', 'cacerts')
145 if cacerts:
145 if cacerts == '!':
146 pass
147 elif cacerts:
146 cacerts = util.expandpath(cacerts)
148 cacerts = util.expandpath(cacerts)
147 if not os.path.exists(cacerts):
149 if not os.path.exists(cacerts):
148 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
150 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
149 elif cacerts is None:
151 else:
150 dummycert = _defaultcacerts()
152 cacerts = _defaultcacerts()
151 if dummycert:
153 if cacerts and cacerts != '!':
152 ui.debug('using %s to enable OS X system CA\n' % dummycert)
154 ui.debug('using %s to enable OS X system CA\n' % cacerts)
153 ui.setconfig('web', 'cacerts', dummycert, 'dummy')
155 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
154 cacerts = dummycert
156 if cacerts != '!':
155 if cacerts:
156 kws.update({'ca_certs': cacerts,
157 kws.update({'ca_certs': cacerts,
157 'cert_reqs': CERT_REQUIRED,
158 'cert_reqs': CERT_REQUIRED,
158 })
159 })
159 return kws
160 return kws
160
161
161 class validator(object):
162 class validator(object):
162 def __init__(self, ui, host):
163 def __init__(self, ui, host):
163 self.ui = ui
164 self.ui = ui
164 self.host = host
165 self.host = host
165
166
166 def __call__(self, sock, strict=False):
167 def __call__(self, sock, strict=False):
167 host = self.host
168 host = self.host
168 cacerts = self.ui.config('web', 'cacerts')
169 cacerts = self.ui.config('web', 'cacerts')
169 hostfingerprint = self.ui.config('hostfingerprints', host)
170 hostfingerprint = self.ui.config('hostfingerprints', host)
170 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
171 if not getattr(sock, 'getpeercert', False): # python 2.5 ?
171 if hostfingerprint:
172 if hostfingerprint:
172 raise util.Abort(_("host fingerprint for %s can't be "
173 raise util.Abort(_("host fingerprint for %s can't be "
173 "verified (Python too old)") % host)
174 "verified (Python too old)") % host)
174 if strict:
175 if strict:
175 raise util.Abort(_("certificate for %s can't be verified "
176 raise util.Abort(_("certificate for %s can't be verified "
176 "(Python too old)") % host)
177 "(Python too old)") % host)
177 if self.ui.configbool('ui', 'reportoldssl', True):
178 if self.ui.configbool('ui', 'reportoldssl', True):
178 self.ui.warn(_("warning: certificate for %s can't be verified "
179 self.ui.warn(_("warning: certificate for %s can't be verified "
179 "(Python too old)\n") % host)
180 "(Python too old)\n") % host)
180 return
181 return
181
182
182 if not sock.cipher(): # work around http://bugs.python.org/issue13721
183 if not sock.cipher(): # work around http://bugs.python.org/issue13721
183 raise util.Abort(_('%s ssl connection error') % host)
184 raise util.Abort(_('%s ssl connection error') % host)
184 try:
185 try:
185 peercert = sock.getpeercert(True)
186 peercert = sock.getpeercert(True)
186 peercert2 = sock.getpeercert()
187 peercert2 = sock.getpeercert()
187 except AttributeError:
188 except AttributeError:
188 raise util.Abort(_('%s ssl connection error') % host)
189 raise util.Abort(_('%s ssl connection error') % host)
189
190
190 if not peercert:
191 if not peercert:
191 raise util.Abort(_('%s certificate error: '
192 raise util.Abort(_('%s certificate error: '
192 'no certificate received') % host)
193 'no certificate received') % host)
193 peerfingerprint = util.sha1(peercert).hexdigest()
194 peerfingerprint = util.sha1(peercert).hexdigest()
194 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
195 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
195 for x in xrange(0, len(peerfingerprint), 2)])
196 for x in xrange(0, len(peerfingerprint), 2)])
196 if hostfingerprint:
197 if hostfingerprint:
197 if peerfingerprint.lower() != \
198 if peerfingerprint.lower() != \
198 hostfingerprint.replace(':', '').lower():
199 hostfingerprint.replace(':', '').lower():
199 raise util.Abort(_('certificate for %s has unexpected '
200 raise util.Abort(_('certificate for %s has unexpected '
200 'fingerprint %s') % (host, nicefingerprint),
201 'fingerprint %s') % (host, nicefingerprint),
201 hint=_('check hostfingerprint configuration'))
202 hint=_('check hostfingerprint configuration'))
202 self.ui.debug('%s certificate matched fingerprint %s\n' %
203 self.ui.debug('%s certificate matched fingerprint %s\n' %
203 (host, nicefingerprint))
204 (host, nicefingerprint))
204 elif cacerts:
205 elif cacerts != '!':
205 msg = _verifycert(peercert2, host)
206 msg = _verifycert(peercert2, host)
206 if msg:
207 if msg:
207 raise util.Abort(_('%s certificate error: %s') % (host, msg),
208 raise util.Abort(_('%s certificate error: %s') % (host, msg),
208 hint=_('configure hostfingerprint %s or use '
209 hint=_('configure hostfingerprint %s or use '
209 '--insecure to connect insecurely') %
210 '--insecure to connect insecurely') %
210 nicefingerprint)
211 nicefingerprint)
211 self.ui.debug('%s certificate successfully verified\n' % host)
212 self.ui.debug('%s certificate successfully verified\n' % host)
212 elif strict:
213 elif strict:
213 raise util.Abort(_('%s certificate with fingerprint %s not '
214 raise util.Abort(_('%s certificate with fingerprint %s not '
214 'verified') % (host, nicefingerprint),
215 'verified') % (host, nicefingerprint),
215 hint=_('check hostfingerprints or web.cacerts '
216 hint=_('check hostfingerprints or web.cacerts '
216 'config setting'))
217 'config setting'))
217 else:
218 else:
218 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
219 self.ui.warn(_('warning: %s certificate with fingerprint %s not '
219 'verified (check hostfingerprints or web.cacerts '
220 'verified (check hostfingerprints or web.cacerts '
220 'config setting)\n') %
221 'config setting)\n') %
221 (host, nicefingerprint))
222 (host, nicefingerprint))
@@ -1,374 +1,374
1 import os, stat
1 import os, stat
2 import re
2 import re
3 import socket
3 import socket
4 import sys
4 import sys
5 import tempfile
5 import tempfile
6
6
7 tempprefix = 'hg-hghave-'
7 tempprefix = 'hg-hghave-'
8
8
9 checks = {
9 checks = {
10 "true": (lambda: True, "yak shaving"),
10 "true": (lambda: True, "yak shaving"),
11 "false": (lambda: False, "nail clipper"),
11 "false": (lambda: False, "nail clipper"),
12 }
12 }
13
13
14 def check(name, desc):
14 def check(name, desc):
15 def decorator(func):
15 def decorator(func):
16 checks[name] = (func, desc)
16 checks[name] = (func, desc)
17 return func
17 return func
18 return decorator
18 return decorator
19
19
20 def matchoutput(cmd, regexp, ignorestatus=False):
20 def matchoutput(cmd, regexp, ignorestatus=False):
21 """Return True if cmd executes successfully and its output
21 """Return True if cmd executes successfully and its output
22 is matched by the supplied regular expression.
22 is matched by the supplied regular expression.
23 """
23 """
24 r = re.compile(regexp)
24 r = re.compile(regexp)
25 fh = os.popen(cmd)
25 fh = os.popen(cmd)
26 s = fh.read()
26 s = fh.read()
27 try:
27 try:
28 ret = fh.close()
28 ret = fh.close()
29 except IOError:
29 except IOError:
30 # Happen in Windows test environment
30 # Happen in Windows test environment
31 ret = 1
31 ret = 1
32 return (ignorestatus or ret is None) and r.search(s)
32 return (ignorestatus or ret is None) and r.search(s)
33
33
34 @check("baz", "GNU Arch baz client")
34 @check("baz", "GNU Arch baz client")
35 def has_baz():
35 def has_baz():
36 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
36 return matchoutput('baz --version 2>&1', r'baz Bazaar version')
37
37
38 @check("bzr", "Canonical's Bazaar client")
38 @check("bzr", "Canonical's Bazaar client")
39 def has_bzr():
39 def has_bzr():
40 try:
40 try:
41 import bzrlib
41 import bzrlib
42 return bzrlib.__doc__ is not None
42 return bzrlib.__doc__ is not None
43 except ImportError:
43 except ImportError:
44 return False
44 return False
45
45
46 @check("bzr114", "Canonical's Bazaar client >= 1.14")
46 @check("bzr114", "Canonical's Bazaar client >= 1.14")
47 def has_bzr114():
47 def has_bzr114():
48 try:
48 try:
49 import bzrlib
49 import bzrlib
50 return (bzrlib.__doc__ is not None
50 return (bzrlib.__doc__ is not None
51 and bzrlib.version_info[:2] >= (1, 14))
51 and bzrlib.version_info[:2] >= (1, 14))
52 except ImportError:
52 except ImportError:
53 return False
53 return False
54
54
55 @check("cvs", "cvs client/server")
55 @check("cvs", "cvs client/server")
56 def has_cvs():
56 def has_cvs():
57 re = r'Concurrent Versions System.*?server'
57 re = r'Concurrent Versions System.*?server'
58 return matchoutput('cvs --version 2>&1', re) and not has_msys()
58 return matchoutput('cvs --version 2>&1', re) and not has_msys()
59
59
60 @check("cvs112", "cvs client/server >= 1.12")
60 @check("cvs112", "cvs client/server >= 1.12")
61 def has_cvs112():
61 def has_cvs112():
62 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
62 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
63 return matchoutput('cvs --version 2>&1', re) and not has_msys()
63 return matchoutput('cvs --version 2>&1', re) and not has_msys()
64
64
65 @check("darcs", "darcs client")
65 @check("darcs", "darcs client")
66 def has_darcs():
66 def has_darcs():
67 return matchoutput('darcs --version', r'2\.[2-9]', True)
67 return matchoutput('darcs --version', r'2\.[2-9]', True)
68
68
69 @check("mtn", "monotone client (>= 1.0)")
69 @check("mtn", "monotone client (>= 1.0)")
70 def has_mtn():
70 def has_mtn():
71 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
71 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
72 'mtn --version', r'monotone 0\.', True)
72 'mtn --version', r'monotone 0\.', True)
73
73
74 @check("eol-in-paths", "end-of-lines in paths")
74 @check("eol-in-paths", "end-of-lines in paths")
75 def has_eol_in_paths():
75 def has_eol_in_paths():
76 try:
76 try:
77 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
77 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
78 os.close(fd)
78 os.close(fd)
79 os.remove(path)
79 os.remove(path)
80 return True
80 return True
81 except (IOError, OSError):
81 except (IOError, OSError):
82 return False
82 return False
83
83
84 @check("execbit", "executable bit")
84 @check("execbit", "executable bit")
85 def has_executablebit():
85 def has_executablebit():
86 try:
86 try:
87 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
87 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
88 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
88 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
89 try:
89 try:
90 os.close(fh)
90 os.close(fh)
91 m = os.stat(fn).st_mode & 0777
91 m = os.stat(fn).st_mode & 0777
92 new_file_has_exec = m & EXECFLAGS
92 new_file_has_exec = m & EXECFLAGS
93 os.chmod(fn, m ^ EXECFLAGS)
93 os.chmod(fn, m ^ EXECFLAGS)
94 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
94 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
95 finally:
95 finally:
96 os.unlink(fn)
96 os.unlink(fn)
97 except (IOError, OSError):
97 except (IOError, OSError):
98 # we don't care, the user probably won't be able to commit anyway
98 # we don't care, the user probably won't be able to commit anyway
99 return False
99 return False
100 return not (new_file_has_exec or exec_flags_cannot_flip)
100 return not (new_file_has_exec or exec_flags_cannot_flip)
101
101
102 @check("icasefs", "case insensitive file system")
102 @check("icasefs", "case insensitive file system")
103 def has_icasefs():
103 def has_icasefs():
104 # Stolen from mercurial.util
104 # Stolen from mercurial.util
105 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
105 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
106 os.close(fd)
106 os.close(fd)
107 try:
107 try:
108 s1 = os.stat(path)
108 s1 = os.stat(path)
109 d, b = os.path.split(path)
109 d, b = os.path.split(path)
110 p2 = os.path.join(d, b.upper())
110 p2 = os.path.join(d, b.upper())
111 if path == p2:
111 if path == p2:
112 p2 = os.path.join(d, b.lower())
112 p2 = os.path.join(d, b.lower())
113 try:
113 try:
114 s2 = os.stat(p2)
114 s2 = os.stat(p2)
115 return s2 == s1
115 return s2 == s1
116 except OSError:
116 except OSError:
117 return False
117 return False
118 finally:
118 finally:
119 os.remove(path)
119 os.remove(path)
120
120
121 @check("fifo", "named pipes")
121 @check("fifo", "named pipes")
122 def has_fifo():
122 def has_fifo():
123 if getattr(os, "mkfifo", None) is None:
123 if getattr(os, "mkfifo", None) is None:
124 return False
124 return False
125 name = tempfile.mktemp(dir='.', prefix=tempprefix)
125 name = tempfile.mktemp(dir='.', prefix=tempprefix)
126 try:
126 try:
127 os.mkfifo(name)
127 os.mkfifo(name)
128 os.unlink(name)
128 os.unlink(name)
129 return True
129 return True
130 except OSError:
130 except OSError:
131 return False
131 return False
132
132
133 @check("killdaemons", 'killdaemons.py support')
133 @check("killdaemons", 'killdaemons.py support')
134 def has_killdaemons():
134 def has_killdaemons():
135 return True
135 return True
136
136
137 @check("cacheable", "cacheable filesystem")
137 @check("cacheable", "cacheable filesystem")
138 def has_cacheable_fs():
138 def has_cacheable_fs():
139 from mercurial import util
139 from mercurial import util
140
140
141 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
141 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
142 os.close(fd)
142 os.close(fd)
143 try:
143 try:
144 return util.cachestat(path).cacheable()
144 return util.cachestat(path).cacheable()
145 finally:
145 finally:
146 os.remove(path)
146 os.remove(path)
147
147
148 @check("lsprof", "python lsprof module")
148 @check("lsprof", "python lsprof module")
149 def has_lsprof():
149 def has_lsprof():
150 try:
150 try:
151 import _lsprof
151 import _lsprof
152 _lsprof.Profiler # silence unused import warning
152 _lsprof.Profiler # silence unused import warning
153 return True
153 return True
154 except ImportError:
154 except ImportError:
155 return False
155 return False
156
156
157 @check("gettext", "GNU Gettext (msgfmt)")
157 @check("gettext", "GNU Gettext (msgfmt)")
158 def has_gettext():
158 def has_gettext():
159 return matchoutput('msgfmt --version', 'GNU gettext-tools')
159 return matchoutput('msgfmt --version', 'GNU gettext-tools')
160
160
161 @check("git", "git command line client")
161 @check("git", "git command line client")
162 def has_git():
162 def has_git():
163 return matchoutput('git --version 2>&1', r'^git version')
163 return matchoutput('git --version 2>&1', r'^git version')
164
164
165 @check("docutils", "Docutils text processing library")
165 @check("docutils", "Docutils text processing library")
166 def has_docutils():
166 def has_docutils():
167 try:
167 try:
168 from docutils.core import publish_cmdline
168 from docutils.core import publish_cmdline
169 publish_cmdline # silence unused import
169 publish_cmdline # silence unused import
170 return True
170 return True
171 except ImportError:
171 except ImportError:
172 return False
172 return False
173
173
174 def getsvnversion():
174 def getsvnversion():
175 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
175 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
176 if not m:
176 if not m:
177 return (0, 0)
177 return (0, 0)
178 return (int(m.group(1)), int(m.group(2)))
178 return (int(m.group(1)), int(m.group(2)))
179
179
180 @check("svn15", "subversion client and admin tools >= 1.5")
180 @check("svn15", "subversion client and admin tools >= 1.5")
181 def has_svn15():
181 def has_svn15():
182 return getsvnversion() >= (1, 5)
182 return getsvnversion() >= (1, 5)
183
183
184 @check("svn13", "subversion client and admin tools >= 1.3")
184 @check("svn13", "subversion client and admin tools >= 1.3")
185 def has_svn13():
185 def has_svn13():
186 return getsvnversion() >= (1, 3)
186 return getsvnversion() >= (1, 3)
187
187
188 @check("svn", "subversion client and admin tools")
188 @check("svn", "subversion client and admin tools")
189 def has_svn():
189 def has_svn():
190 return matchoutput('svn --version 2>&1', r'^svn, version') and \
190 return matchoutput('svn --version 2>&1', r'^svn, version') and \
191 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
191 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
192
192
193 @check("svn-bindings", "subversion python bindings")
193 @check("svn-bindings", "subversion python bindings")
194 def has_svn_bindings():
194 def has_svn_bindings():
195 try:
195 try:
196 import svn.core
196 import svn.core
197 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
197 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
198 if version < (1, 4):
198 if version < (1, 4):
199 return False
199 return False
200 return True
200 return True
201 except ImportError:
201 except ImportError:
202 return False
202 return False
203
203
204 @check("p4", "Perforce server and client")
204 @check("p4", "Perforce server and client")
205 def has_p4():
205 def has_p4():
206 return (matchoutput('p4 -V', r'Rev\. P4/') and
206 return (matchoutput('p4 -V', r'Rev\. P4/') and
207 matchoutput('p4d -V', r'Rev\. P4D/'))
207 matchoutput('p4d -V', r'Rev\. P4D/'))
208
208
209 @check("symlink", "symbolic links")
209 @check("symlink", "symbolic links")
210 def has_symlink():
210 def has_symlink():
211 if getattr(os, "symlink", None) is None:
211 if getattr(os, "symlink", None) is None:
212 return False
212 return False
213 name = tempfile.mktemp(dir='.', prefix=tempprefix)
213 name = tempfile.mktemp(dir='.', prefix=tempprefix)
214 try:
214 try:
215 os.symlink(".", name)
215 os.symlink(".", name)
216 os.unlink(name)
216 os.unlink(name)
217 return True
217 return True
218 except (OSError, AttributeError):
218 except (OSError, AttributeError):
219 return False
219 return False
220
220
221 @check("hardlink", "hardlinks")
221 @check("hardlink", "hardlinks")
222 def has_hardlink():
222 def has_hardlink():
223 from mercurial import util
223 from mercurial import util
224 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
224 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
225 os.close(fh)
225 os.close(fh)
226 name = tempfile.mktemp(dir='.', prefix=tempprefix)
226 name = tempfile.mktemp(dir='.', prefix=tempprefix)
227 try:
227 try:
228 try:
228 try:
229 util.oslink(fn, name)
229 util.oslink(fn, name)
230 os.unlink(name)
230 os.unlink(name)
231 return True
231 return True
232 except OSError:
232 except OSError:
233 return False
233 return False
234 finally:
234 finally:
235 os.unlink(fn)
235 os.unlink(fn)
236
236
237 @check("tla", "GNU Arch tla client")
237 @check("tla", "GNU Arch tla client")
238 def has_tla():
238 def has_tla():
239 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
239 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
240
240
241 @check("gpg", "gpg client")
241 @check("gpg", "gpg client")
242 def has_gpg():
242 def has_gpg():
243 return matchoutput('gpg --version 2>&1', r'GnuPG')
243 return matchoutput('gpg --version 2>&1', r'GnuPG')
244
244
245 @check("unix-permissions", "unix-style permissions")
245 @check("unix-permissions", "unix-style permissions")
246 def has_unix_permissions():
246 def has_unix_permissions():
247 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
247 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
248 try:
248 try:
249 fname = os.path.join(d, 'foo')
249 fname = os.path.join(d, 'foo')
250 for umask in (077, 007, 022):
250 for umask in (077, 007, 022):
251 os.umask(umask)
251 os.umask(umask)
252 f = open(fname, 'w')
252 f = open(fname, 'w')
253 f.close()
253 f.close()
254 mode = os.stat(fname).st_mode
254 mode = os.stat(fname).st_mode
255 os.unlink(fname)
255 os.unlink(fname)
256 if mode & 0777 != ~umask & 0666:
256 if mode & 0777 != ~umask & 0666:
257 return False
257 return False
258 return True
258 return True
259 finally:
259 finally:
260 os.rmdir(d)
260 os.rmdir(d)
261
261
262 @check("unix-socket", "AF_UNIX socket family")
262 @check("unix-socket", "AF_UNIX socket family")
263 def has_unix_socket():
263 def has_unix_socket():
264 return getattr(socket, 'AF_UNIX', None) is not None
264 return getattr(socket, 'AF_UNIX', None) is not None
265
265
266 @check("root", "root permissions")
266 @check("root", "root permissions")
267 def has_root():
267 def has_root():
268 return getattr(os, 'geteuid', None) and os.geteuid() == 0
268 return getattr(os, 'geteuid', None) and os.geteuid() == 0
269
269
270 @check("pyflakes", "Pyflakes python linter")
270 @check("pyflakes", "Pyflakes python linter")
271 def has_pyflakes():
271 def has_pyflakes():
272 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
272 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
273 r"<stdin>:1: 're' imported but unused",
273 r"<stdin>:1: 're' imported but unused",
274 True)
274 True)
275
275
276 @check("pygments", "Pygments source highlighting library")
276 @check("pygments", "Pygments source highlighting library")
277 def has_pygments():
277 def has_pygments():
278 try:
278 try:
279 import pygments
279 import pygments
280 pygments.highlight # silence unused import warning
280 pygments.highlight # silence unused import warning
281 return True
281 return True
282 except ImportError:
282 except ImportError:
283 return False
283 return False
284
284
285 @check("python243", "python >= 2.4.3")
285 @check("python243", "python >= 2.4.3")
286 def has_python243():
286 def has_python243():
287 return sys.version_info >= (2, 4, 3)
287 return sys.version_info >= (2, 4, 3)
288
288
289 @check("json", "some json module available")
289 @check("json", "some json module available")
290 def has_json():
290 def has_json():
291 try:
291 try:
292 import json
292 import json
293 json.dumps
293 json.dumps
294 return True
294 return True
295 except ImportError:
295 except ImportError:
296 try:
296 try:
297 import simplejson as json
297 import simplejson as json
298 json.dumps
298 json.dumps
299 return True
299 return True
300 except ImportError:
300 except ImportError:
301 pass
301 pass
302 return False
302 return False
303
303
304 @check("outer-repo", "outer repo")
304 @check("outer-repo", "outer repo")
305 def has_outer_repo():
305 def has_outer_repo():
306 # failing for other reasons than 'no repo' imply that there is a repo
306 # failing for other reasons than 'no repo' imply that there is a repo
307 return not matchoutput('hg root 2>&1',
307 return not matchoutput('hg root 2>&1',
308 r'abort: no repository found', True)
308 r'abort: no repository found', True)
309
309
310 @check("ssl", ("(python >= 2.6 ssl module and python OpenSSL) "
310 @check("ssl", ("(python >= 2.6 ssl module and python OpenSSL) "
311 "OR python >= 2.7.9 ssl"))
311 "OR python >= 2.7.9 ssl"))
312 def has_ssl():
312 def has_ssl():
313 try:
313 try:
314 import ssl
314 import ssl
315 if getattr(ssl, 'create_default_context', False):
315 if getattr(ssl, 'create_default_context', False):
316 return True
316 return True
317 import OpenSSL
317 import OpenSSL
318 OpenSSL.SSL.Context
318 OpenSSL.SSL.Context
319 return True
319 return True
320 except ImportError:
320 except ImportError:
321 return False
321 return False
322
322
323 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
323 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
324 def has_defaultcacerts():
324 def has_defaultcacerts():
325 from mercurial import sslutil
325 from mercurial import sslutil
326 return sslutil._defaultcacerts()
326 return sslutil._defaultcacerts() != '!'
327
327
328 @check("windows", "Windows")
328 @check("windows", "Windows")
329 def has_windows():
329 def has_windows():
330 return os.name == 'nt'
330 return os.name == 'nt'
331
331
332 @check("system-sh", "system() uses sh")
332 @check("system-sh", "system() uses sh")
333 def has_system_sh():
333 def has_system_sh():
334 return os.name != 'nt'
334 return os.name != 'nt'
335
335
336 @check("serve", "platform and python can manage 'hg serve -d'")
336 @check("serve", "platform and python can manage 'hg serve -d'")
337 def has_serve():
337 def has_serve():
338 return os.name != 'nt' # gross approximation
338 return os.name != 'nt' # gross approximation
339
339
340 @check("test-repo", "running tests from repository")
340 @check("test-repo", "running tests from repository")
341 def has_test_repo():
341 def has_test_repo():
342 t = os.environ["TESTDIR"]
342 t = os.environ["TESTDIR"]
343 return os.path.isdir(os.path.join(t, "..", ".hg"))
343 return os.path.isdir(os.path.join(t, "..", ".hg"))
344
344
345 @check("tic", "terminfo compiler and curses module")
345 @check("tic", "terminfo compiler and curses module")
346 def has_tic():
346 def has_tic():
347 try:
347 try:
348 import curses
348 import curses
349 curses.COLOR_BLUE
349 curses.COLOR_BLUE
350 return matchoutput('test -x "`which tic`"', '')
350 return matchoutput('test -x "`which tic`"', '')
351 except ImportError:
351 except ImportError:
352 return False
352 return False
353
353
354 @check("msys", "Windows with MSYS")
354 @check("msys", "Windows with MSYS")
355 def has_msys():
355 def has_msys():
356 return os.getenv('MSYSTEM')
356 return os.getenv('MSYSTEM')
357
357
358 @check("aix", "AIX")
358 @check("aix", "AIX")
359 def has_aix():
359 def has_aix():
360 return sys.platform.startswith("aix")
360 return sys.platform.startswith("aix")
361
361
362 @check("osx", "OS X")
362 @check("osx", "OS X")
363 def has_osx():
363 def has_osx():
364 return sys.platform == 'darwin'
364 return sys.platform == 'darwin'
365
365
366 @check("absimport", "absolute_import in __future__")
366 @check("absimport", "absolute_import in __future__")
367 def has_absimport():
367 def has_absimport():
368 import __future__
368 import __future__
369 from mercurial import util
369 from mercurial import util
370 return util.safehasattr(__future__, "absolute_import")
370 return util.safehasattr(__future__, "absolute_import")
371
371
372 @check("py3k", "running with Python 3.x")
372 @check("py3k", "running with Python 3.x")
373 def has_py3k():
373 def has_py3k():
374 return 3 == sys.version_info[0]
374 return 3 == sys.version_info[0]
@@ -1,299 +1,299
1 #require serve ssl
1 #require serve ssl
2
2
3 Proper https client requires the built-in ssl from Python 2.6.
3 Proper https client requires the built-in ssl from Python 2.6.
4
4
5 Certificates created with:
5 Certificates created with:
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 Can be dumped with:
8 Can be dumped with:
9 openssl x509 -in pub.pem -text
9 openssl x509 -in pub.pem -text
10
10
11 $ cat << EOT > priv.pem
11 $ cat << EOT > priv.pem
12 > -----BEGIN PRIVATE KEY-----
12 > -----BEGIN PRIVATE KEY-----
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 > HY8gUVkVRVs=
20 > HY8gUVkVRVs=
21 > -----END PRIVATE KEY-----
21 > -----END PRIVATE KEY-----
22 > EOT
22 > EOT
23
23
24 $ cat << EOT > pub.pem
24 $ cat << EOT > pub.pem
25 > -----BEGIN CERTIFICATE-----
25 > -----BEGIN CERTIFICATE-----
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 > -----END CERTIFICATE-----
35 > -----END CERTIFICATE-----
36 > EOT
36 > EOT
37 $ cat priv.pem pub.pem >> server.pem
37 $ cat priv.pem pub.pem >> server.pem
38 $ PRIV=`pwd`/server.pem
38 $ PRIV=`pwd`/server.pem
39
39
40 $ cat << EOT > pub-other.pem
40 $ cat << EOT > pub-other.pem
41 > -----BEGIN CERTIFICATE-----
41 > -----BEGIN CERTIFICATE-----
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 > -----END CERTIFICATE-----
51 > -----END CERTIFICATE-----
52 > EOT
52 > EOT
53
53
54 pub.pem patched with other notBefore / notAfter:
54 pub.pem patched with other notBefore / notAfter:
55
55
56 $ cat << EOT > pub-not-yet.pem
56 $ cat << EOT > pub-not-yet.pem
57 > -----BEGIN CERTIFICATE-----
57 > -----BEGIN CERTIFICATE-----
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 > -----END CERTIFICATE-----
66 > -----END CERTIFICATE-----
67 > EOT
67 > EOT
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69
69
70 $ cat << EOT > pub-expired.pem
70 $ cat << EOT > pub-expired.pem
71 > -----BEGIN CERTIFICATE-----
71 > -----BEGIN CERTIFICATE-----
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 > -----END CERTIFICATE-----
80 > -----END CERTIFICATE-----
81 > EOT
81 > EOT
82 $ cat priv.pem pub-expired.pem > server-expired.pem
82 $ cat priv.pem pub-expired.pem > server-expired.pem
83
83
84 $ hg init test
84 $ hg init test
85 $ cd test
85 $ cd test
86 $ echo foo>foo
86 $ echo foo>foo
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 $ echo foo>foo.d/foo
88 $ echo foo>foo.d/foo
89 $ echo bar>foo.d/bAr.hg.d/BaR
89 $ echo bar>foo.d/bAr.hg.d/BaR
90 $ echo bar>foo.d/baR.d.hg/bAR
90 $ echo bar>foo.d/baR.d.hg/bAR
91 $ hg commit -A -m 1
91 $ hg commit -A -m 1
92 adding foo
92 adding foo
93 adding foo.d/bAr.hg.d/BaR
93 adding foo.d/bAr.hg.d/BaR
94 adding foo.d/baR.d.hg/bAR
94 adding foo.d/baR.d.hg/bAR
95 adding foo.d/foo
95 adding foo.d/foo
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 $ cat ../hg0.pid >> $DAEMON_PIDS
97 $ cat ../hg0.pid >> $DAEMON_PIDS
98
98
99 cacert not found
99 cacert not found
100
100
101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
102 abort: could not find web.cacerts: no-such.pem
102 abort: could not find web.cacerts: no-such.pem
103 [255]
103 [255]
104
104
105 Test server address cannot be reused
105 Test server address cannot be reused
106
106
107 #if windows
107 #if windows
108 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
108 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
109 abort: cannot start server at ':$HGPORT':
109 abort: cannot start server at ':$HGPORT':
110 [255]
110 [255]
111 #else
111 #else
112 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
112 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
113 abort: cannot start server at ':$HGPORT': Address already in use
113 abort: cannot start server at ':$HGPORT': Address already in use
114 [255]
114 [255]
115 #endif
115 #endif
116 $ cd ..
116 $ cd ..
117
117
118 OS X has a dummy CA cert that enables use of the system CA store when using
118 OS X has a dummy CA cert that enables use of the system CA store when using
119 Apple's OpenSSL. This trick do not work with plain OpenSSL.
119 Apple's OpenSSL. This trick do not work with plain OpenSSL.
120
120
121 $ DISABLEOSXDUMMYCERT=
121 $ DISABLEOSXDUMMYCERT=
122 #if defaultcacerts
122 #if defaultcacerts
123 $ hg clone https://localhost:$HGPORT/ copy-pull
123 $ hg clone https://localhost:$HGPORT/ copy-pull
124 abort: error: *certificate verify failed* (glob)
124 abort: error: *certificate verify failed* (glob)
125 [255]
125 [255]
126
126
127 $ DISABLEOSXDUMMYCERT="--config=web.cacerts="
127 $ DISABLEOSXDUMMYCERT="--config=web.cacerts=!"
128 #endif
128 #endif
129
129
130 clone via pull
130 clone via pull
131
131
132 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLEOSXDUMMYCERT
132 $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLEOSXDUMMYCERT
133 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
133 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
134 requesting all changes
134 requesting all changes
135 adding changesets
135 adding changesets
136 adding manifests
136 adding manifests
137 adding file changes
137 adding file changes
138 added 1 changesets with 4 changes to 4 files
138 added 1 changesets with 4 changes to 4 files
139 updating to branch default
139 updating to branch default
140 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
140 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
141 $ hg verify -R copy-pull
141 $ hg verify -R copy-pull
142 checking changesets
142 checking changesets
143 checking manifests
143 checking manifests
144 crosschecking files in changesets and manifests
144 crosschecking files in changesets and manifests
145 checking files
145 checking files
146 4 files, 1 changesets, 4 total revisions
146 4 files, 1 changesets, 4 total revisions
147 $ cd test
147 $ cd test
148 $ echo bar > bar
148 $ echo bar > bar
149 $ hg commit -A -d '1 0' -m 2
149 $ hg commit -A -d '1 0' -m 2
150 adding bar
150 adding bar
151 $ cd ..
151 $ cd ..
152
152
153 pull without cacert
153 pull without cacert
154
154
155 $ cd copy-pull
155 $ cd copy-pull
156 $ echo '[hooks]' >> .hg/hgrc
156 $ echo '[hooks]' >> .hg/hgrc
157 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
157 $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
158 $ hg pull $DISABLEOSXDUMMYCERT
158 $ hg pull $DISABLEOSXDUMMYCERT
159 pulling from https://localhost:$HGPORT/
159 pulling from https://localhost:$HGPORT/
160 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
160 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
161 searching for changes
161 searching for changes
162 adding changesets
162 adding changesets
163 adding manifests
163 adding manifests
164 adding file changes
164 adding file changes
165 added 1 changesets with 1 changes to 1 files
165 added 1 changesets with 1 changes to 1 files
166 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
166 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
167 (run 'hg update' to get a working copy)
167 (run 'hg update' to get a working copy)
168 $ cd ..
168 $ cd ..
169
169
170 cacert configured in local repo
170 cacert configured in local repo
171
171
172 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
172 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
173 $ echo "[web]" >> copy-pull/.hg/hgrc
173 $ echo "[web]" >> copy-pull/.hg/hgrc
174 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
174 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
175 $ hg -R copy-pull pull --traceback
175 $ hg -R copy-pull pull --traceback
176 pulling from https://localhost:$HGPORT/
176 pulling from https://localhost:$HGPORT/
177 searching for changes
177 searching for changes
178 no changes found
178 no changes found
179 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
179 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
180
180
181 cacert configured globally, also testing expansion of environment
181 cacert configured globally, also testing expansion of environment
182 variables in the filename
182 variables in the filename
183
183
184 $ echo "[web]" >> $HGRCPATH
184 $ echo "[web]" >> $HGRCPATH
185 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
185 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
186 $ P=`pwd` hg -R copy-pull pull
186 $ P=`pwd` hg -R copy-pull pull
187 pulling from https://localhost:$HGPORT/
187 pulling from https://localhost:$HGPORT/
188 searching for changes
188 searching for changes
189 no changes found
189 no changes found
190 $ P=`pwd` hg -R copy-pull pull --insecure
190 $ P=`pwd` hg -R copy-pull pull --insecure
191 pulling from https://localhost:$HGPORT/
191 pulling from https://localhost:$HGPORT/
192 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
192 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
193 searching for changes
193 searching for changes
194 no changes found
194 no changes found
195
195
196 cacert mismatch
196 cacert mismatch
197
197
198 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
198 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
199 pulling from https://127.0.0.1:$HGPORT/
199 pulling from https://127.0.0.1:$HGPORT/
200 abort: 127.0.0.1 certificate error: certificate is for localhost
200 abort: 127.0.0.1 certificate error: certificate is for localhost
201 (configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
201 (configure hostfingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca or use --insecure to connect insecurely)
202 [255]
202 [255]
203 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
203 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
204 pulling from https://127.0.0.1:$HGPORT/
204 pulling from https://127.0.0.1:$HGPORT/
205 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
205 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
206 searching for changes
206 searching for changes
207 no changes found
207 no changes found
208 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
208 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
209 pulling from https://localhost:$HGPORT/
209 pulling from https://localhost:$HGPORT/
210 abort: error: *certificate verify failed* (glob)
210 abort: error: *certificate verify failed* (glob)
211 [255]
211 [255]
212 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
212 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
213 pulling from https://localhost:$HGPORT/
213 pulling from https://localhost:$HGPORT/
214 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
214 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
215 searching for changes
215 searching for changes
216 no changes found
216 no changes found
217
217
218 Test server cert which isn't valid yet
218 Test server cert which isn't valid yet
219
219
220 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
220 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
221 $ cat hg1.pid >> $DAEMON_PIDS
221 $ cat hg1.pid >> $DAEMON_PIDS
222 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
222 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
223 pulling from https://localhost:$HGPORT1/
223 pulling from https://localhost:$HGPORT1/
224 abort: error: *certificate verify failed* (glob)
224 abort: error: *certificate verify failed* (glob)
225 [255]
225 [255]
226
226
227 Test server cert which no longer is valid
227 Test server cert which no longer is valid
228
228
229 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
229 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
230 $ cat hg2.pid >> $DAEMON_PIDS
230 $ cat hg2.pid >> $DAEMON_PIDS
231 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
231 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
232 pulling from https://localhost:$HGPORT2/
232 pulling from https://localhost:$HGPORT2/
233 abort: error: *certificate verify failed* (glob)
233 abort: error: *certificate verify failed* (glob)
234 [255]
234 [255]
235
235
236 Fingerprints
236 Fingerprints
237
237
238 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
238 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
239 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
239 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
240 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
240 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
241
241
242 - works without cacerts
242 - works without cacerts
243 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
243 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=!
244 5fed3813f7f5
244 5fed3813f7f5
245
245
246 - fails when cert doesn't match hostname (port is ignored)
246 - fails when cert doesn't match hostname (port is ignored)
247 $ hg -R copy-pull id https://localhost:$HGPORT1/
247 $ hg -R copy-pull id https://localhost:$HGPORT1/
248 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
248 abort: certificate for localhost has unexpected fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
249 (check hostfingerprint configuration)
249 (check hostfingerprint configuration)
250 [255]
250 [255]
251
251
252
252
253 - ignores that certificate doesn't match hostname
253 - ignores that certificate doesn't match hostname
254 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
254 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
255 5fed3813f7f5
255 5fed3813f7f5
256
256
257 HGPORT1 is reused below for tinyproxy tests. Kill that server.
257 HGPORT1 is reused below for tinyproxy tests. Kill that server.
258 $ "$TESTDIR/killdaemons.py" hg1.pid
258 $ "$TESTDIR/killdaemons.py" hg1.pid
259
259
260 Prepare for connecting through proxy
260 Prepare for connecting through proxy
261
261
262 $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
262 $ "$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log </dev/null 2>&1 &
263 $ while [ ! -f proxy.pid ]; do sleep 0; done
263 $ while [ ! -f proxy.pid ]; do sleep 0; done
264 $ cat proxy.pid >> $DAEMON_PIDS
264 $ cat proxy.pid >> $DAEMON_PIDS
265
265
266 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
266 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
267 $ echo "always=True" >> copy-pull/.hg/hgrc
267 $ echo "always=True" >> copy-pull/.hg/hgrc
268 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
268 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
269 $ echo "localhost =" >> copy-pull/.hg/hgrc
269 $ echo "localhost =" >> copy-pull/.hg/hgrc
270
270
271 Test unvalidated https through proxy
271 Test unvalidated https through proxy
272
272
273 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
273 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
274 pulling from https://localhost:$HGPORT/
274 pulling from https://localhost:$HGPORT/
275 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
275 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
276 searching for changes
276 searching for changes
277 no changes found
277 no changes found
278
278
279 Test https with cacert and fingerprint through proxy
279 Test https with cacert and fingerprint through proxy
280
280
281 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
281 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
282 pulling from https://localhost:$HGPORT/
282 pulling from https://localhost:$HGPORT/
283 searching for changes
283 searching for changes
284 no changes found
284 no changes found
285 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
285 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
286 pulling from https://127.0.0.1:$HGPORT/
286 pulling from https://127.0.0.1:$HGPORT/
287 searching for changes
287 searching for changes
288 no changes found
288 no changes found
289
289
290 Test https with cert problems through proxy
290 Test https with cert problems through proxy
291
291
292 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
292 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
293 pulling from https://localhost:$HGPORT/
293 pulling from https://localhost:$HGPORT/
294 abort: error: *certificate verify failed* (glob)
294 abort: error: *certificate verify failed* (glob)
295 [255]
295 [255]
296 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
296 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
297 pulling from https://localhost:$HGPORT2/
297 pulling from https://localhost:$HGPORT2/
298 abort: error: *certificate verify failed* (glob)
298 abort: error: *certificate verify failed* (glob)
299 [255]
299 [255]
General Comments 0
You need to be logged in to leave comments. Login now