PageRenderTime 55ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/sshrepo.py

https://bitbucket.org/mirror/mercurial/
Python | 232 lines | 209 code | 14 blank | 9 comment | 9 complexity | 5bd61ad99acae02f935fd63ef92fe5b0 MD5 | raw file
Possible License(s): GPL-2.0
  1. # sshrepo.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 sshrepository(wireproto.wirerepository):
  25. def __init__(self, ui, path, create=False):
  26. self._url = path
  27. self.ui = ui
  28. u = util.url(path, parsequery=False, parsefragment=False)
  29. if u.scheme != 'ssh' or not u.host or u.path is None:
  30. self._abort(error.RepoError(_("couldn't parse location %s") % path))
  31. self.user = u.user
  32. if u.passwd is not None:
  33. self._abort(error.RepoError(_("password in URL not supported")))
  34. self.host = u.host
  35. self.port = u.port
  36. self.path = u.path or "."
  37. sshcmd = self.ui.config("ui", "ssh", "ssh")
  38. remotecmd = self.ui.config("ui", "remotecmd", "hg")
  39. args = util.sshargs(sshcmd, self.host, self.user, self.port)
  40. if create:
  41. cmd = '%s %s %s' % (sshcmd, args,
  42. util.shellquote("%s init %s" %
  43. (_serverquote(remotecmd), _serverquote(self.path))))
  44. ui.note(_('running %s\n') % cmd)
  45. res = util.system(cmd)
  46. if res != 0:
  47. self._abort(error.RepoError(_("could not create remote repo")))
  48. self.validate_repo(ui, sshcmd, args, remotecmd)
  49. def url(self):
  50. return self._url
  51. def validate_repo(self, ui, sshcmd, args, remotecmd):
  52. # cleanup up previous run
  53. self.cleanup()
  54. cmd = '%s %s %s' % (sshcmd, args,
  55. util.shellquote("%s -R %s serve --stdio" %
  56. (_serverquote(remotecmd), _serverquote(self.path))))
  57. ui.note(_('running %s\n') % cmd)
  58. cmd = util.quotecommand(cmd)
  59. self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
  60. # skip any noise generated by remote shell
  61. self._callstream("hello")
  62. r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
  63. lines = ["", "dummy"]
  64. max_noise = 500
  65. while lines[-1] and max_noise:
  66. l = r.readline()
  67. self.readerr()
  68. if lines[-1] == "1\n" and l == "\n":
  69. break
  70. if l:
  71. ui.debug("remote: ", l)
  72. lines.append(l)
  73. max_noise -= 1
  74. else:
  75. self._abort(error.RepoError(_("no suitable response from remote hg")))
  76. self.capabilities = set()
  77. for l in reversed(lines):
  78. if l.startswith("capabilities:"):
  79. self.capabilities.update(l[:-1].split(":")[1].split())
  80. break
  81. def readerr(self):
  82. while True:
  83. size = util.fstat(self.pipee).st_size
  84. if size == 0:
  85. break
  86. s = self.pipee.read(size)
  87. if not s:
  88. break
  89. for l in s.splitlines():
  90. self.ui.status(_("remote: "), l, '\n')
  91. def _abort(self, exception):
  92. self.cleanup()
  93. raise exception
  94. def cleanup(self):
  95. try:
  96. self.pipeo.close()
  97. self.pipei.close()
  98. # read the error descriptor until EOF
  99. for l in self.pipee:
  100. self.ui.status(_("remote: "), l)
  101. self.pipee.close()
  102. except:
  103. pass
  104. __del__ = cleanup
  105. def _callstream(self, cmd, **args):
  106. self.ui.debug("sending %s command\n" % cmd)
  107. self.pipeo.write("%s\n" % cmd)
  108. _func, names = wireproto.commands[cmd]
  109. keys = names.split()
  110. wireargs = {}
  111. for k in keys:
  112. if k == '*':
  113. wireargs['*'] = args
  114. break
  115. else:
  116. wireargs[k] = args[k]
  117. del args[k]
  118. for k, v in sorted(wireargs.iteritems()):
  119. self.pipeo.write("%s %d\n" % (k, len(v)))
  120. if isinstance(v, dict):
  121. for dk, dv in v.iteritems():
  122. self.pipeo.write("%s %d\n" % (dk, len(dv)))
  123. self.pipeo.write(dv)
  124. else:
  125. self.pipeo.write(v)
  126. self.pipeo.flush()
  127. return self.pipei
  128. def _call(self, cmd, **args):
  129. self._callstream(cmd, **args)
  130. return self._recv()
  131. def _callpush(self, cmd, fp, **args):
  132. r = self._call(cmd, **args)
  133. if r:
  134. return '', r
  135. while True:
  136. d = fp.read(4096)
  137. if not d:
  138. break
  139. self._send(d)
  140. self._send("", flush=True)
  141. r = self._recv()
  142. if r:
  143. return '', r
  144. return self._recv(), ''
  145. def _decompress(self, stream):
  146. return stream
  147. def _recv(self):
  148. l = self.pipei.readline()
  149. if l == '\n':
  150. err = []
  151. while True:
  152. line = self.pipee.readline()
  153. if line == '-\n':
  154. break
  155. err.extend([line])
  156. if len(err) > 0:
  157. # strip the trailing newline added to the last line server-side
  158. err[-1] = err[-1][:-1]
  159. self._abort(error.OutOfBandError(*err))
  160. self.readerr()
  161. try:
  162. l = int(l)
  163. except ValueError:
  164. self._abort(error.ResponseError(_("unexpected response:"), l))
  165. return self.pipei.read(l)
  166. def _send(self, data, flush=False):
  167. self.pipeo.write("%d\n" % len(data))
  168. if data:
  169. self.pipeo.write(data)
  170. if flush:
  171. self.pipeo.flush()
  172. self.readerr()
  173. def lock(self):
  174. self._call("lock")
  175. return remotelock(self)
  176. def unlock(self):
  177. self._call("unlock")
  178. def addchangegroup(self, cg, source, url, lock=None):
  179. '''Send a changegroup to the remote server. Return an integer
  180. similar to unbundle(). DEPRECATED, since it requires locking the
  181. remote.'''
  182. d = self._call("addchangegroup")
  183. if d:
  184. self._abort(error.RepoError(_("push refused: %s") % d))
  185. while True:
  186. d = cg.read(4096)
  187. if not d:
  188. break
  189. self.pipeo.write(d)
  190. self.readerr()
  191. self.pipeo.flush()
  192. self.readerr()
  193. r = self._recv()
  194. if not r:
  195. return 1
  196. try:
  197. return int(r)
  198. except ValueError:
  199. self._abort(error.ResponseError(_("unexpected response:"), r))
  200. instance = sshrepository