test_client.py 33 KB

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