server_utils.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. # server_utils.py -- Git server compatibility utilities
  2. # Copyright (C) 2010 Google, Inc.
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; version 2
  7. # of the License or (at your option) any later version of
  8. # the License.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program; if not, write to the Free Software
  17. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  18. # MA 02110-1301, USA.
  19. """Utilities for testing git server compatibility."""
  20. import errno
  21. import os
  22. import shutil
  23. import socket
  24. import tempfile
  25. from dulwich.repo import Repo
  26. from dulwich.objects import hex_to_sha
  27. from dulwich.server import (
  28. ReceivePackHandler,
  29. )
  30. from dulwich.tests.utils import (
  31. tear_down_repo,
  32. )
  33. from dulwich.tests.compat.utils import (
  34. import_repo,
  35. run_git_or_fail,
  36. )
  37. class _StubRepo(object):
  38. """A stub repo that just contains a path to tear down."""
  39. def __init__(self, name):
  40. temp_dir = tempfile.mkdtemp()
  41. self.path = os.path.join(temp_dir, name)
  42. os.mkdir(self.path)
  43. def _get_shallow(repo):
  44. shallow_file = repo.get_named_file('shallow')
  45. if not shallow_file:
  46. return []
  47. shallows = []
  48. try:
  49. for line in shallow_file:
  50. sha = line.strip()
  51. if not sha:
  52. continue
  53. hex_to_sha(sha)
  54. shallows.append(sha)
  55. finally:
  56. shallow_file.close()
  57. return shallows
  58. class ServerTests(object):
  59. """Base tests for testing servers.
  60. Does not inherit from TestCase so tests are not automatically run.
  61. """
  62. def import_repos(self):
  63. self._old_repo = import_repo('server_old.export')
  64. self.addCleanup(tear_down_repo, self._old_repo)
  65. self._new_repo = import_repo('server_new.export')
  66. self.addCleanup(tear_down_repo, self._new_repo)
  67. def url(self, port):
  68. return '%s://localhost:%s/' % (self.protocol, port)
  69. def branch_args(self, branches=None):
  70. if branches is None:
  71. branches = ['master', 'branch']
  72. return ['%s:%s' % (b, b) for b in branches]
  73. def test_push_to_dulwich(self):
  74. self.import_repos()
  75. self.assertReposNotEqual(self._old_repo, self._new_repo)
  76. port = self._start_server(self._old_repo)
  77. run_git_or_fail(['push', self.url(port)] + self.branch_args(),
  78. cwd=self._new_repo.path)
  79. self.assertReposEqual(self._old_repo, self._new_repo)
  80. def test_push_to_dulwich_no_op(self):
  81. self._old_repo = import_repo('server_old.export')
  82. self.addCleanup(tear_down_repo, self._old_repo)
  83. self._new_repo = import_repo('server_old.export')
  84. self.addCleanup(tear_down_repo, self._new_repo)
  85. self.assertReposEqual(self._old_repo, self._new_repo)
  86. port = self._start_server(self._old_repo)
  87. run_git_or_fail(['push', self.url(port)] + self.branch_args(),
  88. cwd=self._new_repo.path)
  89. self.assertReposEqual(self._old_repo, self._new_repo)
  90. def test_push_to_dulwich_remove_branch(self):
  91. self._old_repo = import_repo('server_old.export')
  92. self.addCleanup(tear_down_repo, self._old_repo)
  93. self._new_repo = import_repo('server_old.export')
  94. self.addCleanup(tear_down_repo, self._new_repo)
  95. self.assertReposEqual(self._old_repo, self._new_repo)
  96. port = self._start_server(self._old_repo)
  97. run_git_or_fail(['push', self.url(port), ":master"],
  98. cwd=self._new_repo.path)
  99. self.assertEqual(
  100. self._old_repo.get_refs().keys(), ["refs/heads/branch"])
  101. def test_fetch_from_dulwich(self):
  102. self.import_repos()
  103. self.assertReposNotEqual(self._old_repo, self._new_repo)
  104. port = self._start_server(self._new_repo)
  105. run_git_or_fail(['fetch', self.url(port)] + self.branch_args(),
  106. cwd=self._old_repo.path)
  107. # flush the pack cache so any new packs are picked up
  108. self._old_repo.object_store._pack_cache_time = 0
  109. self.assertReposEqual(self._old_repo, self._new_repo)
  110. def test_fetch_from_dulwich_no_op(self):
  111. self._old_repo = import_repo('server_old.export')
  112. self.addCleanup(tear_down_repo, self._old_repo)
  113. self._new_repo = import_repo('server_old.export')
  114. self.addCleanup(tear_down_repo, self._new_repo)
  115. self.assertReposEqual(self._old_repo, self._new_repo)
  116. port = self._start_server(self._new_repo)
  117. run_git_or_fail(['fetch', self.url(port)] + self.branch_args(),
  118. cwd=self._old_repo.path)
  119. # flush the pack cache so any new packs are picked up
  120. self._old_repo.object_store._pack_cache_time = 0
  121. self.assertReposEqual(self._old_repo, self._new_repo)
  122. def test_clone_from_dulwich_empty(self):
  123. old_repo_dir = os.path.join(tempfile.mkdtemp(), 'empty_old')
  124. run_git_or_fail(['init', '--quiet', '--bare', old_repo_dir])
  125. self._old_repo = Repo(old_repo_dir)
  126. self.addCleanup(tear_down_repo, self._old_repo)
  127. port = self._start_server(self._old_repo)
  128. new_repo_base_dir = tempfile.mkdtemp()
  129. try:
  130. new_repo_dir = os.path.join(new_repo_base_dir, 'empty_new')
  131. run_git_or_fail(['clone', self.url(port), new_repo_dir],
  132. cwd=new_repo_base_dir)
  133. new_repo = Repo(new_repo_dir)
  134. self.assertReposEqual(self._old_repo, new_repo)
  135. finally:
  136. # We don't create a Repo from new_repo_dir until after some errors
  137. # may have occurred, so don't depend on tearDown to clean it up.
  138. shutil.rmtree(new_repo_base_dir)
  139. def test_lsremote_from_dulwich(self):
  140. self._repo = import_repo('server_old.export')
  141. port = self._start_server(self._repo)
  142. o = run_git_or_fail(['ls-remote', self.url(port)])
  143. self.assertEqual(len(o.split('\n')), 4)
  144. def test_new_shallow_clone_from_dulwich(self):
  145. self._source_repo = import_repo('server_new.export')
  146. self.addCleanup(tear_down_repo, self._source_repo)
  147. self._stub_repo = _StubRepo('shallow')
  148. self.addCleanup(tear_down_repo, self._stub_repo)
  149. port = self._start_server(self._source_repo)
  150. # Fetch at depth 1
  151. run_git_or_fail(['clone', '--mirror', '--depth=1', '--no-single-branch',
  152. self.url(port), self._stub_repo.path])
  153. clone = self._stub_repo = Repo(self._stub_repo.path)
  154. expected_shallow = ['94de09a530df27ac3bb613aaecdd539e0a0655e1',
  155. 'da5cd81e1883c62a25bb37c4d1f8ad965b29bf8d']
  156. self.assertEqual(expected_shallow, _get_shallow(clone))
  157. self.assertReposNotEqual(clone, self._source_repo)
  158. def test_fetch_same_depth_into_shallow_clone_from_dulwich(self):
  159. self._source_repo = import_repo('server_new.export')
  160. self.addCleanup(tear_down_repo, self._source_repo)
  161. self._stub_repo = _StubRepo('shallow')
  162. self.addCleanup(tear_down_repo, self._stub_repo)
  163. port = self._start_server(self._source_repo)
  164. # Fetch at depth 1
  165. run_git_or_fail(['clone', '--mirror', '--depth=1', '--no-single-branch',
  166. self.url(port), self._stub_repo.path])
  167. clone = self._stub_repo = Repo(self._stub_repo.path)
  168. # Fetching at the same depth is a no-op.
  169. run_git_or_fail(
  170. ['fetch', '--depth=1', self.url(port)] + self.branch_args(),
  171. cwd=self._stub_repo.path)
  172. expected_shallow = ['94de09a530df27ac3bb613aaecdd539e0a0655e1',
  173. 'da5cd81e1883c62a25bb37c4d1f8ad965b29bf8d']
  174. self.assertEqual(expected_shallow, _get_shallow(clone))
  175. self.assertReposNotEqual(clone, self._source_repo)
  176. def test_fetch_full_depth_into_shallow_clone_from_dulwich(self):
  177. self._source_repo = import_repo('server_new.export')
  178. self.addCleanup(tear_down_repo, self._source_repo)
  179. self._stub_repo = _StubRepo('shallow')
  180. self.addCleanup(tear_down_repo, self._stub_repo)
  181. port = self._start_server(self._source_repo)
  182. # Fetch at depth 1
  183. run_git_or_fail(['clone', '--mirror', '--depth=1', '--no-single-branch',
  184. self.url(port), self._stub_repo.path])
  185. clone = self._stub_repo = Repo(self._stub_repo.path)
  186. # Fetching at the same depth is a no-op.
  187. run_git_or_fail(
  188. ['fetch', '--depth=1', self.url(port)] + self.branch_args(),
  189. cwd=self._stub_repo.path)
  190. # The whole repo only has depth 3, so it should equal server_new.
  191. run_git_or_fail(
  192. ['fetch', '--depth=3', self.url(port)] + self.branch_args(),
  193. cwd=self._stub_repo.path)
  194. self.assertEqual([], _get_shallow(clone))
  195. self.assertReposEqual(clone, self._source_repo)
  196. # TODO(dborowitz): Come up with a better way of testing various permutations of
  197. # capabilities. The only reason it is the way it is now is that side-band-64k
  198. # was only recently introduced into git-receive-pack.
  199. class NoSideBand64kReceivePackHandler(ReceivePackHandler):
  200. """ReceivePackHandler that does not support side-band-64k."""
  201. @classmethod
  202. def capabilities(cls):
  203. return tuple(c for c in ReceivePackHandler.capabilities()
  204. if c != 'side-band-64k')
  205. def ignore_error(error):
  206. """Check whether this error is safe to ignore."""
  207. (e_type, e_value, e_tb) = error
  208. return (issubclass(e_type, socket.error) and
  209. e_value[0] in (errno.ECONNRESET, errno.EPIPE))