/mercurial/sshpeer.py

https://bitbucket.org/mirror/mercurial/ · Python · 255 lines · 203 code · 35 blank · 17 comment · 53 complexity · 9a2e3efed9c308dad4e0c0f1b4483e16 MD5 · raw file

  1. # sshpeer.py - ssh repository proxy class for mercurial
  2. #
  3. # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
  4. #
  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.
  7. import re
  8. from i18n import _
  9. import util, error, wireproto
  10. class remotelock(object):
  11. def __init__(self, repo):
  12. self.repo = repo
  13. def release(self):
  14. self.repo.unlock()
  15. self.repo = None
  16. def __del__(self):
  17. if self.repo:
  18. self.release()
  19. def _serverquote(s):
  20. '''quote a string for the remote shell ... which we assume is sh'''
  21. if re.match('[a-zA-Z0-9@%_+=:,./-]*$', s):
  22. return s
  23. return "'%s'" % s.replace("'", "'\\''")
  24. class sshpeer(wireproto.wirepeer):
  25. def __init__(self, ui, path, create=False):
  26. self._url = path
  27. self.ui = ui
  28. self.pipeo = self.pipei = self.pipee = None
  29. u = util.url(path, parsequery=False, parsefragment=False)
  30. if u.scheme != 'ssh' or not u.host or u.path is None:
  31. self._abort(error.RepoError(_("couldn't parse location %s") % path))
  32. self.user = u.user
  33. if u.passwd is not None:
  34. self._abort(error.RepoError(_("password in URL not supported")))
  35. self.host = u.host
  36. self.port = u.port
  37. self.path = u.path or "."
  38. sshcmd = self.ui.config("ui", "ssh", "ssh")
  39. remotecmd = self.ui.config("ui", "remotecmd", "hg")
  40. args = util.sshargs(sshcmd, self.host, self.user, self.port)
  41. if create:
  42. cmd = '%s %s %s' % (sshcmd, args,
  43. util.shellquote("%s init %s" %
  44. (_serverquote(remotecmd), _serverquote(self.path))))
  45. ui.debug('running %s\n' % cmd)
  46. res = util.system(cmd)
  47. if res != 0:
  48. self._abort(error.RepoError(_("could not create remote repo")))
  49. self._validaterepo(sshcmd, args, remotecmd)
  50. def url(self):
  51. return self._url
  52. def _validaterepo(self, sshcmd, args, remotecmd):
  53. # cleanup up previous run
  54. self.cleanup()
  55. cmd = '%s %s %s' % (sshcmd, args,
  56. util.shellquote("%s -R %s serve --stdio" %
  57. (_serverquote(remotecmd), _serverquote(self.path))))
  58. self.ui.debug('running %s\n' % cmd)
  59. cmd = util.quotecommand(cmd)
  60. # while self.subprocess isn't used, having it allows the subprocess to
  61. # to clean up correctly later
  62. self.pipeo, self.pipei, self.pipee, self.subprocess = util.popen4(cmd)
  63. # skip any noise generated by remote shell
  64. self._callstream("hello")
  65. r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
  66. lines = ["", "dummy"]
  67. max_noise = 500
  68. while lines[-1] and max_noise:
  69. l = r.readline()
  70. self.readerr()
  71. if lines[-1] == "1\n" and l == "\n":
  72. break
  73. if l:
  74. self.ui.debug("remote: ", l)
  75. lines.append(l)
  76. max_noise -= 1
  77. else:
  78. self._abort(error.RepoError(_('no suitable response from '
  79. 'remote hg')))
  80. self._caps = set()
  81. for l in reversed(lines):
  82. if l.startswith("capabilities:"):
  83. self._caps.update(l[:-1].split(":")[1].split())
  84. break
  85. def _capabilities(self):
  86. return self._caps
  87. def readerr(self):
  88. while True:
  89. size = util.fstat(self.pipee).st_size
  90. if size == 0:
  91. break
  92. s = self.pipee.read(size)
  93. if not s:
  94. break
  95. for l in s.splitlines():
  96. self.ui.status(_("remote: "), l, '\n')
  97. def _abort(self, exception):
  98. self.cleanup()
  99. raise exception
  100. def cleanup(self):
  101. if self.pipeo is None:
  102. return
  103. self.pipeo.close()
  104. self.pipei.close()
  105. try:
  106. # read the error descriptor until EOF
  107. for l in self.pipee:
  108. self.ui.status(_("remote: "), l)
  109. except (IOError, ValueError):
  110. pass
  111. self.pipee.close()
  112. __del__ = cleanup
  113. def _callstream(self, cmd, **args):
  114. self.ui.debug("sending %s command\n" % cmd)
  115. self.pipeo.write("%s\n" % cmd)
  116. _func, names = wireproto.commands[cmd]
  117. keys = names.split()
  118. wireargs = {}
  119. for k in keys:
  120. if k == '*':
  121. wireargs['*'] = args
  122. break
  123. else:
  124. wireargs[k] = args[k]
  125. del args[k]
  126. for k, v in sorted(wireargs.iteritems()):
  127. self.pipeo.write("%s %d\n" % (k, len(v)))
  128. if isinstance(v, dict):
  129. for dk, dv in v.iteritems():
  130. self.pipeo.write("%s %d\n" % (dk, len(dv)))
  131. self.pipeo.write(dv)
  132. else:
  133. self.pipeo.write(v)
  134. self.pipeo.flush()
  135. return self.pipei
  136. def _callcompressable(self, cmd, **args):
  137. return self._callstream(cmd, **args)
  138. def _call(self, cmd, **args):
  139. self._callstream(cmd, **args)
  140. return self._recv()
  141. def _callpush(self, cmd, fp, **args):
  142. r = self._call(cmd, **args)
  143. if r:
  144. return '', r
  145. while True:
  146. d = fp.read(4096)
  147. if not d:
  148. break
  149. self._send(d)
  150. self._send("", flush=True)
  151. r = self._recv()
  152. if r:
  153. return '', r
  154. return self._recv(), ''
  155. def _calltwowaystream(self, cmd, fp, **args):
  156. r = self._call(cmd, **args)
  157. if r:
  158. # XXX needs to be made better
  159. raise util.Abort('unexpected remote reply: %s' % r)
  160. while True:
  161. d = fp.read(4096)
  162. if not d:
  163. break
  164. self._send(d)
  165. self._send("", flush=True)
  166. return self.pipei
  167. def _recv(self):
  168. l = self.pipei.readline()
  169. if l == '\n':
  170. err = []
  171. while True:
  172. line = self.pipee.readline()
  173. if line == '-\n':
  174. break
  175. err.extend([line])
  176. if len(err) > 0:
  177. # strip the trailing newline added to the last line server-side
  178. err[-1] = err[-1][:-1]
  179. self._abort(error.OutOfBandError(*err))
  180. self.readerr()
  181. try:
  182. l = int(l)
  183. except ValueError:
  184. self._abort(error.ResponseError(_("unexpected response:"), l))
  185. return self.pipei.read(l)
  186. def _send(self, data, flush=False):
  187. self.pipeo.write("%d\n" % len(data))
  188. if data:
  189. self.pipeo.write(data)
  190. if flush:
  191. self.pipeo.flush()
  192. self.readerr()
  193. def lock(self):
  194. self._call("lock")
  195. return remotelock(self)
  196. def unlock(self):
  197. self._call("unlock")
  198. def addchangegroup(self, cg, source, url, lock=None):
  199. '''Send a changegroup to the remote server. Return an integer
  200. similar to unbundle(). DEPRECATED, since it requires locking the
  201. remote.'''
  202. d = self._call("addchangegroup")
  203. if d:
  204. self._abort(error.RepoError(_("push refused: %s") % d))
  205. while True:
  206. d = cg.read(4096)
  207. if not d:
  208. break
  209. self.pipeo.write(d)
  210. self.readerr()
  211. self.pipeo.flush()
  212. self.readerr()
  213. r = self._recv()
  214. if not r:
  215. return 1
  216. try:
  217. return int(r)
  218. except ValueError:
  219. self._abort(error.ResponseError(_("unexpected response:"), r))
  220. instance = sshpeer