test_client.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825
  1. # test_client.py -- Tests for the git protocol, client side
  2. # Copyright (C) 2009 Jelmer Vernooij <jelmer@samba.org>
  3. #
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. from contextlib import closing
  21. from io import BytesIO
  22. import sys
  23. import shutil
  24. import tempfile
  25. import dulwich
  26. from dulwich import (
  27. client,
  28. )
  29. from dulwich.client import (
  30. LocalGitClient,
  31. TraditionalGitClient,
  32. TCPGitClient,
  33. SSHGitClient,
  34. HttpGitClient,
  35. ReportStatusParser,
  36. SendPackError,
  37. UpdateRefsError,
  38. get_transport_and_path,
  39. get_transport_and_path_from_url,
  40. )
  41. from dulwich.tests import (
  42. TestCase,
  43. )
  44. from dulwich.protocol import (
  45. TCP_GIT_PORT,
  46. Protocol,
  47. )
  48. from dulwich.pack import (
  49. write_pack_objects,
  50. )
  51. from dulwich.objects import (
  52. Commit,
  53. Tree
  54. )
  55. from dulwich.repo import (
  56. MemoryRepo,
  57. Repo,
  58. )
  59. from dulwich.tests import skipIf
  60. from dulwich.tests.utils import (
  61. open_repo,
  62. tear_down_repo,
  63. )
  64. class DummyClient(TraditionalGitClient):
  65. def __init__(self, can_read, read, write):
  66. self.can_read = can_read
  67. self.read = read
  68. self.write = write
  69. TraditionalGitClient.__init__(self)
  70. def _connect(self, service, path):
  71. return Protocol(self.read, self.write), self.can_read
  72. # TODO(durin42): add unit-level tests of GitClient
  73. class GitClientTests(TestCase):
  74. def setUp(self):
  75. super(GitClientTests, self).setUp()
  76. self.rout = BytesIO()
  77. self.rin = BytesIO()
  78. self.client = DummyClient(lambda x: True, self.rin.read,
  79. self.rout.write)
  80. def test_caps(self):
  81. agent_cap = ('agent=dulwich/%d.%d.%d' % dulwich.__version__).encode('ascii')
  82. self.assertEqual(set([b'multi_ack', b'side-band-64k', b'ofs-delta',
  83. b'thin-pack', b'multi_ack_detailed',
  84. agent_cap]),
  85. set(self.client._fetch_capabilities))
  86. self.assertEqual(set([b'ofs-delta', b'report-status', b'side-band-64k',
  87. agent_cap]),
  88. set(self.client._send_capabilities))
  89. def test_archive_ack(self):
  90. self.rin.write(
  91. b'0009NACK\n'
  92. b'0000')
  93. self.rin.seek(0)
  94. self.client.archive(b'bla', b'HEAD', None, None)
  95. self.assertEqual(self.rout.getvalue(), b'0011argument HEAD0000')
  96. def test_fetch_empty(self):
  97. self.rin.write(b'0000')
  98. self.rin.seek(0)
  99. def check_heads(heads):
  100. self.assertIs(heads, None)
  101. return []
  102. ret = self.client.fetch_pack(b'/', check_heads, None, None)
  103. self.assertIs(None, ret)
  104. def test_fetch_pack_ignores_magic_ref(self):
  105. self.rin.write(
  106. b'00000000000000000000000000000000000000000000 capabilities^{}\x00 multi_ack '
  107. b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
  108. b'include-tag\n'
  109. b'0000')
  110. self.rin.seek(0)
  111. def check_heads(heads):
  112. self.assertEquals({}, heads)
  113. return []
  114. ret = self.client.fetch_pack(b'bla', check_heads, None, None, None)
  115. self.assertIs(None, ret)
  116. self.assertEqual(self.rout.getvalue(), b'0000')
  117. def test_fetch_pack_none(self):
  118. self.rin.write(
  119. b'008855dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 HEAD.multi_ack '
  120. b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
  121. b'include-tag\n'
  122. b'0000')
  123. self.rin.seek(0)
  124. self.client.fetch_pack(b'bla', lambda heads: [], None, None, None)
  125. self.assertEqual(self.rout.getvalue(), b'0000')
  126. def test_send_pack_no_sideband64k_with_update_ref_error(self):
  127. # No side-bank-64k reported by server shouldn't try to parse
  128. # side band data
  129. pkts = [b'55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 capabilities^{}'
  130. b'\x00 report-status delete-refs ofs-delta\n',
  131. b'',
  132. b"unpack ok",
  133. b"ng refs/foo/bar pre-receive hook declined",
  134. b'']
  135. for pkt in pkts:
  136. if pkt == b'':
  137. self.rin.write(b"0000")
  138. else:
  139. self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
  140. self.rin.seek(0)
  141. tree = Tree()
  142. commit = Commit()
  143. commit.tree = tree
  144. commit.parents = []
  145. commit.author = commit.committer = b'test user'
  146. commit.commit_time = commit.author_time = 1174773719
  147. commit.commit_timezone = commit.author_timezone = 0
  148. commit.encoding = b'UTF-8'
  149. commit.message = b'test message'
  150. def determine_wants(refs):
  151. return {b'refs/foo/bar': commit.id, }
  152. def generate_pack_contents(have, want):
  153. return [(commit, None), (tree, ''), ]
  154. self.assertRaises(UpdateRefsError,
  155. self.client.send_pack, "blah",
  156. determine_wants, generate_pack_contents)
  157. def test_send_pack_none(self):
  158. self.rin.write(
  159. b'0078310ca9477129b8586fa2afc779c1f57cf64bba6c '
  160. b'refs/heads/master\x00 report-status delete-refs '
  161. b'side-band-64k quiet ofs-delta\n'
  162. b'0000')
  163. self.rin.seek(0)
  164. def determine_wants(refs):
  165. return {
  166. b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
  167. }
  168. def generate_pack_contents(have, want):
  169. return {}
  170. self.client.send_pack(b'/', determine_wants, generate_pack_contents)
  171. self.assertEqual(self.rout.getvalue(), b'0000')
  172. def test_send_pack_keep_and_delete(self):
  173. self.rin.write(
  174. b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
  175. b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
  176. b'003f310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/keepme\n'
  177. b'0000000eunpack ok\n'
  178. b'0019ok refs/heads/master\n'
  179. b'0000')
  180. self.rin.seek(0)
  181. def determine_wants(refs):
  182. return {b'refs/heads/master': b'0' * 40}
  183. def generate_pack_contents(have, want):
  184. return {}
  185. self.client.send_pack(b'/', determine_wants, generate_pack_contents)
  186. self.assertIn(
  187. self.rout.getvalue(),
  188. [b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
  189. b'0000000000000000000000000000000000000000 '
  190. b'refs/heads/master\x00report-status ofs-delta0000',
  191. b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
  192. b'0000000000000000000000000000000000000000 '
  193. b'refs/heads/master\x00ofs-delta report-status0000'])
  194. def test_send_pack_delete_only(self):
  195. self.rin.write(
  196. b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
  197. b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
  198. b'0000000eunpack ok\n'
  199. b'0019ok refs/heads/master\n'
  200. b'0000')
  201. self.rin.seek(0)
  202. def determine_wants(refs):
  203. return {b'refs/heads/master': b'0' * 40}
  204. def generate_pack_contents(have, want):
  205. return {}
  206. self.client.send_pack(b'/', determine_wants, generate_pack_contents)
  207. self.assertIn(
  208. self.rout.getvalue(),
  209. [b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
  210. b'0000000000000000000000000000000000000000 '
  211. b'refs/heads/master\x00report-status ofs-delta0000',
  212. b'007f310ca9477129b8586fa2afc779c1f57cf64bba6c '
  213. b'0000000000000000000000000000000000000000 '
  214. b'refs/heads/master\x00ofs-delta report-status0000'])
  215. def test_send_pack_new_ref_only(self):
  216. self.rin.write(
  217. b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
  218. b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
  219. b'0000000eunpack ok\n'
  220. b'0019ok refs/heads/blah12\n'
  221. b'0000')
  222. self.rin.seek(0)
  223. def determine_wants(refs):
  224. return {
  225. b'refs/heads/blah12':
  226. b'310ca9477129b8586fa2afc779c1f57cf64bba6c',
  227. b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
  228. }
  229. def generate_pack_contents(have, want):
  230. return {}
  231. f = BytesIO()
  232. write_pack_objects(f, {})
  233. self.client.send_pack('/', determine_wants, generate_pack_contents)
  234. self.assertIn(
  235. self.rout.getvalue(),
  236. [b'007f0000000000000000000000000000000000000000 '
  237. b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
  238. b'refs/heads/blah12\x00report-status ofs-delta0000' +
  239. f.getvalue(),
  240. b'007f0000000000000000000000000000000000000000 '
  241. b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
  242. b'refs/heads/blah12\x00ofs-delta report-status0000' +
  243. f.getvalue()])
  244. def test_send_pack_new_ref(self):
  245. self.rin.write(
  246. b'0064310ca9477129b8586fa2afc779c1f57cf64bba6c '
  247. b'refs/heads/master\x00 report-status delete-refs ofs-delta\n'
  248. b'0000000eunpack ok\n'
  249. b'0019ok refs/heads/blah12\n'
  250. b'0000')
  251. self.rin.seek(0)
  252. tree = Tree()
  253. commit = Commit()
  254. commit.tree = tree
  255. commit.parents = []
  256. commit.author = commit.committer = b'test user'
  257. commit.commit_time = commit.author_time = 1174773719
  258. commit.commit_timezone = commit.author_timezone = 0
  259. commit.encoding = b'UTF-8'
  260. commit.message = b'test message'
  261. def determine_wants(refs):
  262. return {
  263. b'refs/heads/blah12': commit.id,
  264. b'refs/heads/master': b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
  265. }
  266. def generate_pack_contents(have, want):
  267. return [(commit, None), (tree, b''), ]
  268. f = BytesIO()
  269. write_pack_objects(f, generate_pack_contents(None, None))
  270. self.client.send_pack(b'/', determine_wants, generate_pack_contents)
  271. self.assertIn(
  272. self.rout.getvalue(),
  273. [b'007f0000000000000000000000000000000000000000 ' + commit.id +
  274. b' refs/heads/blah12\x00report-status ofs-delta0000' + f.getvalue(),
  275. b'007f0000000000000000000000000000000000000000 ' + commit.id +
  276. b' refs/heads/blah12\x00ofs-delta report-status0000' + f.getvalue()])
  277. def test_send_pack_no_deleteref_delete_only(self):
  278. pkts = [b'310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/master'
  279. b'\x00 report-status ofs-delta\n',
  280. b'',
  281. b'']
  282. for pkt in pkts:
  283. if pkt == b'':
  284. self.rin.write(b"0000")
  285. else:
  286. self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
  287. self.rin.seek(0)
  288. def determine_wants(refs):
  289. return {b'refs/heads/master': b'0' * 40}
  290. def generate_pack_contents(have, want):
  291. return {}
  292. self.assertRaises(UpdateRefsError,
  293. self.client.send_pack, b"/",
  294. determine_wants, generate_pack_contents)
  295. self.assertEqual(self.rout.getvalue(), b'0000')
  296. class TestGetTransportAndPath(TestCase):
  297. def test_tcp(self):
  298. c, path = get_transport_and_path('git://foo.com/bar/baz')
  299. self.assertTrue(isinstance(c, TCPGitClient))
  300. self.assertEqual('foo.com', c._host)
  301. self.assertEqual(TCP_GIT_PORT, c._port)
  302. self.assertEqual('/bar/baz', path)
  303. def test_tcp_port(self):
  304. c, path = get_transport_and_path('git://foo.com:1234/bar/baz')
  305. self.assertTrue(isinstance(c, TCPGitClient))
  306. self.assertEqual('foo.com', c._host)
  307. self.assertEqual(1234, c._port)
  308. self.assertEqual('/bar/baz', path)
  309. def test_git_ssh_explicit(self):
  310. c, path = get_transport_and_path('git+ssh://foo.com/bar/baz')
  311. self.assertTrue(isinstance(c, SSHGitClient))
  312. self.assertEqual('foo.com', c.host)
  313. self.assertEqual(None, c.port)
  314. self.assertEqual(None, c.username)
  315. self.assertEqual('bar/baz', path)
  316. def test_ssh_explicit(self):
  317. c, path = get_transport_and_path('ssh://foo.com/bar/baz')
  318. self.assertTrue(isinstance(c, SSHGitClient))
  319. self.assertEqual('foo.com', c.host)
  320. self.assertEqual(None, c.port)
  321. self.assertEqual(None, c.username)
  322. self.assertEqual('bar/baz', path)
  323. def test_ssh_port_explicit(self):
  324. c, path = get_transport_and_path(
  325. 'git+ssh://foo.com:1234/bar/baz')
  326. self.assertTrue(isinstance(c, SSHGitClient))
  327. self.assertEqual('foo.com', c.host)
  328. self.assertEqual(1234, c.port)
  329. self.assertEqual('bar/baz', path)
  330. def test_ssh_abspath_explicit(self):
  331. c, path = get_transport_and_path('git+ssh://foo.com//bar/baz')
  332. self.assertTrue(isinstance(c, SSHGitClient))
  333. self.assertEqual('foo.com', c.host)
  334. self.assertEqual(None, c.port)
  335. self.assertEqual(None, c.username)
  336. self.assertEqual('/bar/baz', path)
  337. def test_ssh_port_abspath_explicit(self):
  338. c, path = get_transport_and_path(
  339. 'git+ssh://foo.com:1234//bar/baz')
  340. self.assertTrue(isinstance(c, SSHGitClient))
  341. self.assertEqual('foo.com', c.host)
  342. self.assertEqual(1234, c.port)
  343. self.assertEqual('/bar/baz', path)
  344. def test_ssh_implicit(self):
  345. c, path = get_transport_and_path('foo:/bar/baz')
  346. self.assertTrue(isinstance(c, SSHGitClient))
  347. self.assertEqual('foo', c.host)
  348. self.assertEqual(None, c.port)
  349. self.assertEqual(None, c.username)
  350. self.assertEqual('/bar/baz', path)
  351. def test_ssh_host(self):
  352. c, path = get_transport_and_path('foo.com:/bar/baz')
  353. self.assertTrue(isinstance(c, SSHGitClient))
  354. self.assertEqual('foo.com', c.host)
  355. self.assertEqual(None, c.port)
  356. self.assertEqual(None, c.username)
  357. self.assertEqual('/bar/baz', path)
  358. def test_ssh_user_host(self):
  359. c, path = get_transport_and_path('user@foo.com:/bar/baz')
  360. self.assertTrue(isinstance(c, SSHGitClient))
  361. self.assertEqual('foo.com', c.host)
  362. self.assertEqual(None, c.port)
  363. self.assertEqual('user', c.username)
  364. self.assertEqual('/bar/baz', path)
  365. def test_ssh_relpath(self):
  366. c, path = get_transport_and_path('foo:bar/baz')
  367. self.assertTrue(isinstance(c, SSHGitClient))
  368. self.assertEqual('foo', c.host)
  369. self.assertEqual(None, c.port)
  370. self.assertEqual(None, c.username)
  371. self.assertEqual('bar/baz', path)
  372. def test_ssh_host_relpath(self):
  373. c, path = get_transport_and_path('foo.com:bar/baz')
  374. self.assertTrue(isinstance(c, SSHGitClient))
  375. self.assertEqual('foo.com', c.host)
  376. self.assertEqual(None, c.port)
  377. self.assertEqual(None, c.username)
  378. self.assertEqual('bar/baz', path)
  379. def test_ssh_user_host_relpath(self):
  380. c, path = get_transport_and_path('user@foo.com:bar/baz')
  381. self.assertTrue(isinstance(c, SSHGitClient))
  382. self.assertEqual('foo.com', c.host)
  383. self.assertEqual(None, c.port)
  384. self.assertEqual('user', c.username)
  385. self.assertEqual('bar/baz', path)
  386. def test_local(self):
  387. c, path = get_transport_and_path('foo.bar/baz')
  388. self.assertTrue(isinstance(c, LocalGitClient))
  389. self.assertEqual('foo.bar/baz', path)
  390. @skipIf(sys.platform != 'win32', 'Behaviour only happens on windows.')
  391. def test_local_abs_windows_path(self):
  392. c, path = get_transport_and_path('C:\\foo.bar\\baz')
  393. self.assertTrue(isinstance(c, LocalGitClient))
  394. self.assertEqual('C:\\foo.bar\\baz', path)
  395. def test_error(self):
  396. # Need to use a known urlparse.uses_netloc URL scheme to get the
  397. # expected parsing of the URL on Python versions less than 2.6.5
  398. c, path = get_transport_and_path('prospero://bar/baz')
  399. self.assertTrue(isinstance(c, SSHGitClient))
  400. def test_http(self):
  401. url = 'https://github.com/jelmer/dulwich'
  402. c, path = get_transport_and_path(url)
  403. self.assertTrue(isinstance(c, HttpGitClient))
  404. self.assertEqual('/jelmer/dulwich', path)
  405. def test_http_auth(self):
  406. url = 'https://user:passwd@github.com/jelmer/dulwich'
  407. c, path = get_transport_and_path(url)
  408. self.assertTrue(isinstance(c, HttpGitClient))
  409. self.assertEqual('/jelmer/dulwich', path)
  410. self.assertEqual('user', c._username)
  411. self.assertEqual('passwd', c._password)
  412. def test_http_no_auth(self):
  413. url = 'https://github.com/jelmer/dulwich'
  414. c, path = get_transport_and_path(url)
  415. self.assertTrue(isinstance(c, HttpGitClient))
  416. self.assertEqual('/jelmer/dulwich', path)
  417. self.assertIs(None, c._username)
  418. self.assertIs(None, c._password)
  419. class TestGetTransportAndPathFromUrl(TestCase):
  420. def test_tcp(self):
  421. c, path = get_transport_and_path_from_url('git://foo.com/bar/baz')
  422. self.assertTrue(isinstance(c, TCPGitClient))
  423. self.assertEqual('foo.com', c._host)
  424. self.assertEqual(TCP_GIT_PORT, c._port)
  425. self.assertEqual('/bar/baz', path)
  426. def test_tcp_port(self):
  427. c, path = get_transport_and_path_from_url('git://foo.com:1234/bar/baz')
  428. self.assertTrue(isinstance(c, TCPGitClient))
  429. self.assertEqual('foo.com', c._host)
  430. self.assertEqual(1234, c._port)
  431. self.assertEqual('/bar/baz', path)
  432. def test_ssh_explicit(self):
  433. c, path = get_transport_and_path_from_url('git+ssh://foo.com/bar/baz')
  434. self.assertTrue(isinstance(c, SSHGitClient))
  435. self.assertEqual('foo.com', c.host)
  436. self.assertEqual(None, c.port)
  437. self.assertEqual(None, c.username)
  438. self.assertEqual('bar/baz', path)
  439. def test_ssh_port_explicit(self):
  440. c, path = get_transport_and_path_from_url(
  441. 'git+ssh://foo.com:1234/bar/baz')
  442. self.assertTrue(isinstance(c, SSHGitClient))
  443. self.assertEqual('foo.com', c.host)
  444. self.assertEqual(1234, c.port)
  445. self.assertEqual('bar/baz', path)
  446. def test_ssh_abspath_explicit(self):
  447. c, path = get_transport_and_path_from_url('git+ssh://foo.com//bar/baz')
  448. self.assertTrue(isinstance(c, SSHGitClient))
  449. self.assertEqual('foo.com', c.host)
  450. self.assertEqual(None, c.port)
  451. self.assertEqual(None, c.username)
  452. self.assertEqual('/bar/baz', path)
  453. def test_ssh_port_abspath_explicit(self):
  454. c, path = get_transport_and_path_from_url(
  455. 'git+ssh://foo.com:1234//bar/baz')
  456. self.assertTrue(isinstance(c, SSHGitClient))
  457. self.assertEqual('foo.com', c.host)
  458. self.assertEqual(1234, c.port)
  459. self.assertEqual('/bar/baz', path)
  460. def test_ssh_host_relpath(self):
  461. self.assertRaises(ValueError, get_transport_and_path_from_url,
  462. 'foo.com:bar/baz')
  463. def test_ssh_user_host_relpath(self):
  464. self.assertRaises(ValueError, get_transport_and_path_from_url,
  465. 'user@foo.com:bar/baz')
  466. def test_local_path(self):
  467. self.assertRaises(ValueError, get_transport_and_path_from_url,
  468. 'foo.bar/baz')
  469. def test_error(self):
  470. # Need to use a known urlparse.uses_netloc URL scheme to get the
  471. # expected parsing of the URL on Python versions less than 2.6.5
  472. self.assertRaises(ValueError, get_transport_and_path_from_url,
  473. 'prospero://bar/baz')
  474. def test_http(self):
  475. url = 'https://github.com/jelmer/dulwich'
  476. c, path = get_transport_and_path_from_url(url)
  477. self.assertTrue(isinstance(c, HttpGitClient))
  478. self.assertEqual('/jelmer/dulwich', path)
  479. def test_file(self):
  480. c, path = get_transport_and_path_from_url('file:///home/jelmer/foo')
  481. self.assertTrue(isinstance(c, LocalGitClient))
  482. self.assertEqual('/home/jelmer/foo', path)
  483. class TestSSHVendor(object):
  484. def __init__(self):
  485. self.host = None
  486. self.command = ""
  487. self.username = None
  488. self.port = None
  489. def run_command(self, host, command, username=None, port=None):
  490. if not isinstance(command, bytes):
  491. raise TypeError(command)
  492. self.host = host
  493. self.command = command
  494. self.username = username
  495. self.port = port
  496. class Subprocess: pass
  497. setattr(Subprocess, 'read', lambda: None)
  498. setattr(Subprocess, 'write', lambda: None)
  499. setattr(Subprocess, 'close', lambda: None)
  500. setattr(Subprocess, 'can_read', lambda: None)
  501. return Subprocess()
  502. class SSHGitClientTests(TestCase):
  503. def setUp(self):
  504. super(SSHGitClientTests, self).setUp()
  505. self.server = TestSSHVendor()
  506. self.real_vendor = client.get_ssh_vendor
  507. client.get_ssh_vendor = lambda: self.server
  508. self.client = SSHGitClient('git.samba.org')
  509. def tearDown(self):
  510. super(SSHGitClientTests, self).tearDown()
  511. client.get_ssh_vendor = self.real_vendor
  512. def test_get_url(self):
  513. path = '/tmp/repo.git'
  514. c = SSHGitClient('git.samba.org')
  515. url = c.get_url(path)
  516. self.assertEqual('ssh://git.samba.org/tmp/repo.git', url)
  517. def test_get_url_with_username_and_port(self):
  518. path = '/tmp/repo.git'
  519. c = SSHGitClient('git.samba.org', port=2222, username='user')
  520. url = c.get_url(path)
  521. self.assertEqual('ssh://user@git.samba.org:2222/tmp/repo.git', url)
  522. def test_default_command(self):
  523. self.assertEqual(b'git-upload-pack',
  524. self.client._get_cmd_path(b'upload-pack'))
  525. def test_alternative_command_path(self):
  526. self.client.alternative_paths[b'upload-pack'] = (
  527. b'/usr/lib/git/git-upload-pack')
  528. self.assertEqual(b'/usr/lib/git/git-upload-pack',
  529. self.client._get_cmd_path(b'upload-pack'))
  530. def test_alternative_command_path_spaces(self):
  531. self.client.alternative_paths[b'upload-pack'] = (
  532. b'/usr/lib/git/git-upload-pack -ibla')
  533. self.assertEqual(b"/usr/lib/git/git-upload-pack -ibla",
  534. self.client._get_cmd_path(b'upload-pack'))
  535. def test_connect(self):
  536. server = self.server
  537. client = self.client
  538. client.username = b"username"
  539. client.port = 1337
  540. client._connect(b"command", b"/path/to/repo")
  541. self.assertEqual(b"username", server.username)
  542. self.assertEqual(1337, server.port)
  543. self.assertEqual(b"git-command '/path/to/repo'", server.command)
  544. client._connect(b"relative-command", b"/~/path/to/repo")
  545. self.assertEqual(b"git-relative-command '~/path/to/repo'",
  546. server.command)
  547. class ReportStatusParserTests(TestCase):
  548. def test_invalid_pack(self):
  549. parser = ReportStatusParser()
  550. parser.handle_packet(b"unpack error - foo bar")
  551. parser.handle_packet(b"ok refs/foo/bar")
  552. parser.handle_packet(None)
  553. self.assertRaises(SendPackError, parser.check)
  554. def test_update_refs_error(self):
  555. parser = ReportStatusParser()
  556. parser.handle_packet(b"unpack ok")
  557. parser.handle_packet(b"ng refs/foo/bar need to pull")
  558. parser.handle_packet(None)
  559. self.assertRaises(UpdateRefsError, parser.check)
  560. def test_ok(self):
  561. parser = ReportStatusParser()
  562. parser.handle_packet(b"unpack ok")
  563. parser.handle_packet(b"ok refs/foo/bar")
  564. parser.handle_packet(None)
  565. parser.check()
  566. class LocalGitClientTests(TestCase):
  567. def test_get_url(self):
  568. path = "/tmp/repo.git"
  569. c = LocalGitClient()
  570. url = c.get_url(path)
  571. self.assertEqual('file:///tmp/repo.git', url)
  572. def test_fetch_into_empty(self):
  573. c = LocalGitClient()
  574. t = MemoryRepo()
  575. s = open_repo('a.git')
  576. self.addCleanup(tear_down_repo, s)
  577. self.assertEqual(s.get_refs(), c.fetch(s.path, t))
  578. def test_fetch_empty(self):
  579. c = LocalGitClient()
  580. s = open_repo('a.git')
  581. self.addCleanup(tear_down_repo, s)
  582. out = BytesIO()
  583. walker = {}
  584. ret = c.fetch_pack(s.path, lambda heads: [], graph_walker=walker,
  585. pack_data=out.write)
  586. self.assertEqual({
  587. 'HEAD': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  588. 'refs/heads/master': 'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  589. 'refs/tags/mytag': '28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
  590. 'refs/tags/mytag-packed': 'b0931cadc54336e78a1d980420e3268903b57a50'
  591. }, ret)
  592. self.assertEqual(b"PACK\x00\x00\x00\x02\x00\x00\x00\x00\x02\x9d\x08"
  593. b"\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e", out.getvalue())
  594. def test_fetch_pack_none(self):
  595. c = LocalGitClient()
  596. s = open_repo('a.git')
  597. self.addCleanup(tear_down_repo, s)
  598. out = BytesIO()
  599. walker = MemoryRepo().get_graph_walker()
  600. c.fetch_pack(s.path,
  601. lambda heads: [b"a90fa2d900a17e99b433217e988c4eb4a2e9a097"],
  602. graph_walker=walker, pack_data=out.write)
  603. # Hardcoding is not ideal, but we'll fix that some other day..
  604. self.assertTrue(out.getvalue().startswith(b'PACK\x00\x00\x00\x02\x00\x00\x00\x07'))
  605. def test_send_pack_without_changes(self):
  606. local = open_repo('a.git')
  607. self.addCleanup(tear_down_repo, local)
  608. target = open_repo('a.git')
  609. self.addCleanup(tear_down_repo, target)
  610. self.send_and_verify(b"master", local, target)
  611. def test_send_pack_with_changes(self):
  612. local = open_repo('a.git')
  613. self.addCleanup(tear_down_repo, local)
  614. target_path = tempfile.mkdtemp()
  615. self.addCleanup(shutil.rmtree, target_path)
  616. with closing(Repo.init_bare(target_path)) as target:
  617. self.send_and_verify(b"master", local, target)
  618. def test_get_refs(self):
  619. local = open_repo('refs.git')
  620. self.addCleanup(tear_down_repo, local)
  621. client = LocalGitClient()
  622. refs = client.get_refs(local.path)
  623. self.assertDictEqual(local.refs.as_dict(), refs)
  624. def send_and_verify(self, branch, local, target):
  625. """Send a branch from local to remote repository and verify it worked."""
  626. client = LocalGitClient()
  627. ref_name = b"refs/heads/" + branch
  628. new_refs = client.send_pack(target.path,
  629. lambda _: { ref_name: local.refs[ref_name] },
  630. local.object_store.generate_pack_contents)
  631. self.assertEqual(local.refs[ref_name], new_refs[ref_name])
  632. obj_local = local.get_object(new_refs[ref_name])
  633. obj_target = target.get_object(new_refs[ref_name])
  634. self.assertEqual(obj_local, obj_target)
  635. class HttpGitClientTests(TestCase):
  636. def test_get_url(self):
  637. base_url = 'https://github.com/jelmer/dulwich'
  638. path = '/jelmer/dulwich'
  639. c = HttpGitClient(base_url)
  640. url = c.get_url(path)
  641. self.assertEqual('https://github.com/jelmer/dulwich', url)
  642. def test_get_url_with_username_and_passwd(self):
  643. base_url = 'https://github.com/jelmer/dulwich'
  644. path = '/jelmer/dulwich'
  645. c = HttpGitClient(base_url, username='USERNAME', password='PASSWD')
  646. url = c.get_url(path)
  647. self.assertEqual('https://github.com/jelmer/dulwich', url)
  648. def test_init_username_passwd_set(self):
  649. url = 'https://github.com/jelmer/dulwich'
  650. c = HttpGitClient(url, config=None, username='user', password='passwd')
  651. self.assertEqual('user', c._username)
  652. self.assertEqual('passwd', c._password)
  653. [pw_handler] = [
  654. h for h in c.opener.handlers if getattr(h, 'passwd', None) is not None]
  655. self.assertEqual(
  656. ('user', 'passwd'),
  657. pw_handler.passwd.find_user_password(
  658. None, 'https://github.com/jelmer/dulwich'))
  659. def test_init_no_username_passwd(self):
  660. url = 'https://github.com/jelmer/dulwich'
  661. c = HttpGitClient(url, config=None)
  662. self.assertIs(None, c._username)
  663. self.assertIs(None, c._password)
  664. pw_handler = [
  665. h for h in c.opener.handlers if getattr(h, 'passwd', None) is not None]
  666. self.assertEqual(0, len(pw_handler))
  667. class TCPGitClientTests(TestCase):
  668. def test_get_url(self):
  669. host = 'github.com'
  670. path = '/jelmer/dulwich'
  671. c = TCPGitClient(host)
  672. url = c.get_url(path)
  673. self.assertEqual('git://github.com/jelmer/dulwich', url)
  674. def test_get_url_with_port(self):
  675. host = 'github.com'
  676. path = '/jelmer/dulwich'
  677. port = 9090
  678. c = TCPGitClient(host, port=9090)
  679. url = c.get_url(path)
  680. self.assertEqual('git://github.com:9090/jelmer/dulwich', url)