##// END OF EJS Templates
dispatch: make request accept additional reposetups...
Jun Wu -
r32379:71e735bd default
parent child Browse files
Show More
@@ -1,1000 +1,1005 b''
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 __future__ import absolute_import, print_function
8 from __future__ import absolute_import, print_function
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import getopt
12 import getopt
13 import os
13 import os
14 import pdb
14 import pdb
15 import re
15 import re
16 import signal
16 import signal
17 import sys
17 import sys
18 import time
18 import time
19 import traceback
19 import traceback
20
20
21
21
22 from .i18n import _
22 from .i18n import _
23
23
24 from . import (
24 from . import (
25 cmdutil,
25 cmdutil,
26 color,
26 color,
27 commands,
27 commands,
28 demandimport,
28 demandimport,
29 encoding,
29 encoding,
30 error,
30 error,
31 extensions,
31 extensions,
32 fancyopts,
32 fancyopts,
33 fileset,
33 fileset,
34 help,
34 help,
35 hg,
35 hg,
36 hook,
36 hook,
37 profiling,
37 profiling,
38 pycompat,
38 pycompat,
39 revset,
39 revset,
40 scmutil,
40 scmutil,
41 templatefilters,
41 templatefilters,
42 templatekw,
42 templatekw,
43 templater,
43 templater,
44 ui as uimod,
44 ui as uimod,
45 util,
45 util,
46 )
46 )
47
47
48 class request(object):
48 class request(object):
49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
49 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
50 ferr=None):
50 ferr=None, prereposetups=None):
51 self.args = args
51 self.args = args
52 self.ui = ui
52 self.ui = ui
53 self.repo = repo
53 self.repo = repo
54
54
55 # input/output/error streams
55 # input/output/error streams
56 self.fin = fin
56 self.fin = fin
57 self.fout = fout
57 self.fout = fout
58 self.ferr = ferr
58 self.ferr = ferr
59
59
60 # reposetups which run before extensions, useful for chg to pre-fill
61 # low-level repo state (for example, changelog) before extensions.
62 self.prereposetups = prereposetups or []
63
60 def _runexithandlers(self):
64 def _runexithandlers(self):
61 exc = None
65 exc = None
62 handlers = self.ui._exithandlers
66 handlers = self.ui._exithandlers
63 try:
67 try:
64 while handlers:
68 while handlers:
65 func, args, kwargs = handlers.pop()
69 func, args, kwargs = handlers.pop()
66 try:
70 try:
67 func(*args, **kwargs)
71 func(*args, **kwargs)
68 except: # re-raises below
72 except: # re-raises below
69 if exc is None:
73 if exc is None:
70 exc = sys.exc_info()[1]
74 exc = sys.exc_info()[1]
71 self.ui.warn(('error in exit handlers:\n'))
75 self.ui.warn(('error in exit handlers:\n'))
72 self.ui.traceback(force=True)
76 self.ui.traceback(force=True)
73 finally:
77 finally:
74 if exc is not None:
78 if exc is not None:
75 raise exc
79 raise exc
76
80
77 def run():
81 def run():
78 "run the command in sys.argv"
82 "run the command in sys.argv"
79 req = request(pycompat.sysargv[1:])
83 req = request(pycompat.sysargv[1:])
80 err = None
84 err = None
81 try:
85 try:
82 status = (dispatch(req) or 0) & 255
86 status = (dispatch(req) or 0) & 255
83 except error.StdioError as err:
87 except error.StdioError as err:
84 status = -1
88 status = -1
85 if util.safehasattr(req.ui, 'fout'):
89 if util.safehasattr(req.ui, 'fout'):
86 try:
90 try:
87 req.ui.fout.close()
91 req.ui.fout.close()
88 except IOError as err:
92 except IOError as err:
89 status = -1
93 status = -1
90 if util.safehasattr(req.ui, 'ferr'):
94 if util.safehasattr(req.ui, 'ferr'):
91 if err is not None and err.errno != errno.EPIPE:
95 if err is not None and err.errno != errno.EPIPE:
92 req.ui.ferr.write('abort: %s\n' % err.strerror)
96 req.ui.ferr.write('abort: %s\n' % err.strerror)
93 req.ui.ferr.close()
97 req.ui.ferr.close()
94 sys.exit(status & 255)
98 sys.exit(status & 255)
95
99
96 def _getsimilar(symbols, value):
100 def _getsimilar(symbols, value):
97 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
101 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
98 # The cutoff for similarity here is pretty arbitrary. It should
102 # The cutoff for similarity here is pretty arbitrary. It should
99 # probably be investigated and tweaked.
103 # probably be investigated and tweaked.
100 return [s for s in symbols if sim(s) > 0.6]
104 return [s for s in symbols if sim(s) > 0.6]
101
105
102 def _reportsimilar(write, similar):
106 def _reportsimilar(write, similar):
103 if len(similar) == 1:
107 if len(similar) == 1:
104 write(_("(did you mean %s?)\n") % similar[0])
108 write(_("(did you mean %s?)\n") % similar[0])
105 elif similar:
109 elif similar:
106 ss = ", ".join(sorted(similar))
110 ss = ", ".join(sorted(similar))
107 write(_("(did you mean one of %s?)\n") % ss)
111 write(_("(did you mean one of %s?)\n") % ss)
108
112
109 def _formatparse(write, inst):
113 def _formatparse(write, inst):
110 similar = []
114 similar = []
111 if isinstance(inst, error.UnknownIdentifier):
115 if isinstance(inst, error.UnknownIdentifier):
112 # make sure to check fileset first, as revset can invoke fileset
116 # make sure to check fileset first, as revset can invoke fileset
113 similar = _getsimilar(inst.symbols, inst.function)
117 similar = _getsimilar(inst.symbols, inst.function)
114 if len(inst.args) > 1:
118 if len(inst.args) > 1:
115 write(_("hg: parse error at %s: %s\n") %
119 write(_("hg: parse error at %s: %s\n") %
116 (inst.args[1], inst.args[0]))
120 (inst.args[1], inst.args[0]))
117 if (inst.args[0][0] == ' '):
121 if (inst.args[0][0] == ' '):
118 write(_("unexpected leading whitespace\n"))
122 write(_("unexpected leading whitespace\n"))
119 else:
123 else:
120 write(_("hg: parse error: %s\n") % inst.args[0])
124 write(_("hg: parse error: %s\n") % inst.args[0])
121 _reportsimilar(write, similar)
125 _reportsimilar(write, similar)
122 if inst.hint:
126 if inst.hint:
123 write(_("(%s)\n") % inst.hint)
127 write(_("(%s)\n") % inst.hint)
124
128
125 def _formatargs(args):
129 def _formatargs(args):
126 return ' '.join(util.shellquote(a) for a in args)
130 return ' '.join(util.shellquote(a) for a in args)
127
131
128 def dispatch(req):
132 def dispatch(req):
129 "run the command specified in req.args"
133 "run the command specified in req.args"
130 if req.ferr:
134 if req.ferr:
131 ferr = req.ferr
135 ferr = req.ferr
132 elif req.ui:
136 elif req.ui:
133 ferr = req.ui.ferr
137 ferr = req.ui.ferr
134 else:
138 else:
135 ferr = util.stderr
139 ferr = util.stderr
136
140
137 try:
141 try:
138 if not req.ui:
142 if not req.ui:
139 req.ui = uimod.ui.load()
143 req.ui = uimod.ui.load()
140 if '--traceback' in req.args:
144 if '--traceback' in req.args:
141 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
145 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
142
146
143 # set ui streams from the request
147 # set ui streams from the request
144 if req.fin:
148 if req.fin:
145 req.ui.fin = req.fin
149 req.ui.fin = req.fin
146 if req.fout:
150 if req.fout:
147 req.ui.fout = req.fout
151 req.ui.fout = req.fout
148 if req.ferr:
152 if req.ferr:
149 req.ui.ferr = req.ferr
153 req.ui.ferr = req.ferr
150 except error.Abort as inst:
154 except error.Abort as inst:
151 ferr.write(_("abort: %s\n") % inst)
155 ferr.write(_("abort: %s\n") % inst)
152 if inst.hint:
156 if inst.hint:
153 ferr.write(_("(%s)\n") % inst.hint)
157 ferr.write(_("(%s)\n") % inst.hint)
154 return -1
158 return -1
155 except error.ParseError as inst:
159 except error.ParseError as inst:
156 _formatparse(ferr.write, inst)
160 _formatparse(ferr.write, inst)
157 return -1
161 return -1
158
162
159 msg = _formatargs(req.args)
163 msg = _formatargs(req.args)
160 starttime = util.timer()
164 starttime = util.timer()
161 ret = None
165 ret = None
162 try:
166 try:
163 ret = _runcatch(req)
167 ret = _runcatch(req)
164 except error.ProgrammingError as inst:
168 except error.ProgrammingError as inst:
165 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
169 req.ui.warn(_('** ProgrammingError: %s\n') % inst)
166 if inst.hint:
170 if inst.hint:
167 req.ui.warn(_('** (%s)\n') % inst.hint)
171 req.ui.warn(_('** (%s)\n') % inst.hint)
168 raise
172 raise
169 except KeyboardInterrupt as inst:
173 except KeyboardInterrupt as inst:
170 try:
174 try:
171 if isinstance(inst, error.SignalInterrupt):
175 if isinstance(inst, error.SignalInterrupt):
172 msg = _("killed!\n")
176 msg = _("killed!\n")
173 else:
177 else:
174 msg = _("interrupted!\n")
178 msg = _("interrupted!\n")
175 req.ui.warn(msg)
179 req.ui.warn(msg)
176 except error.SignalInterrupt:
180 except error.SignalInterrupt:
177 # maybe pager would quit without consuming all the output, and
181 # maybe pager would quit without consuming all the output, and
178 # SIGPIPE was raised. we cannot print anything in this case.
182 # SIGPIPE was raised. we cannot print anything in this case.
179 pass
183 pass
180 except IOError as inst:
184 except IOError as inst:
181 if inst.errno != errno.EPIPE:
185 if inst.errno != errno.EPIPE:
182 raise
186 raise
183 ret = -1
187 ret = -1
184 finally:
188 finally:
185 duration = util.timer() - starttime
189 duration = util.timer() - starttime
186 req.ui.flush()
190 req.ui.flush()
187 if req.ui.logblockedtimes:
191 if req.ui.logblockedtimes:
188 req.ui._blockedtimes['command_duration'] = duration * 1000
192 req.ui._blockedtimes['command_duration'] = duration * 1000
189 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
193 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
190 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
194 req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
191 msg, ret or 0, duration)
195 msg, ret or 0, duration)
192 try:
196 try:
193 req._runexithandlers()
197 req._runexithandlers()
194 except: # exiting, so no re-raises
198 except: # exiting, so no re-raises
195 ret = ret or -1
199 ret = ret or -1
196 return ret
200 return ret
197
201
198 def _runcatch(req):
202 def _runcatch(req):
199 def catchterm(*args):
203 def catchterm(*args):
200 raise error.SignalInterrupt
204 raise error.SignalInterrupt
201
205
202 ui = req.ui
206 ui = req.ui
203 try:
207 try:
204 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
208 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
205 num = getattr(signal, name, None)
209 num = getattr(signal, name, None)
206 if num:
210 if num:
207 signal.signal(num, catchterm)
211 signal.signal(num, catchterm)
208 except ValueError:
212 except ValueError:
209 pass # happens if called in a thread
213 pass # happens if called in a thread
210
214
211 def _runcatchfunc():
215 def _runcatchfunc():
212 realcmd = None
216 realcmd = None
213 try:
217 try:
214 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
218 cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
215 cmd = cmdargs[0]
219 cmd = cmdargs[0]
216 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
220 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
217 realcmd = aliases[0]
221 realcmd = aliases[0]
218 except (error.UnknownCommand, error.AmbiguousCommand,
222 except (error.UnknownCommand, error.AmbiguousCommand,
219 IndexError, getopt.GetoptError):
223 IndexError, getopt.GetoptError):
220 # Don't handle this here. We know the command is
224 # Don't handle this here. We know the command is
221 # invalid, but all we're worried about for now is that
225 # invalid, but all we're worried about for now is that
222 # it's not a command that server operators expect to
226 # it's not a command that server operators expect to
223 # be safe to offer to users in a sandbox.
227 # be safe to offer to users in a sandbox.
224 pass
228 pass
225 if realcmd == 'serve' and '--stdio' in cmdargs:
229 if realcmd == 'serve' and '--stdio' in cmdargs:
226 # We want to constrain 'hg serve --stdio' instances pretty
230 # We want to constrain 'hg serve --stdio' instances pretty
227 # closely, as many shared-ssh access tools want to grant
231 # closely, as many shared-ssh access tools want to grant
228 # access to run *only* 'hg -R $repo serve --stdio'. We
232 # access to run *only* 'hg -R $repo serve --stdio'. We
229 # restrict to exactly that set of arguments, and prohibit
233 # restrict to exactly that set of arguments, and prohibit
230 # any repo name that starts with '--' to prevent
234 # any repo name that starts with '--' to prevent
231 # shenanigans wherein a user does something like pass
235 # shenanigans wherein a user does something like pass
232 # --debugger or --config=ui.debugger=1 as a repo
236 # --debugger or --config=ui.debugger=1 as a repo
233 # name. This used to actually run the debugger.
237 # name. This used to actually run the debugger.
234 if (len(req.args) != 4 or
238 if (len(req.args) != 4 or
235 req.args[0] != '-R' or
239 req.args[0] != '-R' or
236 req.args[1].startswith('--') or
240 req.args[1].startswith('--') or
237 req.args[2] != 'serve' or
241 req.args[2] != 'serve' or
238 req.args[3] != '--stdio'):
242 req.args[3] != '--stdio'):
239 raise error.Abort(
243 raise error.Abort(
240 _('potentially unsafe serve --stdio invocation: %r') %
244 _('potentially unsafe serve --stdio invocation: %r') %
241 (req.args,))
245 (req.args,))
242
246
243 try:
247 try:
244 debugger = 'pdb'
248 debugger = 'pdb'
245 debugtrace = {
249 debugtrace = {
246 'pdb' : pdb.set_trace
250 'pdb' : pdb.set_trace
247 }
251 }
248 debugmortem = {
252 debugmortem = {
249 'pdb' : pdb.post_mortem
253 'pdb' : pdb.post_mortem
250 }
254 }
251
255
252 # read --config before doing anything else
256 # read --config before doing anything else
253 # (e.g. to change trust settings for reading .hg/hgrc)
257 # (e.g. to change trust settings for reading .hg/hgrc)
254 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
258 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
255
259
256 if req.repo:
260 if req.repo:
257 # copy configs that were passed on the cmdline (--config) to
261 # copy configs that were passed on the cmdline (--config) to
258 # the repo ui
262 # the repo ui
259 for sec, name, val in cfgs:
263 for sec, name, val in cfgs:
260 req.repo.ui.setconfig(sec, name, val, source='--config')
264 req.repo.ui.setconfig(sec, name, val, source='--config')
261
265
262 # developer config: ui.debugger
266 # developer config: ui.debugger
263 debugger = ui.config("ui", "debugger")
267 debugger = ui.config("ui", "debugger")
264 debugmod = pdb
268 debugmod = pdb
265 if not debugger or ui.plain():
269 if not debugger or ui.plain():
266 # if we are in HGPLAIN mode, then disable custom debugging
270 # if we are in HGPLAIN mode, then disable custom debugging
267 debugger = 'pdb'
271 debugger = 'pdb'
268 elif '--debugger' in req.args:
272 elif '--debugger' in req.args:
269 # This import can be slow for fancy debuggers, so only
273 # This import can be slow for fancy debuggers, so only
270 # do it when absolutely necessary, i.e. when actual
274 # do it when absolutely necessary, i.e. when actual
271 # debugging has been requested
275 # debugging has been requested
272 with demandimport.deactivated():
276 with demandimport.deactivated():
273 try:
277 try:
274 debugmod = __import__(debugger)
278 debugmod = __import__(debugger)
275 except ImportError:
279 except ImportError:
276 pass # Leave debugmod = pdb
280 pass # Leave debugmod = pdb
277
281
278 debugtrace[debugger] = debugmod.set_trace
282 debugtrace[debugger] = debugmod.set_trace
279 debugmortem[debugger] = debugmod.post_mortem
283 debugmortem[debugger] = debugmod.post_mortem
280
284
281 # enter the debugger before command execution
285 # enter the debugger before command execution
282 if '--debugger' in req.args:
286 if '--debugger' in req.args:
283 ui.warn(_("entering debugger - "
287 ui.warn(_("entering debugger - "
284 "type c to continue starting hg or h for help\n"))
288 "type c to continue starting hg or h for help\n"))
285
289
286 if (debugger != 'pdb' and
290 if (debugger != 'pdb' and
287 debugtrace[debugger] == debugtrace['pdb']):
291 debugtrace[debugger] == debugtrace['pdb']):
288 ui.warn(_("%s debugger specified "
292 ui.warn(_("%s debugger specified "
289 "but its module was not found\n") % debugger)
293 "but its module was not found\n") % debugger)
290 with demandimport.deactivated():
294 with demandimport.deactivated():
291 debugtrace[debugger]()
295 debugtrace[debugger]()
292 try:
296 try:
293 return _dispatch(req)
297 return _dispatch(req)
294 finally:
298 finally:
295 ui.flush()
299 ui.flush()
296 except: # re-raises
300 except: # re-raises
297 # enter the debugger when we hit an exception
301 # enter the debugger when we hit an exception
298 if '--debugger' in req.args:
302 if '--debugger' in req.args:
299 traceback.print_exc()
303 traceback.print_exc()
300 debugmortem[debugger](sys.exc_info()[2])
304 debugmortem[debugger](sys.exc_info()[2])
301 raise
305 raise
302
306
303 return _callcatch(ui, _runcatchfunc)
307 return _callcatch(ui, _runcatchfunc)
304
308
305 def _callcatch(ui, func):
309 def _callcatch(ui, func):
306 """like scmutil.callcatch but handles more high-level exceptions about
310 """like scmutil.callcatch but handles more high-level exceptions about
307 config parsing and commands. besides, use handlecommandexception to handle
311 config parsing and commands. besides, use handlecommandexception to handle
308 uncaught exceptions.
312 uncaught exceptions.
309 """
313 """
310 try:
314 try:
311 return scmutil.callcatch(ui, func)
315 return scmutil.callcatch(ui, func)
312 except error.AmbiguousCommand as inst:
316 except error.AmbiguousCommand as inst:
313 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
317 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
314 (inst.args[0], " ".join(inst.args[1])))
318 (inst.args[0], " ".join(inst.args[1])))
315 except error.CommandError as inst:
319 except error.CommandError as inst:
316 if inst.args[0]:
320 if inst.args[0]:
317 ui.pager('help')
321 ui.pager('help')
318 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
322 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
319 commands.help_(ui, inst.args[0], full=False, command=True)
323 commands.help_(ui, inst.args[0], full=False, command=True)
320 else:
324 else:
321 ui.pager('help')
325 ui.pager('help')
322 ui.warn(_("hg: %s\n") % inst.args[1])
326 ui.warn(_("hg: %s\n") % inst.args[1])
323 commands.help_(ui, 'shortlist')
327 commands.help_(ui, 'shortlist')
324 except error.ParseError as inst:
328 except error.ParseError as inst:
325 _formatparse(ui.warn, inst)
329 _formatparse(ui.warn, inst)
326 return -1
330 return -1
327 except error.UnknownCommand as inst:
331 except error.UnknownCommand as inst:
328 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
332 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
329 try:
333 try:
330 # check if the command is in a disabled extension
334 # check if the command is in a disabled extension
331 # (but don't check for extensions themselves)
335 # (but don't check for extensions themselves)
332 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
336 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
333 ui.warn(nocmdmsg)
337 ui.warn(nocmdmsg)
334 ui.write(formatted)
338 ui.write(formatted)
335 except (error.UnknownCommand, error.Abort):
339 except (error.UnknownCommand, error.Abort):
336 suggested = False
340 suggested = False
337 if len(inst.args) == 2:
341 if len(inst.args) == 2:
338 sim = _getsimilar(inst.args[1], inst.args[0])
342 sim = _getsimilar(inst.args[1], inst.args[0])
339 if sim:
343 if sim:
340 ui.warn(nocmdmsg)
344 ui.warn(nocmdmsg)
341 _reportsimilar(ui.warn, sim)
345 _reportsimilar(ui.warn, sim)
342 suggested = True
346 suggested = True
343 if not suggested:
347 if not suggested:
344 ui.pager('help')
348 ui.pager('help')
345 ui.warn(nocmdmsg)
349 ui.warn(nocmdmsg)
346 commands.help_(ui, 'shortlist')
350 commands.help_(ui, 'shortlist')
347 except IOError:
351 except IOError:
348 raise
352 raise
349 except KeyboardInterrupt:
353 except KeyboardInterrupt:
350 raise
354 raise
351 except: # probably re-raises
355 except: # probably re-raises
352 if not handlecommandexception(ui):
356 if not handlecommandexception(ui):
353 raise
357 raise
354
358
355 return -1
359 return -1
356
360
357 def aliasargs(fn, givenargs):
361 def aliasargs(fn, givenargs):
358 args = getattr(fn, 'args', [])
362 args = getattr(fn, 'args', [])
359 if args:
363 if args:
360 cmd = ' '.join(map(util.shellquote, args))
364 cmd = ' '.join(map(util.shellquote, args))
361
365
362 nums = []
366 nums = []
363 def replacer(m):
367 def replacer(m):
364 num = int(m.group(1)) - 1
368 num = int(m.group(1)) - 1
365 nums.append(num)
369 nums.append(num)
366 if num < len(givenargs):
370 if num < len(givenargs):
367 return givenargs[num]
371 return givenargs[num]
368 raise error.Abort(_('too few arguments for command alias'))
372 raise error.Abort(_('too few arguments for command alias'))
369 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
373 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
370 givenargs = [x for i, x in enumerate(givenargs)
374 givenargs = [x for i, x in enumerate(givenargs)
371 if i not in nums]
375 if i not in nums]
372 args = pycompat.shlexsplit(cmd)
376 args = pycompat.shlexsplit(cmd)
373 return args + givenargs
377 return args + givenargs
374
378
375 def aliasinterpolate(name, args, cmd):
379 def aliasinterpolate(name, args, cmd):
376 '''interpolate args into cmd for shell aliases
380 '''interpolate args into cmd for shell aliases
377
381
378 This also handles $0, $@ and "$@".
382 This also handles $0, $@ and "$@".
379 '''
383 '''
380 # util.interpolate can't deal with "$@" (with quotes) because it's only
384 # util.interpolate can't deal with "$@" (with quotes) because it's only
381 # built to match prefix + patterns.
385 # built to match prefix + patterns.
382 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
386 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
383 replacemap['$0'] = name
387 replacemap['$0'] = name
384 replacemap['$$'] = '$'
388 replacemap['$$'] = '$'
385 replacemap['$@'] = ' '.join(args)
389 replacemap['$@'] = ' '.join(args)
386 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
390 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
387 # parameters, separated out into words. Emulate the same behavior here by
391 # parameters, separated out into words. Emulate the same behavior here by
388 # quoting the arguments individually. POSIX shells will then typically
392 # quoting the arguments individually. POSIX shells will then typically
389 # tokenize each argument into exactly one word.
393 # tokenize each argument into exactly one word.
390 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
394 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
391 # escape '\$' for regex
395 # escape '\$' for regex
392 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
396 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
393 r = re.compile(regex)
397 r = re.compile(regex)
394 return r.sub(lambda x: replacemap[x.group()], cmd)
398 return r.sub(lambda x: replacemap[x.group()], cmd)
395
399
396 class cmdalias(object):
400 class cmdalias(object):
397 def __init__(self, name, definition, cmdtable, source):
401 def __init__(self, name, definition, cmdtable, source):
398 self.name = self.cmd = name
402 self.name = self.cmd = name
399 self.cmdname = ''
403 self.cmdname = ''
400 self.definition = definition
404 self.definition = definition
401 self.fn = None
405 self.fn = None
402 self.givenargs = []
406 self.givenargs = []
403 self.opts = []
407 self.opts = []
404 self.help = ''
408 self.help = ''
405 self.badalias = None
409 self.badalias = None
406 self.unknowncmd = False
410 self.unknowncmd = False
407 self.source = source
411 self.source = source
408
412
409 try:
413 try:
410 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
414 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
411 for alias, e in cmdtable.iteritems():
415 for alias, e in cmdtable.iteritems():
412 if e is entry:
416 if e is entry:
413 self.cmd = alias
417 self.cmd = alias
414 break
418 break
415 self.shadows = True
419 self.shadows = True
416 except error.UnknownCommand:
420 except error.UnknownCommand:
417 self.shadows = False
421 self.shadows = False
418
422
419 if not self.definition:
423 if not self.definition:
420 self.badalias = _("no definition for alias '%s'") % self.name
424 self.badalias = _("no definition for alias '%s'") % self.name
421 return
425 return
422
426
423 if self.definition.startswith('!'):
427 if self.definition.startswith('!'):
424 self.shell = True
428 self.shell = True
425 def fn(ui, *args):
429 def fn(ui, *args):
426 env = {'HG_ARGS': ' '.join((self.name,) + args)}
430 env = {'HG_ARGS': ' '.join((self.name,) + args)}
427 def _checkvar(m):
431 def _checkvar(m):
428 if m.groups()[0] == '$':
432 if m.groups()[0] == '$':
429 return m.group()
433 return m.group()
430 elif int(m.groups()[0]) <= len(args):
434 elif int(m.groups()[0]) <= len(args):
431 return m.group()
435 return m.group()
432 else:
436 else:
433 ui.debug("No argument found for substitution "
437 ui.debug("No argument found for substitution "
434 "of %i variable in alias '%s' definition."
438 "of %i variable in alias '%s' definition."
435 % (int(m.groups()[0]), self.name))
439 % (int(m.groups()[0]), self.name))
436 return ''
440 return ''
437 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
441 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
438 cmd = aliasinterpolate(self.name, args, cmd)
442 cmd = aliasinterpolate(self.name, args, cmd)
439 return ui.system(cmd, environ=env,
443 return ui.system(cmd, environ=env,
440 blockedtag='alias_%s' % self.name)
444 blockedtag='alias_%s' % self.name)
441 self.fn = fn
445 self.fn = fn
442 return
446 return
443
447
444 try:
448 try:
445 args = pycompat.shlexsplit(self.definition)
449 args = pycompat.shlexsplit(self.definition)
446 except ValueError as inst:
450 except ValueError as inst:
447 self.badalias = (_("error in definition for alias '%s': %s")
451 self.badalias = (_("error in definition for alias '%s': %s")
448 % (self.name, inst))
452 % (self.name, inst))
449 return
453 return
450 self.cmdname = cmd = args.pop(0)
454 self.cmdname = cmd = args.pop(0)
451 self.givenargs = args
455 self.givenargs = args
452
456
453 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
457 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
454 if _earlygetopt([invalidarg], args):
458 if _earlygetopt([invalidarg], args):
455 self.badalias = (_("error in definition for alias '%s': %s may "
459 self.badalias = (_("error in definition for alias '%s': %s may "
456 "only be given on the command line")
460 "only be given on the command line")
457 % (self.name, invalidarg))
461 % (self.name, invalidarg))
458 return
462 return
459
463
460 try:
464 try:
461 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
465 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
462 if len(tableentry) > 2:
466 if len(tableentry) > 2:
463 self.fn, self.opts, self.help = tableentry
467 self.fn, self.opts, self.help = tableentry
464 else:
468 else:
465 self.fn, self.opts = tableentry
469 self.fn, self.opts = tableentry
466
470
467 if self.help.startswith("hg " + cmd):
471 if self.help.startswith("hg " + cmd):
468 # drop prefix in old-style help lines so hg shows the alias
472 # drop prefix in old-style help lines so hg shows the alias
469 self.help = self.help[4 + len(cmd):]
473 self.help = self.help[4 + len(cmd):]
470 self.__doc__ = self.fn.__doc__
474 self.__doc__ = self.fn.__doc__
471
475
472 except error.UnknownCommand:
476 except error.UnknownCommand:
473 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
477 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
474 % (self.name, cmd))
478 % (self.name, cmd))
475 self.unknowncmd = True
479 self.unknowncmd = True
476 except error.AmbiguousCommand:
480 except error.AmbiguousCommand:
477 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
481 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
478 % (self.name, cmd))
482 % (self.name, cmd))
479
483
480 @property
484 @property
481 def args(self):
485 def args(self):
482 args = pycompat.maplist(util.expandpath, self.givenargs)
486 args = pycompat.maplist(util.expandpath, self.givenargs)
483 return aliasargs(self.fn, args)
487 return aliasargs(self.fn, args)
484
488
485 def __getattr__(self, name):
489 def __getattr__(self, name):
486 adefaults = {r'norepo': True,
490 adefaults = {r'norepo': True,
487 r'optionalrepo': False, r'inferrepo': False}
491 r'optionalrepo': False, r'inferrepo': False}
488 if name not in adefaults:
492 if name not in adefaults:
489 raise AttributeError(name)
493 raise AttributeError(name)
490 if self.badalias or util.safehasattr(self, 'shell'):
494 if self.badalias or util.safehasattr(self, 'shell'):
491 return adefaults[name]
495 return adefaults[name]
492 return getattr(self.fn, name)
496 return getattr(self.fn, name)
493
497
494 def __call__(self, ui, *args, **opts):
498 def __call__(self, ui, *args, **opts):
495 if self.badalias:
499 if self.badalias:
496 hint = None
500 hint = None
497 if self.unknowncmd:
501 if self.unknowncmd:
498 try:
502 try:
499 # check if the command is in a disabled extension
503 # check if the command is in a disabled extension
500 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
504 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
501 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
505 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
502 except error.UnknownCommand:
506 except error.UnknownCommand:
503 pass
507 pass
504 raise error.Abort(self.badalias, hint=hint)
508 raise error.Abort(self.badalias, hint=hint)
505 if self.shadows:
509 if self.shadows:
506 ui.debug("alias '%s' shadows command '%s'\n" %
510 ui.debug("alias '%s' shadows command '%s'\n" %
507 (self.name, self.cmdname))
511 (self.name, self.cmdname))
508
512
509 ui.log('commandalias', "alias '%s' expands to '%s'\n",
513 ui.log('commandalias', "alias '%s' expands to '%s'\n",
510 self.name, self.definition)
514 self.name, self.definition)
511 if util.safehasattr(self, 'shell'):
515 if util.safehasattr(self, 'shell'):
512 return self.fn(ui, *args, **opts)
516 return self.fn(ui, *args, **opts)
513 else:
517 else:
514 try:
518 try:
515 return util.checksignature(self.fn)(ui, *args, **opts)
519 return util.checksignature(self.fn)(ui, *args, **opts)
516 except error.SignatureError:
520 except error.SignatureError:
517 args = ' '.join([self.cmdname] + self.args)
521 args = ' '.join([self.cmdname] + self.args)
518 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
522 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
519 raise
523 raise
520
524
521 def addaliases(ui, cmdtable):
525 def addaliases(ui, cmdtable):
522 # aliases are processed after extensions have been loaded, so they
526 # aliases are processed after extensions have been loaded, so they
523 # may use extension commands. Aliases can also use other alias definitions,
527 # may use extension commands. Aliases can also use other alias definitions,
524 # but only if they have been defined prior to the current definition.
528 # but only if they have been defined prior to the current definition.
525 for alias, definition in ui.configitems('alias'):
529 for alias, definition in ui.configitems('alias'):
526 source = ui.configsource('alias', alias)
530 source = ui.configsource('alias', alias)
527 aliasdef = cmdalias(alias, definition, cmdtable, source)
531 aliasdef = cmdalias(alias, definition, cmdtable, source)
528
532
529 try:
533 try:
530 olddef = cmdtable[aliasdef.cmd][0]
534 olddef = cmdtable[aliasdef.cmd][0]
531 if olddef.definition == aliasdef.definition:
535 if olddef.definition == aliasdef.definition:
532 continue
536 continue
533 except (KeyError, AttributeError):
537 except (KeyError, AttributeError):
534 # definition might not exist or it might not be a cmdalias
538 # definition might not exist or it might not be a cmdalias
535 pass
539 pass
536
540
537 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
541 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
538
542
539 def _parse(ui, args):
543 def _parse(ui, args):
540 options = {}
544 options = {}
541 cmdoptions = {}
545 cmdoptions = {}
542
546
543 try:
547 try:
544 args = fancyopts.fancyopts(args, commands.globalopts, options)
548 args = fancyopts.fancyopts(args, commands.globalopts, options)
545 except getopt.GetoptError as inst:
549 except getopt.GetoptError as inst:
546 raise error.CommandError(None, inst)
550 raise error.CommandError(None, inst)
547
551
548 if args:
552 if args:
549 cmd, args = args[0], args[1:]
553 cmd, args = args[0], args[1:]
550 aliases, entry = cmdutil.findcmd(cmd, commands.table,
554 aliases, entry = cmdutil.findcmd(cmd, commands.table,
551 ui.configbool("ui", "strict"))
555 ui.configbool("ui", "strict"))
552 cmd = aliases[0]
556 cmd = aliases[0]
553 args = aliasargs(entry[0], args)
557 args = aliasargs(entry[0], args)
554 defaults = ui.config("defaults", cmd)
558 defaults = ui.config("defaults", cmd)
555 if defaults:
559 if defaults:
556 args = pycompat.maplist(
560 args = pycompat.maplist(
557 util.expandpath, pycompat.shlexsplit(defaults)) + args
561 util.expandpath, pycompat.shlexsplit(defaults)) + args
558 c = list(entry[1])
562 c = list(entry[1])
559 else:
563 else:
560 cmd = None
564 cmd = None
561 c = []
565 c = []
562
566
563 # combine global options into local
567 # combine global options into local
564 for o in commands.globalopts:
568 for o in commands.globalopts:
565 c.append((o[0], o[1], options[o[1]], o[3]))
569 c.append((o[0], o[1], options[o[1]], o[3]))
566
570
567 try:
571 try:
568 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
572 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
569 except getopt.GetoptError as inst:
573 except getopt.GetoptError as inst:
570 raise error.CommandError(cmd, inst)
574 raise error.CommandError(cmd, inst)
571
575
572 # separate global options back out
576 # separate global options back out
573 for o in commands.globalopts:
577 for o in commands.globalopts:
574 n = o[1]
578 n = o[1]
575 options[n] = cmdoptions[n]
579 options[n] = cmdoptions[n]
576 del cmdoptions[n]
580 del cmdoptions[n]
577
581
578 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
582 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
579
583
580 def _parseconfig(ui, config):
584 def _parseconfig(ui, config):
581 """parse the --config options from the command line"""
585 """parse the --config options from the command line"""
582 configs = []
586 configs = []
583
587
584 for cfg in config:
588 for cfg in config:
585 try:
589 try:
586 name, value = [cfgelem.strip()
590 name, value = [cfgelem.strip()
587 for cfgelem in cfg.split('=', 1)]
591 for cfgelem in cfg.split('=', 1)]
588 section, name = name.split('.', 1)
592 section, name = name.split('.', 1)
589 if not section or not name:
593 if not section or not name:
590 raise IndexError
594 raise IndexError
591 ui.setconfig(section, name, value, '--config')
595 ui.setconfig(section, name, value, '--config')
592 configs.append((section, name, value))
596 configs.append((section, name, value))
593 except (IndexError, ValueError):
597 except (IndexError, ValueError):
594 raise error.Abort(_('malformed --config option: %r '
598 raise error.Abort(_('malformed --config option: %r '
595 '(use --config section.name=value)') % cfg)
599 '(use --config section.name=value)') % cfg)
596
600
597 return configs
601 return configs
598
602
599 def _earlygetopt(aliases, args):
603 def _earlygetopt(aliases, args):
600 """Return list of values for an option (or aliases).
604 """Return list of values for an option (or aliases).
601
605
602 The values are listed in the order they appear in args.
606 The values are listed in the order they appear in args.
603 The options and values are removed from args.
607 The options and values are removed from args.
604
608
605 >>> args = ['x', '--cwd', 'foo', 'y']
609 >>> args = ['x', '--cwd', 'foo', 'y']
606 >>> _earlygetopt(['--cwd'], args), args
610 >>> _earlygetopt(['--cwd'], args), args
607 (['foo'], ['x', 'y'])
611 (['foo'], ['x', 'y'])
608
612
609 >>> args = ['x', '--cwd=bar', 'y']
613 >>> args = ['x', '--cwd=bar', 'y']
610 >>> _earlygetopt(['--cwd'], args), args
614 >>> _earlygetopt(['--cwd'], args), args
611 (['bar'], ['x', 'y'])
615 (['bar'], ['x', 'y'])
612
616
613 >>> args = ['x', '-R', 'foo', 'y']
617 >>> args = ['x', '-R', 'foo', 'y']
614 >>> _earlygetopt(['-R'], args), args
618 >>> _earlygetopt(['-R'], args), args
615 (['foo'], ['x', 'y'])
619 (['foo'], ['x', 'y'])
616
620
617 >>> args = ['x', '-Rbar', 'y']
621 >>> args = ['x', '-Rbar', 'y']
618 >>> _earlygetopt(['-R'], args), args
622 >>> _earlygetopt(['-R'], args), args
619 (['bar'], ['x', 'y'])
623 (['bar'], ['x', 'y'])
620 """
624 """
621 try:
625 try:
622 argcount = args.index("--")
626 argcount = args.index("--")
623 except ValueError:
627 except ValueError:
624 argcount = len(args)
628 argcount = len(args)
625 shortopts = [opt for opt in aliases if len(opt) == 2]
629 shortopts = [opt for opt in aliases if len(opt) == 2]
626 values = []
630 values = []
627 pos = 0
631 pos = 0
628 while pos < argcount:
632 while pos < argcount:
629 fullarg = arg = args[pos]
633 fullarg = arg = args[pos]
630 equals = arg.find('=')
634 equals = arg.find('=')
631 if equals > -1:
635 if equals > -1:
632 arg = arg[:equals]
636 arg = arg[:equals]
633 if arg in aliases:
637 if arg in aliases:
634 del args[pos]
638 del args[pos]
635 if equals > -1:
639 if equals > -1:
636 values.append(fullarg[equals + 1:])
640 values.append(fullarg[equals + 1:])
637 argcount -= 1
641 argcount -= 1
638 else:
642 else:
639 if pos + 1 >= argcount:
643 if pos + 1 >= argcount:
640 # ignore and let getopt report an error if there is no value
644 # ignore and let getopt report an error if there is no value
641 break
645 break
642 values.append(args.pop(pos))
646 values.append(args.pop(pos))
643 argcount -= 2
647 argcount -= 2
644 elif arg[:2] in shortopts:
648 elif arg[:2] in shortopts:
645 # short option can have no following space, e.g. hg log -Rfoo
649 # short option can have no following space, e.g. hg log -Rfoo
646 values.append(args.pop(pos)[2:])
650 values.append(args.pop(pos)[2:])
647 argcount -= 1
651 argcount -= 1
648 else:
652 else:
649 pos += 1
653 pos += 1
650 return values
654 return values
651
655
652 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
656 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
653 # run pre-hook, and abort if it fails
657 # run pre-hook, and abort if it fails
654 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
658 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
655 pats=cmdpats, opts=cmdoptions)
659 pats=cmdpats, opts=cmdoptions)
656 try:
660 try:
657 ret = _runcommand(ui, options, cmd, d)
661 ret = _runcommand(ui, options, cmd, d)
658 # run post-hook, passing command result
662 # run post-hook, passing command result
659 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
663 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
660 result=ret, pats=cmdpats, opts=cmdoptions)
664 result=ret, pats=cmdpats, opts=cmdoptions)
661 except Exception:
665 except Exception:
662 # run failure hook and re-raise
666 # run failure hook and re-raise
663 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
667 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
664 pats=cmdpats, opts=cmdoptions)
668 pats=cmdpats, opts=cmdoptions)
665 raise
669 raise
666 return ret
670 return ret
667
671
668 def _getlocal(ui, rpath, wd=None):
672 def _getlocal(ui, rpath, wd=None):
669 """Return (path, local ui object) for the given target path.
673 """Return (path, local ui object) for the given target path.
670
674
671 Takes paths in [cwd]/.hg/hgrc into account."
675 Takes paths in [cwd]/.hg/hgrc into account."
672 """
676 """
673 if wd is None:
677 if wd is None:
674 try:
678 try:
675 wd = pycompat.getcwd()
679 wd = pycompat.getcwd()
676 except OSError as e:
680 except OSError as e:
677 raise error.Abort(_("error getting current working directory: %s") %
681 raise error.Abort(_("error getting current working directory: %s") %
678 e.strerror)
682 e.strerror)
679 path = cmdutil.findrepo(wd) or ""
683 path = cmdutil.findrepo(wd) or ""
680 if not path:
684 if not path:
681 lui = ui
685 lui = ui
682 else:
686 else:
683 lui = ui.copy()
687 lui = ui.copy()
684 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
688 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
685
689
686 if rpath and rpath[-1]:
690 if rpath and rpath[-1]:
687 path = lui.expandpath(rpath[-1])
691 path = lui.expandpath(rpath[-1])
688 lui = ui.copy()
692 lui = ui.copy()
689 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
693 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
690
694
691 return path, lui
695 return path, lui
692
696
693 def _checkshellalias(lui, ui, args):
697 def _checkshellalias(lui, ui, args):
694 """Return the function to run the shell alias, if it is required"""
698 """Return the function to run the shell alias, if it is required"""
695 options = {}
699 options = {}
696
700
697 try:
701 try:
698 args = fancyopts.fancyopts(args, commands.globalopts, options)
702 args = fancyopts.fancyopts(args, commands.globalopts, options)
699 except getopt.GetoptError:
703 except getopt.GetoptError:
700 return
704 return
701
705
702 if not args:
706 if not args:
703 return
707 return
704
708
705 cmdtable = commands.table
709 cmdtable = commands.table
706
710
707 cmd = args[0]
711 cmd = args[0]
708 try:
712 try:
709 strict = ui.configbool("ui", "strict")
713 strict = ui.configbool("ui", "strict")
710 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
714 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
711 except (error.AmbiguousCommand, error.UnknownCommand):
715 except (error.AmbiguousCommand, error.UnknownCommand):
712 return
716 return
713
717
714 cmd = aliases[0]
718 cmd = aliases[0]
715 fn = entry[0]
719 fn = entry[0]
716
720
717 if cmd and util.safehasattr(fn, 'shell'):
721 if cmd and util.safehasattr(fn, 'shell'):
718 d = lambda: fn(ui, *args[1:])
722 d = lambda: fn(ui, *args[1:])
719 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
723 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
720 [], {})
724 [], {})
721
725
722 _loaded = set()
726 _loaded = set()
723
727
724 # list of (objname, loadermod, loadername) tuple:
728 # list of (objname, loadermod, loadername) tuple:
725 # - objname is the name of an object in extension module, from which
729 # - objname is the name of an object in extension module, from which
726 # extra information is loaded
730 # extra information is loaded
727 # - loadermod is the module where loader is placed
731 # - loadermod is the module where loader is placed
728 # - loadername is the name of the function, which takes (ui, extensionname,
732 # - loadername is the name of the function, which takes (ui, extensionname,
729 # extraobj) arguments
733 # extraobj) arguments
730 extraloaders = [
734 extraloaders = [
731 ('cmdtable', commands, 'loadcmdtable'),
735 ('cmdtable', commands, 'loadcmdtable'),
732 ('colortable', color, 'loadcolortable'),
736 ('colortable', color, 'loadcolortable'),
733 ('filesetpredicate', fileset, 'loadpredicate'),
737 ('filesetpredicate', fileset, 'loadpredicate'),
734 ('revsetpredicate', revset, 'loadpredicate'),
738 ('revsetpredicate', revset, 'loadpredicate'),
735 ('templatefilter', templatefilters, 'loadfilter'),
739 ('templatefilter', templatefilters, 'loadfilter'),
736 ('templatefunc', templater, 'loadfunction'),
740 ('templatefunc', templater, 'loadfunction'),
737 ('templatekeyword', templatekw, 'loadkeyword'),
741 ('templatekeyword', templatekw, 'loadkeyword'),
738 ]
742 ]
739
743
740 def _dispatch(req):
744 def _dispatch(req):
741 args = req.args
745 args = req.args
742 ui = req.ui
746 ui = req.ui
743
747
744 # check for cwd
748 # check for cwd
745 cwd = _earlygetopt(['--cwd'], args)
749 cwd = _earlygetopt(['--cwd'], args)
746 if cwd:
750 if cwd:
747 os.chdir(cwd[-1])
751 os.chdir(cwd[-1])
748
752
749 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
753 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
750 path, lui = _getlocal(ui, rpath)
754 path, lui = _getlocal(ui, rpath)
751
755
752 uis = {ui, lui}
756 uis = {ui, lui}
753
757
754 if req.repo:
758 if req.repo:
755 uis.add(req.repo.ui)
759 uis.add(req.repo.ui)
756
760
757 if '--profile' in args:
761 if '--profile' in args:
758 for ui_ in uis:
762 for ui_ in uis:
759 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
763 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
760
764
761 with profiling.maybeprofile(lui):
765 with profiling.maybeprofile(lui):
762 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
766 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
763 # reposetup. Programs like TortoiseHg will call _dispatch several
767 # reposetup. Programs like TortoiseHg will call _dispatch several
764 # times so we keep track of configured extensions in _loaded.
768 # times so we keep track of configured extensions in _loaded.
765 extensions.loadall(lui)
769 extensions.loadall(lui)
766 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
770 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
767 # Propagate any changes to lui.__class__ by extensions
771 # Propagate any changes to lui.__class__ by extensions
768 ui.__class__ = lui.__class__
772 ui.__class__ = lui.__class__
769
773
770 # (uisetup and extsetup are handled in extensions.loadall)
774 # (uisetup and extsetup are handled in extensions.loadall)
771
775
772 for name, module in exts:
776 for name, module in exts:
773 for objname, loadermod, loadername in extraloaders:
777 for objname, loadermod, loadername in extraloaders:
774 extraobj = getattr(module, objname, None)
778 extraobj = getattr(module, objname, None)
775 if extraobj is not None:
779 if extraobj is not None:
776 getattr(loadermod, loadername)(ui, name, extraobj)
780 getattr(loadermod, loadername)(ui, name, extraobj)
777 _loaded.add(name)
781 _loaded.add(name)
778
782
779 # (reposetup is handled in hg.repository)
783 # (reposetup is handled in hg.repository)
780
784
781 addaliases(lui, commands.table)
785 addaliases(lui, commands.table)
782
786
783 # All aliases and commands are completely defined, now.
787 # All aliases and commands are completely defined, now.
784 # Check abbreviation/ambiguity of shell alias.
788 # Check abbreviation/ambiguity of shell alias.
785 shellaliasfn = _checkshellalias(lui, ui, args)
789 shellaliasfn = _checkshellalias(lui, ui, args)
786 if shellaliasfn:
790 if shellaliasfn:
787 return shellaliasfn()
791 return shellaliasfn()
788
792
789 # check for fallback encoding
793 # check for fallback encoding
790 fallback = lui.config('ui', 'fallbackencoding')
794 fallback = lui.config('ui', 'fallbackencoding')
791 if fallback:
795 if fallback:
792 encoding.fallbackencoding = fallback
796 encoding.fallbackencoding = fallback
793
797
794 fullargs = args
798 fullargs = args
795 cmd, func, args, options, cmdoptions = _parse(lui, args)
799 cmd, func, args, options, cmdoptions = _parse(lui, args)
796
800
797 if options["config"]:
801 if options["config"]:
798 raise error.Abort(_("option --config may not be abbreviated!"))
802 raise error.Abort(_("option --config may not be abbreviated!"))
799 if options["cwd"]:
803 if options["cwd"]:
800 raise error.Abort(_("option --cwd may not be abbreviated!"))
804 raise error.Abort(_("option --cwd may not be abbreviated!"))
801 if options["repository"]:
805 if options["repository"]:
802 raise error.Abort(_(
806 raise error.Abort(_(
803 "option -R has to be separated from other options (e.g. not "
807 "option -R has to be separated from other options (e.g. not "
804 "-qR) and --repository may only be abbreviated as --repo!"))
808 "-qR) and --repository may only be abbreviated as --repo!"))
805
809
806 if options["encoding"]:
810 if options["encoding"]:
807 encoding.encoding = options["encoding"]
811 encoding.encoding = options["encoding"]
808 if options["encodingmode"]:
812 if options["encodingmode"]:
809 encoding.encodingmode = options["encodingmode"]
813 encoding.encodingmode = options["encodingmode"]
810 if options["time"]:
814 if options["time"]:
811 def get_times():
815 def get_times():
812 t = os.times()
816 t = os.times()
813 if t[4] == 0.0:
817 if t[4] == 0.0:
814 # Windows leaves this as zero, so use time.clock()
818 # Windows leaves this as zero, so use time.clock()
815 t = (t[0], t[1], t[2], t[3], time.clock())
819 t = (t[0], t[1], t[2], t[3], time.clock())
816 return t
820 return t
817 s = get_times()
821 s = get_times()
818 def print_time():
822 def print_time():
819 t = get_times()
823 t = get_times()
820 ui.warn(
824 ui.warn(
821 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
825 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
822 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
826 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
823 ui.atexit(print_time)
827 ui.atexit(print_time)
824
828
825 if options['verbose'] or options['debug'] or options['quiet']:
829 if options['verbose'] or options['debug'] or options['quiet']:
826 for opt in ('verbose', 'debug', 'quiet'):
830 for opt in ('verbose', 'debug', 'quiet'):
827 val = str(bool(options[opt]))
831 val = str(bool(options[opt]))
828 if pycompat.ispy3:
832 if pycompat.ispy3:
829 val = val.encode('ascii')
833 val = val.encode('ascii')
830 for ui_ in uis:
834 for ui_ in uis:
831 ui_.setconfig('ui', opt, val, '--' + opt)
835 ui_.setconfig('ui', opt, val, '--' + opt)
832
836
833 if options['traceback']:
837 if options['traceback']:
834 for ui_ in uis:
838 for ui_ in uis:
835 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
839 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
836
840
837 if options['noninteractive']:
841 if options['noninteractive']:
838 for ui_ in uis:
842 for ui_ in uis:
839 ui_.setconfig('ui', 'interactive', 'off', '-y')
843 ui_.setconfig('ui', 'interactive', 'off', '-y')
840
844
841 if util.parsebool(options['pager']):
845 if util.parsebool(options['pager']):
842 ui.pager('internal-always-' + cmd)
846 ui.pager('internal-always-' + cmd)
843 elif options['pager'] != 'auto':
847 elif options['pager'] != 'auto':
844 ui.disablepager()
848 ui.disablepager()
845
849
846 if cmdoptions.get('insecure', False):
850 if cmdoptions.get('insecure', False):
847 for ui_ in uis:
851 for ui_ in uis:
848 ui_.insecureconnections = True
852 ui_.insecureconnections = True
849
853
850 # setup color handling
854 # setup color handling
851 coloropt = options['color']
855 coloropt = options['color']
852 for ui_ in uis:
856 for ui_ in uis:
853 if coloropt:
857 if coloropt:
854 ui_.setconfig('ui', 'color', coloropt, '--color')
858 ui_.setconfig('ui', 'color', coloropt, '--color')
855 color.setup(ui_)
859 color.setup(ui_)
856
860
857 if options['version']:
861 if options['version']:
858 return commands.version_(ui)
862 return commands.version_(ui)
859 if options['help']:
863 if options['help']:
860 return commands.help_(ui, cmd, command=cmd is not None)
864 return commands.help_(ui, cmd, command=cmd is not None)
861 elif not cmd:
865 elif not cmd:
862 return commands.help_(ui, 'shortlist')
866 return commands.help_(ui, 'shortlist')
863
867
864 repo = None
868 repo = None
865 cmdpats = args[:]
869 cmdpats = args[:]
866 if not func.norepo:
870 if not func.norepo:
867 # use the repo from the request only if we don't have -R
871 # use the repo from the request only if we don't have -R
868 if not rpath and not cwd:
872 if not rpath and not cwd:
869 repo = req.repo
873 repo = req.repo
870
874
871 if repo:
875 if repo:
872 # set the descriptors of the repo ui to those of ui
876 # set the descriptors of the repo ui to those of ui
873 repo.ui.fin = ui.fin
877 repo.ui.fin = ui.fin
874 repo.ui.fout = ui.fout
878 repo.ui.fout = ui.fout
875 repo.ui.ferr = ui.ferr
879 repo.ui.ferr = ui.ferr
876 else:
880 else:
877 try:
881 try:
878 repo = hg.repository(ui, path=path)
882 repo = hg.repository(ui, path=path,
883 presetupfuncs=req.prereposetups)
879 if not repo.local():
884 if not repo.local():
880 raise error.Abort(_("repository '%s' is not local")
885 raise error.Abort(_("repository '%s' is not local")
881 % path)
886 % path)
882 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
887 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
883 'repo')
888 'repo')
884 except error.RequirementError:
889 except error.RequirementError:
885 raise
890 raise
886 except error.RepoError:
891 except error.RepoError:
887 if rpath and rpath[-1]: # invalid -R path
892 if rpath and rpath[-1]: # invalid -R path
888 raise
893 raise
889 if not func.optionalrepo:
894 if not func.optionalrepo:
890 if func.inferrepo and args and not path:
895 if func.inferrepo and args and not path:
891 # try to infer -R from command args
896 # try to infer -R from command args
892 repos = map(cmdutil.findrepo, args)
897 repos = map(cmdutil.findrepo, args)
893 guess = repos[0]
898 guess = repos[0]
894 if guess and repos.count(guess) == len(repos):
899 if guess and repos.count(guess) == len(repos):
895 req.args = ['--repository', guess] + fullargs
900 req.args = ['--repository', guess] + fullargs
896 return _dispatch(req)
901 return _dispatch(req)
897 if not path:
902 if not path:
898 raise error.RepoError(_("no repository found in"
903 raise error.RepoError(_("no repository found in"
899 " '%s' (.hg not found)")
904 " '%s' (.hg not found)")
900 % pycompat.getcwd())
905 % pycompat.getcwd())
901 raise
906 raise
902 if repo:
907 if repo:
903 ui = repo.ui
908 ui = repo.ui
904 if options['hidden']:
909 if options['hidden']:
905 repo = repo.unfiltered()
910 repo = repo.unfiltered()
906 args.insert(0, repo)
911 args.insert(0, repo)
907 elif rpath:
912 elif rpath:
908 ui.warn(_("warning: --repository ignored\n"))
913 ui.warn(_("warning: --repository ignored\n"))
909
914
910 msg = _formatargs(fullargs)
915 msg = _formatargs(fullargs)
911 ui.log("command", '%s\n', msg)
916 ui.log("command", '%s\n', msg)
912 strcmdopt = pycompat.strkwargs(cmdoptions)
917 strcmdopt = pycompat.strkwargs(cmdoptions)
913 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
918 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
914 try:
919 try:
915 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
920 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
916 cmdpats, cmdoptions)
921 cmdpats, cmdoptions)
917 finally:
922 finally:
918 if repo and repo != req.repo:
923 if repo and repo != req.repo:
919 repo.close()
924 repo.close()
920
925
921 def _runcommand(ui, options, cmd, cmdfunc):
926 def _runcommand(ui, options, cmd, cmdfunc):
922 """Run a command function, possibly with profiling enabled."""
927 """Run a command function, possibly with profiling enabled."""
923 try:
928 try:
924 return cmdfunc()
929 return cmdfunc()
925 except error.SignatureError:
930 except error.SignatureError:
926 raise error.CommandError(cmd, _('invalid arguments'))
931 raise error.CommandError(cmd, _('invalid arguments'))
927
932
928 def _exceptionwarning(ui):
933 def _exceptionwarning(ui):
929 """Produce a warning message for the current active exception"""
934 """Produce a warning message for the current active exception"""
930
935
931 # For compatibility checking, we discard the portion of the hg
936 # For compatibility checking, we discard the portion of the hg
932 # version after the + on the assumption that if a "normal
937 # version after the + on the assumption that if a "normal
933 # user" is running a build with a + in it the packager
938 # user" is running a build with a + in it the packager
934 # probably built from fairly close to a tag and anyone with a
939 # probably built from fairly close to a tag and anyone with a
935 # 'make local' copy of hg (where the version number can be out
940 # 'make local' copy of hg (where the version number can be out
936 # of date) will be clueful enough to notice the implausible
941 # of date) will be clueful enough to notice the implausible
937 # version number and try updating.
942 # version number and try updating.
938 ct = util.versiontuple(n=2)
943 ct = util.versiontuple(n=2)
939 worst = None, ct, ''
944 worst = None, ct, ''
940 if ui.config('ui', 'supportcontact', None) is None:
945 if ui.config('ui', 'supportcontact', None) is None:
941 for name, mod in extensions.extensions():
946 for name, mod in extensions.extensions():
942 testedwith = getattr(mod, 'testedwith', '')
947 testedwith = getattr(mod, 'testedwith', '')
943 if pycompat.ispy3 and isinstance(testedwith, str):
948 if pycompat.ispy3 and isinstance(testedwith, str):
944 testedwith = testedwith.encode(u'utf-8')
949 testedwith = testedwith.encode(u'utf-8')
945 report = getattr(mod, 'buglink', _('the extension author.'))
950 report = getattr(mod, 'buglink', _('the extension author.'))
946 if not testedwith.strip():
951 if not testedwith.strip():
947 # We found an untested extension. It's likely the culprit.
952 # We found an untested extension. It's likely the culprit.
948 worst = name, 'unknown', report
953 worst = name, 'unknown', report
949 break
954 break
950
955
951 # Never blame on extensions bundled with Mercurial.
956 # Never blame on extensions bundled with Mercurial.
952 if extensions.ismoduleinternal(mod):
957 if extensions.ismoduleinternal(mod):
953 continue
958 continue
954
959
955 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
960 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
956 if ct in tested:
961 if ct in tested:
957 continue
962 continue
958
963
959 lower = [t for t in tested if t < ct]
964 lower = [t for t in tested if t < ct]
960 nearest = max(lower or tested)
965 nearest = max(lower or tested)
961 if worst[0] is None or nearest < worst[1]:
966 if worst[0] is None or nearest < worst[1]:
962 worst = name, nearest, report
967 worst = name, nearest, report
963 if worst[0] is not None:
968 if worst[0] is not None:
964 name, testedwith, report = worst
969 name, testedwith, report = worst
965 if not isinstance(testedwith, (bytes, str)):
970 if not isinstance(testedwith, (bytes, str)):
966 testedwith = '.'.join([str(c) for c in testedwith])
971 testedwith = '.'.join([str(c) for c in testedwith])
967 warning = (_('** Unknown exception encountered with '
972 warning = (_('** Unknown exception encountered with '
968 'possibly-broken third-party extension %s\n'
973 'possibly-broken third-party extension %s\n'
969 '** which supports versions %s of Mercurial.\n'
974 '** which supports versions %s of Mercurial.\n'
970 '** Please disable %s and try your action again.\n'
975 '** Please disable %s and try your action again.\n'
971 '** If that fixes the bug please report it to %s\n')
976 '** If that fixes the bug please report it to %s\n')
972 % (name, testedwith, name, report))
977 % (name, testedwith, name, report))
973 else:
978 else:
974 bugtracker = ui.config('ui', 'supportcontact', None)
979 bugtracker = ui.config('ui', 'supportcontact', None)
975 if bugtracker is None:
980 if bugtracker is None:
976 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
981 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
977 warning = (_("** unknown exception encountered, "
982 warning = (_("** unknown exception encountered, "
978 "please report by visiting\n** ") + bugtracker + '\n')
983 "please report by visiting\n** ") + bugtracker + '\n')
979 if pycompat.ispy3:
984 if pycompat.ispy3:
980 sysversion = sys.version.encode(u'utf-8')
985 sysversion = sys.version.encode(u'utf-8')
981 else:
986 else:
982 sysversion = sys.version
987 sysversion = sys.version
983 sysversion = sysversion.replace('\n', '')
988 sysversion = sysversion.replace('\n', '')
984 warning += ((_("** Python %s\n") % sysversion) +
989 warning += ((_("** Python %s\n") % sysversion) +
985 (_("** Mercurial Distributed SCM (version %s)\n") %
990 (_("** Mercurial Distributed SCM (version %s)\n") %
986 util.version()) +
991 util.version()) +
987 (_("** Extensions loaded: %s\n") %
992 (_("** Extensions loaded: %s\n") %
988 ", ".join([x[0] for x in extensions.extensions()])))
993 ", ".join([x[0] for x in extensions.extensions()])))
989 return warning
994 return warning
990
995
991 def handlecommandexception(ui):
996 def handlecommandexception(ui):
992 """Produce a warning message for broken commands
997 """Produce a warning message for broken commands
993
998
994 Called when handling an exception; the exception is reraised if
999 Called when handling an exception; the exception is reraised if
995 this function returns False, ignored otherwise.
1000 this function returns False, ignored otherwise.
996 """
1001 """
997 warning = _exceptionwarning(ui)
1002 warning = _exceptionwarning(ui)
998 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
1003 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
999 ui.warn(warning)
1004 ui.warn(warning)
1000 return False # re-raise the exception
1005 return False # re-raise the exception
@@ -1,1052 +1,1054 b''
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 __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import shutil
14 import shutil
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18
18
19 from . import (
19 from . import (
20 bookmarks,
20 bookmarks,
21 bundlerepo,
21 bundlerepo,
22 cmdutil,
22 cmdutil,
23 destutil,
23 destutil,
24 discovery,
24 discovery,
25 error,
25 error,
26 exchange,
26 exchange,
27 extensions,
27 extensions,
28 httppeer,
28 httppeer,
29 localrepo,
29 localrepo,
30 lock,
30 lock,
31 merge as mergemod,
31 merge as mergemod,
32 node,
32 node,
33 phases,
33 phases,
34 repoview,
34 repoview,
35 scmutil,
35 scmutil,
36 sshpeer,
36 sshpeer,
37 statichttprepo,
37 statichttprepo,
38 ui as uimod,
38 ui as uimod,
39 unionrepo,
39 unionrepo,
40 url,
40 url,
41 util,
41 util,
42 verify as verifymod,
42 verify as verifymod,
43 vfs as vfsmod,
43 vfs as vfsmod,
44 )
44 )
45
45
46 release = lock.release
46 release = lock.release
47
47
48 # shared features
48 # shared features
49 sharedbookmarks = 'bookmarks'
49 sharedbookmarks = 'bookmarks'
50
50
51 def _local(path):
51 def _local(path):
52 path = util.expandpath(util.urllocalpath(path))
52 path = util.expandpath(util.urllocalpath(path))
53 return (os.path.isfile(path) and bundlerepo or localrepo)
53 return (os.path.isfile(path) and bundlerepo or localrepo)
54
54
55 def addbranchrevs(lrepo, other, branches, revs):
55 def addbranchrevs(lrepo, other, branches, revs):
56 peer = other.peer() # a courtesy to callers using a localrepo for other
56 peer = other.peer() # a courtesy to callers using a localrepo for other
57 hashbranch, branches = branches
57 hashbranch, branches = branches
58 if not hashbranch and not branches:
58 if not hashbranch and not branches:
59 x = revs or None
59 x = revs or None
60 if util.safehasattr(revs, 'first'):
60 if util.safehasattr(revs, 'first'):
61 y = revs.first()
61 y = revs.first()
62 elif revs:
62 elif revs:
63 y = revs[0]
63 y = revs[0]
64 else:
64 else:
65 y = None
65 y = None
66 return x, y
66 return x, y
67 if revs:
67 if revs:
68 revs = list(revs)
68 revs = list(revs)
69 else:
69 else:
70 revs = []
70 revs = []
71
71
72 if not peer.capable('branchmap'):
72 if not peer.capable('branchmap'):
73 if branches:
73 if branches:
74 raise error.Abort(_("remote branch lookup not supported"))
74 raise error.Abort(_("remote branch lookup not supported"))
75 revs.append(hashbranch)
75 revs.append(hashbranch)
76 return revs, revs[0]
76 return revs, revs[0]
77 branchmap = peer.branchmap()
77 branchmap = peer.branchmap()
78
78
79 def primary(branch):
79 def primary(branch):
80 if branch == '.':
80 if branch == '.':
81 if not lrepo:
81 if not lrepo:
82 raise error.Abort(_("dirstate branch not accessible"))
82 raise error.Abort(_("dirstate branch not accessible"))
83 branch = lrepo.dirstate.branch()
83 branch = lrepo.dirstate.branch()
84 if branch in branchmap:
84 if branch in branchmap:
85 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
85 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
86 return True
86 return True
87 else:
87 else:
88 return False
88 return False
89
89
90 for branch in branches:
90 for branch in branches:
91 if not primary(branch):
91 if not primary(branch):
92 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
92 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
93 if hashbranch:
93 if hashbranch:
94 if not primary(hashbranch):
94 if not primary(hashbranch):
95 revs.append(hashbranch)
95 revs.append(hashbranch)
96 return revs, revs[0]
96 return revs, revs[0]
97
97
98 def parseurl(path, branches=None):
98 def parseurl(path, branches=None):
99 '''parse url#branch, returning (url, (branch, branches))'''
99 '''parse url#branch, returning (url, (branch, branches))'''
100
100
101 u = util.url(path)
101 u = util.url(path)
102 branch = None
102 branch = None
103 if u.fragment:
103 if u.fragment:
104 branch = u.fragment
104 branch = u.fragment
105 u.fragment = None
105 u.fragment = None
106 return bytes(u), (branch, branches or [])
106 return bytes(u), (branch, branches or [])
107
107
108 schemes = {
108 schemes = {
109 'bundle': bundlerepo,
109 'bundle': bundlerepo,
110 'union': unionrepo,
110 'union': unionrepo,
111 'file': _local,
111 'file': _local,
112 'http': httppeer,
112 'http': httppeer,
113 'https': httppeer,
113 'https': httppeer,
114 'ssh': sshpeer,
114 'ssh': sshpeer,
115 'static-http': statichttprepo,
115 'static-http': statichttprepo,
116 }
116 }
117
117
118 def _peerlookup(path):
118 def _peerlookup(path):
119 u = util.url(path)
119 u = util.url(path)
120 scheme = u.scheme or 'file'
120 scheme = u.scheme or 'file'
121 thing = schemes.get(scheme) or schemes['file']
121 thing = schemes.get(scheme) or schemes['file']
122 try:
122 try:
123 return thing(path)
123 return thing(path)
124 except TypeError:
124 except TypeError:
125 # we can't test callable(thing) because 'thing' can be an unloaded
125 # we can't test callable(thing) because 'thing' can be an unloaded
126 # module that implements __call__
126 # module that implements __call__
127 if not util.safehasattr(thing, 'instance'):
127 if not util.safehasattr(thing, 'instance'):
128 raise
128 raise
129 return thing
129 return thing
130
130
131 def islocal(repo):
131 def islocal(repo):
132 '''return true if repo (or path pointing to repo) is local'''
132 '''return true if repo (or path pointing to repo) is local'''
133 if isinstance(repo, str):
133 if isinstance(repo, str):
134 try:
134 try:
135 return _peerlookup(repo).islocal(repo)
135 return _peerlookup(repo).islocal(repo)
136 except AttributeError:
136 except AttributeError:
137 return False
137 return False
138 return repo.local()
138 return repo.local()
139
139
140 def openpath(ui, path):
140 def openpath(ui, path):
141 '''open path with open if local, url.open if remote'''
141 '''open path with open if local, url.open if remote'''
142 pathurl = util.url(path, parsequery=False, parsefragment=False)
142 pathurl = util.url(path, parsequery=False, parsefragment=False)
143 if pathurl.islocal():
143 if pathurl.islocal():
144 return util.posixfile(pathurl.localpath(), 'rb')
144 return util.posixfile(pathurl.localpath(), 'rb')
145 else:
145 else:
146 return url.open(ui, path)
146 return url.open(ui, path)
147
147
148 # a list of (ui, repo) functions called for wire peer initialization
148 # a list of (ui, repo) functions called for wire peer initialization
149 wirepeersetupfuncs = []
149 wirepeersetupfuncs = []
150
150
151 def _peerorrepo(ui, path, create=False):
151 def _peerorrepo(ui, path, create=False, presetupfuncs=None):
152 """return a repository object for the specified path"""
152 """return a repository object for the specified path"""
153 obj = _peerlookup(path).instance(ui, path, create)
153 obj = _peerlookup(path).instance(ui, path, create)
154 ui = getattr(obj, "ui", ui)
154 ui = getattr(obj, "ui", ui)
155 for f in presetupfuncs or []:
156 f(ui, obj)
155 for name, module in extensions.extensions(ui):
157 for name, module in extensions.extensions(ui):
156 hook = getattr(module, 'reposetup', None)
158 hook = getattr(module, 'reposetup', None)
157 if hook:
159 if hook:
158 hook(ui, obj)
160 hook(ui, obj)
159 if not obj.local():
161 if not obj.local():
160 for f in wirepeersetupfuncs:
162 for f in wirepeersetupfuncs:
161 f(ui, obj)
163 f(ui, obj)
162 return obj
164 return obj
163
165
164 def repository(ui, path='', create=False):
166 def repository(ui, path='', create=False, presetupfuncs=None):
165 """return a repository object for the specified path"""
167 """return a repository object for the specified path"""
166 peer = _peerorrepo(ui, path, create)
168 peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs)
167 repo = peer.local()
169 repo = peer.local()
168 if not repo:
170 if not repo:
169 raise error.Abort(_("repository '%s' is not local") %
171 raise error.Abort(_("repository '%s' is not local") %
170 (path or peer.url()))
172 (path or peer.url()))
171 return repo.filtered('visible')
173 return repo.filtered('visible')
172
174
173 def peer(uiorrepo, opts, path, create=False):
175 def peer(uiorrepo, opts, path, create=False):
174 '''return a repository peer for the specified path'''
176 '''return a repository peer for the specified path'''
175 rui = remoteui(uiorrepo, opts)
177 rui = remoteui(uiorrepo, opts)
176 return _peerorrepo(rui, path, create).peer()
178 return _peerorrepo(rui, path, create).peer()
177
179
178 def defaultdest(source):
180 def defaultdest(source):
179 '''return default destination of clone if none is given
181 '''return default destination of clone if none is given
180
182
181 >>> defaultdest('foo')
183 >>> defaultdest('foo')
182 'foo'
184 'foo'
183 >>> defaultdest('/foo/bar')
185 >>> defaultdest('/foo/bar')
184 'bar'
186 'bar'
185 >>> defaultdest('/')
187 >>> defaultdest('/')
186 ''
188 ''
187 >>> defaultdest('')
189 >>> defaultdest('')
188 ''
190 ''
189 >>> defaultdest('http://example.org/')
191 >>> defaultdest('http://example.org/')
190 ''
192 ''
191 >>> defaultdest('http://example.org/foo/')
193 >>> defaultdest('http://example.org/foo/')
192 'foo'
194 'foo'
193 '''
195 '''
194 path = util.url(source).path
196 path = util.url(source).path
195 if not path:
197 if not path:
196 return ''
198 return ''
197 return os.path.basename(os.path.normpath(path))
199 return os.path.basename(os.path.normpath(path))
198
200
199 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
201 def share(ui, source, dest=None, update=True, bookmarks=True, defaultpath=None,
200 relative=False):
202 relative=False):
201 '''create a shared repository'''
203 '''create a shared repository'''
202
204
203 if not islocal(source):
205 if not islocal(source):
204 raise error.Abort(_('can only share local repositories'))
206 raise error.Abort(_('can only share local repositories'))
205
207
206 if not dest:
208 if not dest:
207 dest = defaultdest(source)
209 dest = defaultdest(source)
208 else:
210 else:
209 dest = ui.expandpath(dest)
211 dest = ui.expandpath(dest)
210
212
211 if isinstance(source, str):
213 if isinstance(source, str):
212 origsource = ui.expandpath(source)
214 origsource = ui.expandpath(source)
213 source, branches = parseurl(origsource)
215 source, branches = parseurl(origsource)
214 srcrepo = repository(ui, source)
216 srcrepo = repository(ui, source)
215 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
217 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
216 else:
218 else:
217 srcrepo = source.local()
219 srcrepo = source.local()
218 origsource = source = srcrepo.url()
220 origsource = source = srcrepo.url()
219 checkout = None
221 checkout = None
220
222
221 sharedpath = srcrepo.sharedpath # if our source is already sharing
223 sharedpath = srcrepo.sharedpath # if our source is already sharing
222
224
223 destwvfs = vfsmod.vfs(dest, realpath=True)
225 destwvfs = vfsmod.vfs(dest, realpath=True)
224 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
226 destvfs = vfsmod.vfs(os.path.join(destwvfs.base, '.hg'), realpath=True)
225
227
226 if destvfs.lexists():
228 if destvfs.lexists():
227 raise error.Abort(_('destination already exists'))
229 raise error.Abort(_('destination already exists'))
228
230
229 if not destwvfs.isdir():
231 if not destwvfs.isdir():
230 destwvfs.mkdir()
232 destwvfs.mkdir()
231 destvfs.makedir()
233 destvfs.makedir()
232
234
233 requirements = ''
235 requirements = ''
234 try:
236 try:
235 requirements = srcrepo.vfs.read('requires')
237 requirements = srcrepo.vfs.read('requires')
236 except IOError as inst:
238 except IOError as inst:
237 if inst.errno != errno.ENOENT:
239 if inst.errno != errno.ENOENT:
238 raise
240 raise
239
241
240 if relative:
242 if relative:
241 try:
243 try:
242 sharedpath = os.path.relpath(sharedpath, destvfs.base)
244 sharedpath = os.path.relpath(sharedpath, destvfs.base)
243 requirements += 'relshared\n'
245 requirements += 'relshared\n'
244 except IOError as e:
246 except IOError as e:
245 raise error.Abort(_('cannot calculate relative path'),
247 raise error.Abort(_('cannot calculate relative path'),
246 hint=str(e))
248 hint=str(e))
247 else:
249 else:
248 requirements += 'shared\n'
250 requirements += 'shared\n'
249
251
250 destvfs.write('requires', requirements)
252 destvfs.write('requires', requirements)
251 destvfs.write('sharedpath', sharedpath)
253 destvfs.write('sharedpath', sharedpath)
252
254
253 r = repository(ui, destwvfs.base)
255 r = repository(ui, destwvfs.base)
254 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
256 postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
255 _postshareupdate(r, update, checkout=checkout)
257 _postshareupdate(r, update, checkout=checkout)
256
258
257 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
259 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
258 """Called after a new shared repo is created.
260 """Called after a new shared repo is created.
259
261
260 The new repo only has a requirements file and pointer to the source.
262 The new repo only has a requirements file and pointer to the source.
261 This function configures additional shared data.
263 This function configures additional shared data.
262
264
263 Extensions can wrap this function and write additional entries to
265 Extensions can wrap this function and write additional entries to
264 destrepo/.hg/shared to indicate additional pieces of data to be shared.
266 destrepo/.hg/shared to indicate additional pieces of data to be shared.
265 """
267 """
266 default = defaultpath or sourcerepo.ui.config('paths', 'default')
268 default = defaultpath or sourcerepo.ui.config('paths', 'default')
267 if default:
269 if default:
268 fp = destrepo.vfs("hgrc", "w", text=True)
270 fp = destrepo.vfs("hgrc", "w", text=True)
269 fp.write("[paths]\n")
271 fp.write("[paths]\n")
270 fp.write("default = %s\n" % default)
272 fp.write("default = %s\n" % default)
271 fp.close()
273 fp.close()
272
274
273 with destrepo.wlock():
275 with destrepo.wlock():
274 if bookmarks:
276 if bookmarks:
275 fp = destrepo.vfs('shared', 'w')
277 fp = destrepo.vfs('shared', 'w')
276 fp.write(sharedbookmarks + '\n')
278 fp.write(sharedbookmarks + '\n')
277 fp.close()
279 fp.close()
278
280
279 def _postshareupdate(repo, update, checkout=None):
281 def _postshareupdate(repo, update, checkout=None):
280 """Maybe perform a working directory update after a shared repo is created.
282 """Maybe perform a working directory update after a shared repo is created.
281
283
282 ``update`` can be a boolean or a revision to update to.
284 ``update`` can be a boolean or a revision to update to.
283 """
285 """
284 if not update:
286 if not update:
285 return
287 return
286
288
287 repo.ui.status(_("updating working directory\n"))
289 repo.ui.status(_("updating working directory\n"))
288 if update is not True:
290 if update is not True:
289 checkout = update
291 checkout = update
290 for test in (checkout, 'default', 'tip'):
292 for test in (checkout, 'default', 'tip'):
291 if test is None:
293 if test is None:
292 continue
294 continue
293 try:
295 try:
294 uprev = repo.lookup(test)
296 uprev = repo.lookup(test)
295 break
297 break
296 except error.RepoLookupError:
298 except error.RepoLookupError:
297 continue
299 continue
298 _update(repo, uprev)
300 _update(repo, uprev)
299
301
300 def copystore(ui, srcrepo, destpath):
302 def copystore(ui, srcrepo, destpath):
301 '''copy files from store of srcrepo in destpath
303 '''copy files from store of srcrepo in destpath
302
304
303 returns destlock
305 returns destlock
304 '''
306 '''
305 destlock = None
307 destlock = None
306 try:
308 try:
307 hardlink = None
309 hardlink = None
308 num = 0
310 num = 0
309 closetopic = [None]
311 closetopic = [None]
310 def prog(topic, pos):
312 def prog(topic, pos):
311 if pos is None:
313 if pos is None:
312 closetopic[0] = topic
314 closetopic[0] = topic
313 else:
315 else:
314 ui.progress(topic, pos + num)
316 ui.progress(topic, pos + num)
315 srcpublishing = srcrepo.publishing()
317 srcpublishing = srcrepo.publishing()
316 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
318 srcvfs = vfsmod.vfs(srcrepo.sharedpath)
317 dstvfs = vfsmod.vfs(destpath)
319 dstvfs = vfsmod.vfs(destpath)
318 for f in srcrepo.store.copylist():
320 for f in srcrepo.store.copylist():
319 if srcpublishing and f.endswith('phaseroots'):
321 if srcpublishing and f.endswith('phaseroots'):
320 continue
322 continue
321 dstbase = os.path.dirname(f)
323 dstbase = os.path.dirname(f)
322 if dstbase and not dstvfs.exists(dstbase):
324 if dstbase and not dstvfs.exists(dstbase):
323 dstvfs.mkdir(dstbase)
325 dstvfs.mkdir(dstbase)
324 if srcvfs.exists(f):
326 if srcvfs.exists(f):
325 if f.endswith('data'):
327 if f.endswith('data'):
326 # 'dstbase' may be empty (e.g. revlog format 0)
328 # 'dstbase' may be empty (e.g. revlog format 0)
327 lockfile = os.path.join(dstbase, "lock")
329 lockfile = os.path.join(dstbase, "lock")
328 # lock to avoid premature writing to the target
330 # lock to avoid premature writing to the target
329 destlock = lock.lock(dstvfs, lockfile)
331 destlock = lock.lock(dstvfs, lockfile)
330 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
332 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
331 hardlink, progress=prog)
333 hardlink, progress=prog)
332 num += n
334 num += n
333 if hardlink:
335 if hardlink:
334 ui.debug("linked %d files\n" % num)
336 ui.debug("linked %d files\n" % num)
335 if closetopic[0]:
337 if closetopic[0]:
336 ui.progress(closetopic[0], None)
338 ui.progress(closetopic[0], None)
337 else:
339 else:
338 ui.debug("copied %d files\n" % num)
340 ui.debug("copied %d files\n" % num)
339 if closetopic[0]:
341 if closetopic[0]:
340 ui.progress(closetopic[0], None)
342 ui.progress(closetopic[0], None)
341 return destlock
343 return destlock
342 except: # re-raises
344 except: # re-raises
343 release(destlock)
345 release(destlock)
344 raise
346 raise
345
347
346 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
348 def clonewithshare(ui, peeropts, sharepath, source, srcpeer, dest, pull=False,
347 rev=None, update=True, stream=False):
349 rev=None, update=True, stream=False):
348 """Perform a clone using a shared repo.
350 """Perform a clone using a shared repo.
349
351
350 The store for the repository will be located at <sharepath>/.hg. The
352 The store for the repository will be located at <sharepath>/.hg. The
351 specified revisions will be cloned or pulled from "source". A shared repo
353 specified revisions will be cloned or pulled from "source". A shared repo
352 will be created at "dest" and a working copy will be created if "update" is
354 will be created at "dest" and a working copy will be created if "update" is
353 True.
355 True.
354 """
356 """
355 revs = None
357 revs = None
356 if rev:
358 if rev:
357 if not srcpeer.capable('lookup'):
359 if not srcpeer.capable('lookup'):
358 raise error.Abort(_("src repository does not support "
360 raise error.Abort(_("src repository does not support "
359 "revision lookup and so doesn't "
361 "revision lookup and so doesn't "
360 "support clone by revision"))
362 "support clone by revision"))
361 revs = [srcpeer.lookup(r) for r in rev]
363 revs = [srcpeer.lookup(r) for r in rev]
362
364
363 # Obtain a lock before checking for or cloning the pooled repo otherwise
365 # Obtain a lock before checking for or cloning the pooled repo otherwise
364 # 2 clients may race creating or populating it.
366 # 2 clients may race creating or populating it.
365 pooldir = os.path.dirname(sharepath)
367 pooldir = os.path.dirname(sharepath)
366 # lock class requires the directory to exist.
368 # lock class requires the directory to exist.
367 try:
369 try:
368 util.makedir(pooldir, False)
370 util.makedir(pooldir, False)
369 except OSError as e:
371 except OSError as e:
370 if e.errno != errno.EEXIST:
372 if e.errno != errno.EEXIST:
371 raise
373 raise
372
374
373 poolvfs = vfsmod.vfs(pooldir)
375 poolvfs = vfsmod.vfs(pooldir)
374 basename = os.path.basename(sharepath)
376 basename = os.path.basename(sharepath)
375
377
376 with lock.lock(poolvfs, '%s.lock' % basename):
378 with lock.lock(poolvfs, '%s.lock' % basename):
377 if os.path.exists(sharepath):
379 if os.path.exists(sharepath):
378 ui.status(_('(sharing from existing pooled repository %s)\n') %
380 ui.status(_('(sharing from existing pooled repository %s)\n') %
379 basename)
381 basename)
380 else:
382 else:
381 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
383 ui.status(_('(sharing from new pooled repository %s)\n') % basename)
382 # Always use pull mode because hardlinks in share mode don't work
384 # Always use pull mode because hardlinks in share mode don't work
383 # well. Never update because working copies aren't necessary in
385 # well. Never update because working copies aren't necessary in
384 # share mode.
386 # share mode.
385 clone(ui, peeropts, source, dest=sharepath, pull=True,
387 clone(ui, peeropts, source, dest=sharepath, pull=True,
386 rev=rev, update=False, stream=stream)
388 rev=rev, update=False, stream=stream)
387
389
388 # Resolve the value to put in [paths] section for the source.
390 # Resolve the value to put in [paths] section for the source.
389 if islocal(source):
391 if islocal(source):
390 defaultpath = os.path.abspath(util.urllocalpath(source))
392 defaultpath = os.path.abspath(util.urllocalpath(source))
391 else:
393 else:
392 defaultpath = source
394 defaultpath = source
393
395
394 sharerepo = repository(ui, path=sharepath)
396 sharerepo = repository(ui, path=sharepath)
395 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
397 share(ui, sharerepo, dest=dest, update=False, bookmarks=False,
396 defaultpath=defaultpath)
398 defaultpath=defaultpath)
397
399
398 # We need to perform a pull against the dest repo to fetch bookmarks
400 # We need to perform a pull against the dest repo to fetch bookmarks
399 # and other non-store data that isn't shared by default. In the case of
401 # and other non-store data that isn't shared by default. In the case of
400 # non-existing shared repo, this means we pull from the remote twice. This
402 # non-existing shared repo, this means we pull from the remote twice. This
401 # is a bit weird. But at the time it was implemented, there wasn't an easy
403 # is a bit weird. But at the time it was implemented, there wasn't an easy
402 # way to pull just non-changegroup data.
404 # way to pull just non-changegroup data.
403 destrepo = repository(ui, path=dest)
405 destrepo = repository(ui, path=dest)
404 exchange.pull(destrepo, srcpeer, heads=revs)
406 exchange.pull(destrepo, srcpeer, heads=revs)
405
407
406 _postshareupdate(destrepo, update)
408 _postshareupdate(destrepo, update)
407
409
408 return srcpeer, peer(ui, peeropts, dest)
410 return srcpeer, peer(ui, peeropts, dest)
409
411
410 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
412 def clone(ui, peeropts, source, dest=None, pull=False, rev=None,
411 update=True, stream=False, branch=None, shareopts=None):
413 update=True, stream=False, branch=None, shareopts=None):
412 """Make a copy of an existing repository.
414 """Make a copy of an existing repository.
413
415
414 Create a copy of an existing repository in a new directory. The
416 Create a copy of an existing repository in a new directory. The
415 source and destination are URLs, as passed to the repository
417 source and destination are URLs, as passed to the repository
416 function. Returns a pair of repository peers, the source and
418 function. Returns a pair of repository peers, the source and
417 newly created destination.
419 newly created destination.
418
420
419 The location of the source is added to the new repository's
421 The location of the source is added to the new repository's
420 .hg/hgrc file, as the default to be used for future pulls and
422 .hg/hgrc file, as the default to be used for future pulls and
421 pushes.
423 pushes.
422
424
423 If an exception is raised, the partly cloned/updated destination
425 If an exception is raised, the partly cloned/updated destination
424 repository will be deleted.
426 repository will be deleted.
425
427
426 Arguments:
428 Arguments:
427
429
428 source: repository object or URL
430 source: repository object or URL
429
431
430 dest: URL of destination repository to create (defaults to base
432 dest: URL of destination repository to create (defaults to base
431 name of source repository)
433 name of source repository)
432
434
433 pull: always pull from source repository, even in local case or if the
435 pull: always pull from source repository, even in local case or if the
434 server prefers streaming
436 server prefers streaming
435
437
436 stream: stream raw data uncompressed from repository (fast over
438 stream: stream raw data uncompressed from repository (fast over
437 LAN, slow over WAN)
439 LAN, slow over WAN)
438
440
439 rev: revision to clone up to (implies pull=True)
441 rev: revision to clone up to (implies pull=True)
440
442
441 update: update working directory after clone completes, if
443 update: update working directory after clone completes, if
442 destination is local repository (True means update to default rev,
444 destination is local repository (True means update to default rev,
443 anything else is treated as a revision)
445 anything else is treated as a revision)
444
446
445 branch: branches to clone
447 branch: branches to clone
446
448
447 shareopts: dict of options to control auto sharing behavior. The "pool" key
449 shareopts: dict of options to control auto sharing behavior. The "pool" key
448 activates auto sharing mode and defines the directory for stores. The
450 activates auto sharing mode and defines the directory for stores. The
449 "mode" key determines how to construct the directory name of the shared
451 "mode" key determines how to construct the directory name of the shared
450 repository. "identity" means the name is derived from the node of the first
452 repository. "identity" means the name is derived from the node of the first
451 changeset in the repository. "remote" means the name is derived from the
453 changeset in the repository. "remote" means the name is derived from the
452 remote's path/URL. Defaults to "identity."
454 remote's path/URL. Defaults to "identity."
453 """
455 """
454
456
455 if isinstance(source, str):
457 if isinstance(source, str):
456 origsource = ui.expandpath(source)
458 origsource = ui.expandpath(source)
457 source, branch = parseurl(origsource, branch)
459 source, branch = parseurl(origsource, branch)
458 srcpeer = peer(ui, peeropts, source)
460 srcpeer = peer(ui, peeropts, source)
459 else:
461 else:
460 srcpeer = source.peer() # in case we were called with a localrepo
462 srcpeer = source.peer() # in case we were called with a localrepo
461 branch = (None, branch or [])
463 branch = (None, branch or [])
462 origsource = source = srcpeer.url()
464 origsource = source = srcpeer.url()
463 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
465 rev, checkout = addbranchrevs(srcpeer, srcpeer, branch, rev)
464
466
465 if dest is None:
467 if dest is None:
466 dest = defaultdest(source)
468 dest = defaultdest(source)
467 if dest:
469 if dest:
468 ui.status(_("destination directory: %s\n") % dest)
470 ui.status(_("destination directory: %s\n") % dest)
469 else:
471 else:
470 dest = ui.expandpath(dest)
472 dest = ui.expandpath(dest)
471
473
472 dest = util.urllocalpath(dest)
474 dest = util.urllocalpath(dest)
473 source = util.urllocalpath(source)
475 source = util.urllocalpath(source)
474
476
475 if not dest:
477 if not dest:
476 raise error.Abort(_("empty destination path is not valid"))
478 raise error.Abort(_("empty destination path is not valid"))
477
479
478 destvfs = vfsmod.vfs(dest, expandpath=True)
480 destvfs = vfsmod.vfs(dest, expandpath=True)
479 if destvfs.lexists():
481 if destvfs.lexists():
480 if not destvfs.isdir():
482 if not destvfs.isdir():
481 raise error.Abort(_("destination '%s' already exists") % dest)
483 raise error.Abort(_("destination '%s' already exists") % dest)
482 elif destvfs.listdir():
484 elif destvfs.listdir():
483 raise error.Abort(_("destination '%s' is not empty") % dest)
485 raise error.Abort(_("destination '%s' is not empty") % dest)
484
486
485 shareopts = shareopts or {}
487 shareopts = shareopts or {}
486 sharepool = shareopts.get('pool')
488 sharepool = shareopts.get('pool')
487 sharenamemode = shareopts.get('mode')
489 sharenamemode = shareopts.get('mode')
488 if sharepool and islocal(dest):
490 if sharepool and islocal(dest):
489 sharepath = None
491 sharepath = None
490 if sharenamemode == 'identity':
492 if sharenamemode == 'identity':
491 # Resolve the name from the initial changeset in the remote
493 # Resolve the name from the initial changeset in the remote
492 # repository. This returns nullid when the remote is empty. It
494 # repository. This returns nullid when the remote is empty. It
493 # raises RepoLookupError if revision 0 is filtered or otherwise
495 # raises RepoLookupError if revision 0 is filtered or otherwise
494 # not available. If we fail to resolve, sharing is not enabled.
496 # not available. If we fail to resolve, sharing is not enabled.
495 try:
497 try:
496 rootnode = srcpeer.lookup('0')
498 rootnode = srcpeer.lookup('0')
497 if rootnode != node.nullid:
499 if rootnode != node.nullid:
498 sharepath = os.path.join(sharepool, node.hex(rootnode))
500 sharepath = os.path.join(sharepool, node.hex(rootnode))
499 else:
501 else:
500 ui.status(_('(not using pooled storage: '
502 ui.status(_('(not using pooled storage: '
501 'remote appears to be empty)\n'))
503 'remote appears to be empty)\n'))
502 except error.RepoLookupError:
504 except error.RepoLookupError:
503 ui.status(_('(not using pooled storage: '
505 ui.status(_('(not using pooled storage: '
504 'unable to resolve identity of remote)\n'))
506 'unable to resolve identity of remote)\n'))
505 elif sharenamemode == 'remote':
507 elif sharenamemode == 'remote':
506 sharepath = os.path.join(
508 sharepath = os.path.join(
507 sharepool, hashlib.sha1(source).hexdigest())
509 sharepool, hashlib.sha1(source).hexdigest())
508 else:
510 else:
509 raise error.Abort(_('unknown share naming mode: %s') %
511 raise error.Abort(_('unknown share naming mode: %s') %
510 sharenamemode)
512 sharenamemode)
511
513
512 if sharepath:
514 if sharepath:
513 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
515 return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
514 dest, pull=pull, rev=rev, update=update,
516 dest, pull=pull, rev=rev, update=update,
515 stream=stream)
517 stream=stream)
516
518
517 srclock = destlock = cleandir = None
519 srclock = destlock = cleandir = None
518 srcrepo = srcpeer.local()
520 srcrepo = srcpeer.local()
519 try:
521 try:
520 abspath = origsource
522 abspath = origsource
521 if islocal(origsource):
523 if islocal(origsource):
522 abspath = os.path.abspath(util.urllocalpath(origsource))
524 abspath = os.path.abspath(util.urllocalpath(origsource))
523
525
524 if islocal(dest):
526 if islocal(dest):
525 cleandir = dest
527 cleandir = dest
526
528
527 copy = False
529 copy = False
528 if (srcrepo and srcrepo.cancopy() and islocal(dest)
530 if (srcrepo and srcrepo.cancopy() and islocal(dest)
529 and not phases.hassecret(srcrepo)):
531 and not phases.hassecret(srcrepo)):
530 copy = not pull and not rev
532 copy = not pull and not rev
531
533
532 if copy:
534 if copy:
533 try:
535 try:
534 # we use a lock here because if we race with commit, we
536 # we use a lock here because if we race with commit, we
535 # can end up with extra data in the cloned revlogs that's
537 # can end up with extra data in the cloned revlogs that's
536 # not pointed to by changesets, thus causing verify to
538 # not pointed to by changesets, thus causing verify to
537 # fail
539 # fail
538 srclock = srcrepo.lock(wait=False)
540 srclock = srcrepo.lock(wait=False)
539 except error.LockError:
541 except error.LockError:
540 copy = False
542 copy = False
541
543
542 if copy:
544 if copy:
543 srcrepo.hook('preoutgoing', throw=True, source='clone')
545 srcrepo.hook('preoutgoing', throw=True, source='clone')
544 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
546 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
545 if not os.path.exists(dest):
547 if not os.path.exists(dest):
546 os.mkdir(dest)
548 os.mkdir(dest)
547 else:
549 else:
548 # only clean up directories we create ourselves
550 # only clean up directories we create ourselves
549 cleandir = hgdir
551 cleandir = hgdir
550 try:
552 try:
551 destpath = hgdir
553 destpath = hgdir
552 util.makedir(destpath, notindexed=True)
554 util.makedir(destpath, notindexed=True)
553 except OSError as inst:
555 except OSError as inst:
554 if inst.errno == errno.EEXIST:
556 if inst.errno == errno.EEXIST:
555 cleandir = None
557 cleandir = None
556 raise error.Abort(_("destination '%s' already exists")
558 raise error.Abort(_("destination '%s' already exists")
557 % dest)
559 % dest)
558 raise
560 raise
559
561
560 destlock = copystore(ui, srcrepo, destpath)
562 destlock = copystore(ui, srcrepo, destpath)
561 # copy bookmarks over
563 # copy bookmarks over
562 srcbookmarks = srcrepo.vfs.join('bookmarks')
564 srcbookmarks = srcrepo.vfs.join('bookmarks')
563 dstbookmarks = os.path.join(destpath, 'bookmarks')
565 dstbookmarks = os.path.join(destpath, 'bookmarks')
564 if os.path.exists(srcbookmarks):
566 if os.path.exists(srcbookmarks):
565 util.copyfile(srcbookmarks, dstbookmarks)
567 util.copyfile(srcbookmarks, dstbookmarks)
566
568
567 # Recomputing branch cache might be slow on big repos,
569 # Recomputing branch cache might be slow on big repos,
568 # so just copy it
570 # so just copy it
569 def copybranchcache(fname):
571 def copybranchcache(fname):
570 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
572 srcbranchcache = srcrepo.vfs.join('cache/%s' % fname)
571 dstbranchcache = os.path.join(dstcachedir, fname)
573 dstbranchcache = os.path.join(dstcachedir, fname)
572 if os.path.exists(srcbranchcache):
574 if os.path.exists(srcbranchcache):
573 if not os.path.exists(dstcachedir):
575 if not os.path.exists(dstcachedir):
574 os.mkdir(dstcachedir)
576 os.mkdir(dstcachedir)
575 util.copyfile(srcbranchcache, dstbranchcache)
577 util.copyfile(srcbranchcache, dstbranchcache)
576
578
577 dstcachedir = os.path.join(destpath, 'cache')
579 dstcachedir = os.path.join(destpath, 'cache')
578 # In local clones we're copying all nodes, not just served
580 # In local clones we're copying all nodes, not just served
579 # ones. Therefore copy all branch caches over.
581 # ones. Therefore copy all branch caches over.
580 copybranchcache('branch2')
582 copybranchcache('branch2')
581 for cachename in repoview.filtertable:
583 for cachename in repoview.filtertable:
582 copybranchcache('branch2-%s' % cachename)
584 copybranchcache('branch2-%s' % cachename)
583
585
584 # we need to re-init the repo after manually copying the data
586 # we need to re-init the repo after manually copying the data
585 # into it
587 # into it
586 destpeer = peer(srcrepo, peeropts, dest)
588 destpeer = peer(srcrepo, peeropts, dest)
587 srcrepo.hook('outgoing', source='clone',
589 srcrepo.hook('outgoing', source='clone',
588 node=node.hex(node.nullid))
590 node=node.hex(node.nullid))
589 else:
591 else:
590 try:
592 try:
591 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
593 destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
592 # only pass ui when no srcrepo
594 # only pass ui when no srcrepo
593 except OSError as inst:
595 except OSError as inst:
594 if inst.errno == errno.EEXIST:
596 if inst.errno == errno.EEXIST:
595 cleandir = None
597 cleandir = None
596 raise error.Abort(_("destination '%s' already exists")
598 raise error.Abort(_("destination '%s' already exists")
597 % dest)
599 % dest)
598 raise
600 raise
599
601
600 revs = None
602 revs = None
601 if rev:
603 if rev:
602 if not srcpeer.capable('lookup'):
604 if not srcpeer.capable('lookup'):
603 raise error.Abort(_("src repository does not support "
605 raise error.Abort(_("src repository does not support "
604 "revision lookup and so doesn't "
606 "revision lookup and so doesn't "
605 "support clone by revision"))
607 "support clone by revision"))
606 revs = [srcpeer.lookup(r) for r in rev]
608 revs = [srcpeer.lookup(r) for r in rev]
607 checkout = revs[0]
609 checkout = revs[0]
608 local = destpeer.local()
610 local = destpeer.local()
609 if local:
611 if local:
610 if not stream:
612 if not stream:
611 if pull:
613 if pull:
612 stream = False
614 stream = False
613 else:
615 else:
614 stream = None
616 stream = None
615 # internal config: ui.quietbookmarkmove
617 # internal config: ui.quietbookmarkmove
616 overrides = {('ui', 'quietbookmarkmove'): True}
618 overrides = {('ui', 'quietbookmarkmove'): True}
617 with local.ui.configoverride(overrides, 'clone'):
619 with local.ui.configoverride(overrides, 'clone'):
618 exchange.pull(local, srcpeer, revs,
620 exchange.pull(local, srcpeer, revs,
619 streamclonerequested=stream)
621 streamclonerequested=stream)
620 elif srcrepo:
622 elif srcrepo:
621 exchange.push(srcrepo, destpeer, revs=revs,
623 exchange.push(srcrepo, destpeer, revs=revs,
622 bookmarks=srcrepo._bookmarks.keys())
624 bookmarks=srcrepo._bookmarks.keys())
623 else:
625 else:
624 raise error.Abort(_("clone from remote to remote not supported")
626 raise error.Abort(_("clone from remote to remote not supported")
625 )
627 )
626
628
627 cleandir = None
629 cleandir = None
628
630
629 destrepo = destpeer.local()
631 destrepo = destpeer.local()
630 if destrepo:
632 if destrepo:
631 template = uimod.samplehgrcs['cloned']
633 template = uimod.samplehgrcs['cloned']
632 fp = destrepo.vfs("hgrc", "w", text=True)
634 fp = destrepo.vfs("hgrc", "w", text=True)
633 u = util.url(abspath)
635 u = util.url(abspath)
634 u.passwd = None
636 u.passwd = None
635 defaulturl = str(u)
637 defaulturl = str(u)
636 fp.write(template % defaulturl)
638 fp.write(template % defaulturl)
637 fp.close()
639 fp.close()
638
640
639 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
641 destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
640
642
641 if update:
643 if update:
642 if update is not True:
644 if update is not True:
643 checkout = srcpeer.lookup(update)
645 checkout = srcpeer.lookup(update)
644 uprev = None
646 uprev = None
645 status = None
647 status = None
646 if checkout is not None:
648 if checkout is not None:
647 try:
649 try:
648 uprev = destrepo.lookup(checkout)
650 uprev = destrepo.lookup(checkout)
649 except error.RepoLookupError:
651 except error.RepoLookupError:
650 if update is not True:
652 if update is not True:
651 try:
653 try:
652 uprev = destrepo.lookup(update)
654 uprev = destrepo.lookup(update)
653 except error.RepoLookupError:
655 except error.RepoLookupError:
654 pass
656 pass
655 if uprev is None:
657 if uprev is None:
656 try:
658 try:
657 uprev = destrepo._bookmarks['@']
659 uprev = destrepo._bookmarks['@']
658 update = '@'
660 update = '@'
659 bn = destrepo[uprev].branch()
661 bn = destrepo[uprev].branch()
660 if bn == 'default':
662 if bn == 'default':
661 status = _("updating to bookmark @\n")
663 status = _("updating to bookmark @\n")
662 else:
664 else:
663 status = (_("updating to bookmark @ on branch %s\n")
665 status = (_("updating to bookmark @ on branch %s\n")
664 % bn)
666 % bn)
665 except KeyError:
667 except KeyError:
666 try:
668 try:
667 uprev = destrepo.branchtip('default')
669 uprev = destrepo.branchtip('default')
668 except error.RepoLookupError:
670 except error.RepoLookupError:
669 uprev = destrepo.lookup('tip')
671 uprev = destrepo.lookup('tip')
670 if not status:
672 if not status:
671 bn = destrepo[uprev].branch()
673 bn = destrepo[uprev].branch()
672 status = _("updating to branch %s\n") % bn
674 status = _("updating to branch %s\n") % bn
673 destrepo.ui.status(status)
675 destrepo.ui.status(status)
674 _update(destrepo, uprev)
676 _update(destrepo, uprev)
675 if update in destrepo._bookmarks:
677 if update in destrepo._bookmarks:
676 bookmarks.activate(destrepo, update)
678 bookmarks.activate(destrepo, update)
677 finally:
679 finally:
678 release(srclock, destlock)
680 release(srclock, destlock)
679 if cleandir is not None:
681 if cleandir is not None:
680 shutil.rmtree(cleandir, True)
682 shutil.rmtree(cleandir, True)
681 if srcpeer is not None:
683 if srcpeer is not None:
682 srcpeer.close()
684 srcpeer.close()
683 return srcpeer, destpeer
685 return srcpeer, destpeer
684
686
685 def _showstats(repo, stats, quietempty=False):
687 def _showstats(repo, stats, quietempty=False):
686 if quietempty and not any(stats):
688 if quietempty and not any(stats):
687 return
689 return
688 repo.ui.status(_("%d files updated, %d files merged, "
690 repo.ui.status(_("%d files updated, %d files merged, "
689 "%d files removed, %d files unresolved\n") % stats)
691 "%d files removed, %d files unresolved\n") % stats)
690
692
691 def updaterepo(repo, node, overwrite, updatecheck=None):
693 def updaterepo(repo, node, overwrite, updatecheck=None):
692 """Update the working directory to node.
694 """Update the working directory to node.
693
695
694 When overwrite is set, changes are clobbered, merged else
696 When overwrite is set, changes are clobbered, merged else
695
697
696 returns stats (see pydoc mercurial.merge.applyupdates)"""
698 returns stats (see pydoc mercurial.merge.applyupdates)"""
697 return mergemod.update(repo, node, False, overwrite,
699 return mergemod.update(repo, node, False, overwrite,
698 labels=['working copy', 'destination'],
700 labels=['working copy', 'destination'],
699 updatecheck=updatecheck)
701 updatecheck=updatecheck)
700
702
701 def update(repo, node, quietempty=False, updatecheck=None):
703 def update(repo, node, quietempty=False, updatecheck=None):
702 """update the working directory to node"""
704 """update the working directory to node"""
703 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
705 stats = updaterepo(repo, node, False, updatecheck=updatecheck)
704 _showstats(repo, stats, quietempty)
706 _showstats(repo, stats, quietempty)
705 if stats[3]:
707 if stats[3]:
706 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
708 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
707 return stats[3] > 0
709 return stats[3] > 0
708
710
709 # naming conflict in clone()
711 # naming conflict in clone()
710 _update = update
712 _update = update
711
713
712 def clean(repo, node, show_stats=True, quietempty=False):
714 def clean(repo, node, show_stats=True, quietempty=False):
713 """forcibly switch the working directory to node, clobbering changes"""
715 """forcibly switch the working directory to node, clobbering changes"""
714 stats = updaterepo(repo, node, True)
716 stats = updaterepo(repo, node, True)
715 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
717 repo.vfs.unlinkpath('graftstate', ignoremissing=True)
716 if show_stats:
718 if show_stats:
717 _showstats(repo, stats, quietempty)
719 _showstats(repo, stats, quietempty)
718 return stats[3] > 0
720 return stats[3] > 0
719
721
720 # naming conflict in updatetotally()
722 # naming conflict in updatetotally()
721 _clean = clean
723 _clean = clean
722
724
723 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
725 def updatetotally(ui, repo, checkout, brev, clean=False, updatecheck=None):
724 """Update the working directory with extra care for non-file components
726 """Update the working directory with extra care for non-file components
725
727
726 This takes care of non-file components below:
728 This takes care of non-file components below:
727
729
728 :bookmark: might be advanced or (in)activated
730 :bookmark: might be advanced or (in)activated
729
731
730 This takes arguments below:
732 This takes arguments below:
731
733
732 :checkout: to which revision the working directory is updated
734 :checkout: to which revision the working directory is updated
733 :brev: a name, which might be a bookmark to be activated after updating
735 :brev: a name, which might be a bookmark to be activated after updating
734 :clean: whether changes in the working directory can be discarded
736 :clean: whether changes in the working directory can be discarded
735 :updatecheck: how to deal with a dirty working directory
737 :updatecheck: how to deal with a dirty working directory
736
738
737 Valid values for updatecheck are (None => linear):
739 Valid values for updatecheck are (None => linear):
738
740
739 * abort: abort if the working directory is dirty
741 * abort: abort if the working directory is dirty
740 * none: don't check (merge working directory changes into destination)
742 * none: don't check (merge working directory changes into destination)
741 * linear: check that update is linear before merging working directory
743 * linear: check that update is linear before merging working directory
742 changes into destination
744 changes into destination
743 * noconflict: check that the update does not result in file merges
745 * noconflict: check that the update does not result in file merges
744
746
745 This returns whether conflict is detected at updating or not.
747 This returns whether conflict is detected at updating or not.
746 """
748 """
747 if updatecheck is None:
749 if updatecheck is None:
748 updatecheck = ui.config('experimental', 'updatecheck')
750 updatecheck = ui.config('experimental', 'updatecheck')
749 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
751 if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
750 # If not configured, or invalid value configured
752 # If not configured, or invalid value configured
751 updatecheck = 'linear'
753 updatecheck = 'linear'
752 with repo.wlock():
754 with repo.wlock():
753 movemarkfrom = None
755 movemarkfrom = None
754 warndest = False
756 warndest = False
755 if checkout is None:
757 if checkout is None:
756 updata = destutil.destupdate(repo, clean=clean)
758 updata = destutil.destupdate(repo, clean=clean)
757 checkout, movemarkfrom, brev = updata
759 checkout, movemarkfrom, brev = updata
758 warndest = True
760 warndest = True
759
761
760 if clean:
762 if clean:
761 ret = _clean(repo, checkout)
763 ret = _clean(repo, checkout)
762 else:
764 else:
763 if updatecheck == 'abort':
765 if updatecheck == 'abort':
764 cmdutil.bailifchanged(repo, merge=False)
766 cmdutil.bailifchanged(repo, merge=False)
765 updatecheck = 'none'
767 updatecheck = 'none'
766 ret = _update(repo, checkout, updatecheck=updatecheck)
768 ret = _update(repo, checkout, updatecheck=updatecheck)
767
769
768 if not ret and movemarkfrom:
770 if not ret and movemarkfrom:
769 if movemarkfrom == repo['.'].node():
771 if movemarkfrom == repo['.'].node():
770 pass # no-op update
772 pass # no-op update
771 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
773 elif bookmarks.update(repo, [movemarkfrom], repo['.'].node()):
772 b = ui.label(repo._activebookmark, 'bookmarks.active')
774 b = ui.label(repo._activebookmark, 'bookmarks.active')
773 ui.status(_("updating bookmark %s\n") % b)
775 ui.status(_("updating bookmark %s\n") % b)
774 else:
776 else:
775 # this can happen with a non-linear update
777 # this can happen with a non-linear update
776 b = ui.label(repo._activebookmark, 'bookmarks')
778 b = ui.label(repo._activebookmark, 'bookmarks')
777 ui.status(_("(leaving bookmark %s)\n") % b)
779 ui.status(_("(leaving bookmark %s)\n") % b)
778 bookmarks.deactivate(repo)
780 bookmarks.deactivate(repo)
779 elif brev in repo._bookmarks:
781 elif brev in repo._bookmarks:
780 if brev != repo._activebookmark:
782 if brev != repo._activebookmark:
781 b = ui.label(brev, 'bookmarks.active')
783 b = ui.label(brev, 'bookmarks.active')
782 ui.status(_("(activating bookmark %s)\n") % b)
784 ui.status(_("(activating bookmark %s)\n") % b)
783 bookmarks.activate(repo, brev)
785 bookmarks.activate(repo, brev)
784 elif brev:
786 elif brev:
785 if repo._activebookmark:
787 if repo._activebookmark:
786 b = ui.label(repo._activebookmark, 'bookmarks')
788 b = ui.label(repo._activebookmark, 'bookmarks')
787 ui.status(_("(leaving bookmark %s)\n") % b)
789 ui.status(_("(leaving bookmark %s)\n") % b)
788 bookmarks.deactivate(repo)
790 bookmarks.deactivate(repo)
789
791
790 if warndest:
792 if warndest:
791 destutil.statusotherdests(ui, repo)
793 destutil.statusotherdests(ui, repo)
792
794
793 return ret
795 return ret
794
796
795 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None):
797 def merge(repo, node, force=None, remind=True, mergeforce=False, labels=None):
796 """Branch merge with node, resolving changes. Return true if any
798 """Branch merge with node, resolving changes. Return true if any
797 unresolved conflicts."""
799 unresolved conflicts."""
798 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
800 stats = mergemod.update(repo, node, True, force, mergeforce=mergeforce,
799 labels=labels)
801 labels=labels)
800 _showstats(repo, stats)
802 _showstats(repo, stats)
801 if stats[3]:
803 if stats[3]:
802 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
804 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
803 "or 'hg update -C .' to abandon\n"))
805 "or 'hg update -C .' to abandon\n"))
804 elif remind:
806 elif remind:
805 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
807 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
806 return stats[3] > 0
808 return stats[3] > 0
807
809
808 def _incoming(displaychlist, subreporecurse, ui, repo, source,
810 def _incoming(displaychlist, subreporecurse, ui, repo, source,
809 opts, buffered=False):
811 opts, buffered=False):
810 """
812 """
811 Helper for incoming / gincoming.
813 Helper for incoming / gincoming.
812 displaychlist gets called with
814 displaychlist gets called with
813 (remoterepo, incomingchangesetlist, displayer) parameters,
815 (remoterepo, incomingchangesetlist, displayer) parameters,
814 and is supposed to contain only code that can't be unified.
816 and is supposed to contain only code that can't be unified.
815 """
817 """
816 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
818 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
817 other = peer(repo, opts, source)
819 other = peer(repo, opts, source)
818 ui.status(_('comparing with %s\n') % util.hidepassword(source))
820 ui.status(_('comparing with %s\n') % util.hidepassword(source))
819 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
821 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
820
822
821 if revs:
823 if revs:
822 revs = [other.lookup(rev) for rev in revs]
824 revs = [other.lookup(rev) for rev in revs]
823 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
825 other, chlist, cleanupfn = bundlerepo.getremotechanges(ui, repo, other,
824 revs, opts["bundle"], opts["force"])
826 revs, opts["bundle"], opts["force"])
825 try:
827 try:
826 if not chlist:
828 if not chlist:
827 ui.status(_("no changes found\n"))
829 ui.status(_("no changes found\n"))
828 return subreporecurse()
830 return subreporecurse()
829 ui.pager('incoming')
831 ui.pager('incoming')
830 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
832 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
831 displaychlist(other, chlist, displayer)
833 displaychlist(other, chlist, displayer)
832 displayer.close()
834 displayer.close()
833 finally:
835 finally:
834 cleanupfn()
836 cleanupfn()
835 subreporecurse()
837 subreporecurse()
836 return 0 # exit code is zero since we found incoming changes
838 return 0 # exit code is zero since we found incoming changes
837
839
838 def incoming(ui, repo, source, opts):
840 def incoming(ui, repo, source, opts):
839 def subreporecurse():
841 def subreporecurse():
840 ret = 1
842 ret = 1
841 if opts.get('subrepos'):
843 if opts.get('subrepos'):
842 ctx = repo[None]
844 ctx = repo[None]
843 for subpath in sorted(ctx.substate):
845 for subpath in sorted(ctx.substate):
844 sub = ctx.sub(subpath)
846 sub = ctx.sub(subpath)
845 ret = min(ret, sub.incoming(ui, source, opts))
847 ret = min(ret, sub.incoming(ui, source, opts))
846 return ret
848 return ret
847
849
848 def display(other, chlist, displayer):
850 def display(other, chlist, displayer):
849 limit = cmdutil.loglimit(opts)
851 limit = cmdutil.loglimit(opts)
850 if opts.get('newest_first'):
852 if opts.get('newest_first'):
851 chlist.reverse()
853 chlist.reverse()
852 count = 0
854 count = 0
853 for n in chlist:
855 for n in chlist:
854 if limit is not None and count >= limit:
856 if limit is not None and count >= limit:
855 break
857 break
856 parents = [p for p in other.changelog.parents(n) if p != nullid]
858 parents = [p for p in other.changelog.parents(n) if p != nullid]
857 if opts.get('no_merges') and len(parents) == 2:
859 if opts.get('no_merges') and len(parents) == 2:
858 continue
860 continue
859 count += 1
861 count += 1
860 displayer.show(other[n])
862 displayer.show(other[n])
861 return _incoming(display, subreporecurse, ui, repo, source, opts)
863 return _incoming(display, subreporecurse, ui, repo, source, opts)
862
864
863 def _outgoing(ui, repo, dest, opts):
865 def _outgoing(ui, repo, dest, opts):
864 dest = ui.expandpath(dest or 'default-push', dest or 'default')
866 dest = ui.expandpath(dest or 'default-push', dest or 'default')
865 dest, branches = parseurl(dest, opts.get('branch'))
867 dest, branches = parseurl(dest, opts.get('branch'))
866 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
868 ui.status(_('comparing with %s\n') % util.hidepassword(dest))
867 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
869 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
868 if revs:
870 if revs:
869 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
871 revs = [repo.lookup(rev) for rev in scmutil.revrange(repo, revs)]
870
872
871 other = peer(repo, opts, dest)
873 other = peer(repo, opts, dest)
872 outgoing = discovery.findcommonoutgoing(repo, other, revs,
874 outgoing = discovery.findcommonoutgoing(repo, other, revs,
873 force=opts.get('force'))
875 force=opts.get('force'))
874 o = outgoing.missing
876 o = outgoing.missing
875 if not o:
877 if not o:
876 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
878 scmutil.nochangesfound(repo.ui, repo, outgoing.excluded)
877 return o, other
879 return o, other
878
880
879 def outgoing(ui, repo, dest, opts):
881 def outgoing(ui, repo, dest, opts):
880 def recurse():
882 def recurse():
881 ret = 1
883 ret = 1
882 if opts.get('subrepos'):
884 if opts.get('subrepos'):
883 ctx = repo[None]
885 ctx = repo[None]
884 for subpath in sorted(ctx.substate):
886 for subpath in sorted(ctx.substate):
885 sub = ctx.sub(subpath)
887 sub = ctx.sub(subpath)
886 ret = min(ret, sub.outgoing(ui, dest, opts))
888 ret = min(ret, sub.outgoing(ui, dest, opts))
887 return ret
889 return ret
888
890
889 limit = cmdutil.loglimit(opts)
891 limit = cmdutil.loglimit(opts)
890 o, other = _outgoing(ui, repo, dest, opts)
892 o, other = _outgoing(ui, repo, dest, opts)
891 if not o:
893 if not o:
892 cmdutil.outgoinghooks(ui, repo, other, opts, o)
894 cmdutil.outgoinghooks(ui, repo, other, opts, o)
893 return recurse()
895 return recurse()
894
896
895 if opts.get('newest_first'):
897 if opts.get('newest_first'):
896 o.reverse()
898 o.reverse()
897 ui.pager('outgoing')
899 ui.pager('outgoing')
898 displayer = cmdutil.show_changeset(ui, repo, opts)
900 displayer = cmdutil.show_changeset(ui, repo, opts)
899 count = 0
901 count = 0
900 for n in o:
902 for n in o:
901 if limit is not None and count >= limit:
903 if limit is not None and count >= limit:
902 break
904 break
903 parents = [p for p in repo.changelog.parents(n) if p != nullid]
905 parents = [p for p in repo.changelog.parents(n) if p != nullid]
904 if opts.get('no_merges') and len(parents) == 2:
906 if opts.get('no_merges') and len(parents) == 2:
905 continue
907 continue
906 count += 1
908 count += 1
907 displayer.show(repo[n])
909 displayer.show(repo[n])
908 displayer.close()
910 displayer.close()
909 cmdutil.outgoinghooks(ui, repo, other, opts, o)
911 cmdutil.outgoinghooks(ui, repo, other, opts, o)
910 recurse()
912 recurse()
911 return 0 # exit code is zero since we found outgoing changes
913 return 0 # exit code is zero since we found outgoing changes
912
914
913 def verify(repo):
915 def verify(repo):
914 """verify the consistency of a repository"""
916 """verify the consistency of a repository"""
915 ret = verifymod.verify(repo)
917 ret = verifymod.verify(repo)
916
918
917 # Broken subrepo references in hidden csets don't seem worth worrying about,
919 # Broken subrepo references in hidden csets don't seem worth worrying about,
918 # since they can't be pushed/pulled, and --hidden can be used if they are a
920 # since they can't be pushed/pulled, and --hidden can be used if they are a
919 # concern.
921 # concern.
920
922
921 # pathto() is needed for -R case
923 # pathto() is needed for -R case
922 revs = repo.revs("filelog(%s)",
924 revs = repo.revs("filelog(%s)",
923 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
925 util.pathto(repo.root, repo.getcwd(), '.hgsubstate'))
924
926
925 if revs:
927 if revs:
926 repo.ui.status(_('checking subrepo links\n'))
928 repo.ui.status(_('checking subrepo links\n'))
927 for rev in revs:
929 for rev in revs:
928 ctx = repo[rev]
930 ctx = repo[rev]
929 try:
931 try:
930 for subpath in ctx.substate:
932 for subpath in ctx.substate:
931 try:
933 try:
932 ret = (ctx.sub(subpath, allowcreate=False).verify()
934 ret = (ctx.sub(subpath, allowcreate=False).verify()
933 or ret)
935 or ret)
934 except error.RepoError as e:
936 except error.RepoError as e:
935 repo.ui.warn(('%s: %s\n') % (rev, e))
937 repo.ui.warn(('%s: %s\n') % (rev, e))
936 except Exception:
938 except Exception:
937 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
939 repo.ui.warn(_('.hgsubstate is corrupt in revision %s\n') %
938 node.short(ctx.node()))
940 node.short(ctx.node()))
939
941
940 return ret
942 return ret
941
943
942 def remoteui(src, opts):
944 def remoteui(src, opts):
943 'build a remote ui from ui or repo and opts'
945 'build a remote ui from ui or repo and opts'
944 if util.safehasattr(src, 'baseui'): # looks like a repository
946 if util.safehasattr(src, 'baseui'): # looks like a repository
945 dst = src.baseui.copy() # drop repo-specific config
947 dst = src.baseui.copy() # drop repo-specific config
946 src = src.ui # copy target options from repo
948 src = src.ui # copy target options from repo
947 else: # assume it's a global ui object
949 else: # assume it's a global ui object
948 dst = src.copy() # keep all global options
950 dst = src.copy() # keep all global options
949
951
950 # copy ssh-specific options
952 # copy ssh-specific options
951 for o in 'ssh', 'remotecmd':
953 for o in 'ssh', 'remotecmd':
952 v = opts.get(o) or src.config('ui', o)
954 v = opts.get(o) or src.config('ui', o)
953 if v:
955 if v:
954 dst.setconfig("ui", o, v, 'copied')
956 dst.setconfig("ui", o, v, 'copied')
955
957
956 # copy bundle-specific options
958 # copy bundle-specific options
957 r = src.config('bundle', 'mainreporoot')
959 r = src.config('bundle', 'mainreporoot')
958 if r:
960 if r:
959 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
961 dst.setconfig('bundle', 'mainreporoot', r, 'copied')
960
962
961 # copy selected local settings to the remote ui
963 # copy selected local settings to the remote ui
962 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
964 for sect in ('auth', 'hostfingerprints', 'hostsecurity', 'http_proxy'):
963 for key, val in src.configitems(sect):
965 for key, val in src.configitems(sect):
964 dst.setconfig(sect, key, val, 'copied')
966 dst.setconfig(sect, key, val, 'copied')
965 v = src.config('web', 'cacerts')
967 v = src.config('web', 'cacerts')
966 if v:
968 if v:
967 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
969 dst.setconfig('web', 'cacerts', util.expandpath(v), 'copied')
968
970
969 return dst
971 return dst
970
972
971 # Files of interest
973 # Files of interest
972 # Used to check if the repository has changed looking at mtime and size of
974 # Used to check if the repository has changed looking at mtime and size of
973 # these files.
975 # these files.
974 foi = [('spath', '00changelog.i'),
976 foi = [('spath', '00changelog.i'),
975 ('spath', 'phaseroots'), # ! phase can change content at the same size
977 ('spath', 'phaseroots'), # ! phase can change content at the same size
976 ('spath', 'obsstore'),
978 ('spath', 'obsstore'),
977 ('path', 'bookmarks'), # ! bookmark can change content at the same size
979 ('path', 'bookmarks'), # ! bookmark can change content at the same size
978 ]
980 ]
979
981
980 class cachedlocalrepo(object):
982 class cachedlocalrepo(object):
981 """Holds a localrepository that can be cached and reused."""
983 """Holds a localrepository that can be cached and reused."""
982
984
983 def __init__(self, repo):
985 def __init__(self, repo):
984 """Create a new cached repo from an existing repo.
986 """Create a new cached repo from an existing repo.
985
987
986 We assume the passed in repo was recently created. If the
988 We assume the passed in repo was recently created. If the
987 repo has changed between when it was created and when it was
989 repo has changed between when it was created and when it was
988 turned into a cache, it may not refresh properly.
990 turned into a cache, it may not refresh properly.
989 """
991 """
990 assert isinstance(repo, localrepo.localrepository)
992 assert isinstance(repo, localrepo.localrepository)
991 self._repo = repo
993 self._repo = repo
992 self._state, self.mtime = self._repostate()
994 self._state, self.mtime = self._repostate()
993 self._filtername = repo.filtername
995 self._filtername = repo.filtername
994
996
995 def fetch(self):
997 def fetch(self):
996 """Refresh (if necessary) and return a repository.
998 """Refresh (if necessary) and return a repository.
997
999
998 If the cached instance is out of date, it will be recreated
1000 If the cached instance is out of date, it will be recreated
999 automatically and returned.
1001 automatically and returned.
1000
1002
1001 Returns a tuple of the repo and a boolean indicating whether a new
1003 Returns a tuple of the repo and a boolean indicating whether a new
1002 repo instance was created.
1004 repo instance was created.
1003 """
1005 """
1004 # We compare the mtimes and sizes of some well-known files to
1006 # We compare the mtimes and sizes of some well-known files to
1005 # determine if the repo changed. This is not precise, as mtimes
1007 # determine if the repo changed. This is not precise, as mtimes
1006 # are susceptible to clock skew and imprecise filesystems and
1008 # are susceptible to clock skew and imprecise filesystems and
1007 # file content can change while maintaining the same size.
1009 # file content can change while maintaining the same size.
1008
1010
1009 state, mtime = self._repostate()
1011 state, mtime = self._repostate()
1010 if state == self._state:
1012 if state == self._state:
1011 return self._repo, False
1013 return self._repo, False
1012
1014
1013 repo = repository(self._repo.baseui, self._repo.url())
1015 repo = repository(self._repo.baseui, self._repo.url())
1014 if self._filtername:
1016 if self._filtername:
1015 self._repo = repo.filtered(self._filtername)
1017 self._repo = repo.filtered(self._filtername)
1016 else:
1018 else:
1017 self._repo = repo.unfiltered()
1019 self._repo = repo.unfiltered()
1018 self._state = state
1020 self._state = state
1019 self.mtime = mtime
1021 self.mtime = mtime
1020
1022
1021 return self._repo, True
1023 return self._repo, True
1022
1024
1023 def _repostate(self):
1025 def _repostate(self):
1024 state = []
1026 state = []
1025 maxmtime = -1
1027 maxmtime = -1
1026 for attr, fname in foi:
1028 for attr, fname in foi:
1027 prefix = getattr(self._repo, attr)
1029 prefix = getattr(self._repo, attr)
1028 p = os.path.join(prefix, fname)
1030 p = os.path.join(prefix, fname)
1029 try:
1031 try:
1030 st = os.stat(p)
1032 st = os.stat(p)
1031 except OSError:
1033 except OSError:
1032 st = os.stat(prefix)
1034 st = os.stat(prefix)
1033 state.append((st.st_mtime, st.st_size))
1035 state.append((st.st_mtime, st.st_size))
1034 maxmtime = max(maxmtime, st.st_mtime)
1036 maxmtime = max(maxmtime, st.st_mtime)
1035
1037
1036 return tuple(state), maxmtime
1038 return tuple(state), maxmtime
1037
1039
1038 def copy(self):
1040 def copy(self):
1039 """Obtain a copy of this class instance.
1041 """Obtain a copy of this class instance.
1040
1042
1041 A new localrepository instance is obtained. The new instance should be
1043 A new localrepository instance is obtained. The new instance should be
1042 completely independent of the original.
1044 completely independent of the original.
1043 """
1045 """
1044 repo = repository(self._repo.baseui, self._repo.origroot)
1046 repo = repository(self._repo.baseui, self._repo.origroot)
1045 if self._filtername:
1047 if self._filtername:
1046 repo = repo.filtered(self._filtername)
1048 repo = repo.filtered(self._filtername)
1047 else:
1049 else:
1048 repo = repo.unfiltered()
1050 repo = repo.unfiltered()
1049 c = cachedlocalrepo(repo)
1051 c = cachedlocalrepo(repo)
1050 c._state = self._state
1052 c._state = self._state
1051 c.mtime = self.mtime
1053 c.mtime = self.mtime
1052 return c
1054 return c
General Comments 0
You need to be logged in to leave comments. Login now