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