test_client.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350
  1. # test_client.py -- Tests for the git protocol, client side
  2. # Copyright (C) 2009 Jelmer Vernooij <jelmer@jelmer.uk>
  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 base64
  22. import os
  23. import sys
  24. import shutil
  25. import tempfile
  26. import warnings
  27. from urllib.parse import (
  28. quote as urlquote,
  29. urlparse,
  30. )
  31. import dulwich
  32. from dulwich import (
  33. client,
  34. )
  35. from dulwich.client import (
  36. InvalidWants,
  37. LocalGitClient,
  38. TraditionalGitClient,
  39. TCPGitClient,
  40. SSHGitClient,
  41. HttpGitClient,
  42. FetchPackResult,
  43. ReportStatusParser,
  44. SendPackError,
  45. StrangeHostname,
  46. SubprocessSSHVendor,
  47. PLinkSSHVendor,
  48. UpdateRefsError,
  49. check_wants,
  50. default_urllib3_manager,
  51. get_credentials_from_store,
  52. get_transport_and_path,
  53. get_transport_and_path_from_url,
  54. parse_rsync_url,
  55. )
  56. from dulwich.config import (
  57. ConfigDict,
  58. )
  59. from dulwich.tests import (
  60. TestCase,
  61. )
  62. from dulwich.protocol import (
  63. TCP_GIT_PORT,
  64. Protocol,
  65. )
  66. from dulwich.pack import (
  67. pack_objects_to_data,
  68. write_pack_data,
  69. write_pack_objects,
  70. )
  71. from dulwich.objects import (
  72. Commit,
  73. Tree
  74. )
  75. from dulwich.repo import (
  76. MemoryRepo,
  77. Repo,
  78. )
  79. from dulwich.tests import skipIf
  80. from dulwich.tests.utils import (
  81. open_repo,
  82. tear_down_repo,
  83. setup_warning_catcher,
  84. )
  85. class DummyClient(TraditionalGitClient):
  86. def __init__(self, can_read, read, write):
  87. self.can_read = can_read
  88. self.read = read
  89. self.write = write
  90. TraditionalGitClient.__init__(self)
  91. def _connect(self, service, path):
  92. return Protocol(self.read, self.write), self.can_read, None
  93. class DummyPopen():
  94. def __init__(self, *args, **kwards):
  95. self.stdin = BytesIO(b"stdin")
  96. self.stdout = BytesIO(b"stdout")
  97. self.stderr = BytesIO(b"stderr")
  98. self.returncode = 0
  99. self.args = args
  100. self.kwargs = kwards
  101. def communicate(self, *args, **kwards):
  102. return ('Running', '')
  103. def wait(self, *args, **kwards):
  104. return False
  105. # TODO(durin42): add unit-level tests of GitClient
  106. class GitClientTests(TestCase):
  107. def setUp(self):
  108. super(GitClientTests, self).setUp()
  109. self.rout = BytesIO()
  110. self.rin = BytesIO()
  111. self.client = DummyClient(lambda x: True, self.rin.read,
  112. self.rout.write)
  113. def test_caps(self):
  114. agent_cap = (
  115. 'agent=dulwich/%d.%d.%d' % dulwich.__version__).encode('ascii')
  116. self.assertEqual(set([b'multi_ack', b'side-band-64k', b'ofs-delta',
  117. b'thin-pack', b'multi_ack_detailed', b'shallow',
  118. agent_cap]),
  119. set(self.client._fetch_capabilities))
  120. self.assertEqual(
  121. set([b'delete-refs', b'ofs-delta', b'report-status',
  122. b'side-band-64k', agent_cap]),
  123. set(self.client._send_capabilities))
  124. def test_archive_ack(self):
  125. self.rin.write(
  126. b'0009NACK\n'
  127. b'0000')
  128. self.rin.seek(0)
  129. self.client.archive(b'bla', b'HEAD', None, None)
  130. self.assertEqual(self.rout.getvalue(), b'0011argument HEAD0000')
  131. def test_fetch_empty(self):
  132. self.rin.write(b'0000')
  133. self.rin.seek(0)
  134. def check_heads(heads):
  135. self.assertEqual(heads, {})
  136. return []
  137. ret = self.client.fetch_pack(b'/', check_heads, None, None)
  138. self.assertEqual({}, ret.refs)
  139. self.assertEqual({}, ret.symrefs)
  140. def test_fetch_pack_ignores_magic_ref(self):
  141. self.rin.write(
  142. b'00000000000000000000000000000000000000000000 capabilities^{}'
  143. b'\x00 multi_ack '
  144. b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
  145. b'include-tag\n'
  146. b'0000')
  147. self.rin.seek(0)
  148. def check_heads(heads):
  149. self.assertEqual({}, heads)
  150. return []
  151. ret = self.client.fetch_pack(b'bla', check_heads, None, None, None)
  152. self.assertEqual({}, ret.refs)
  153. self.assertEqual({}, ret.symrefs)
  154. self.assertEqual(self.rout.getvalue(), b'0000')
  155. def test_fetch_pack_none(self):
  156. self.rin.write(
  157. b'008855dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 HEAD\x00multi_ack '
  158. b'thin-pack side-band side-band-64k ofs-delta shallow no-progress '
  159. b'include-tag\n'
  160. b'0000')
  161. self.rin.seek(0)
  162. ret = self.client.fetch_pack(
  163. b'bla', lambda heads: [], None, None, None)
  164. self.assertEqual(
  165. {b'HEAD': b'55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7'},
  166. ret.refs)
  167. self.assertEqual({}, ret.symrefs)
  168. self.assertEqual(self.rout.getvalue(), b'0000')
  169. def test_send_pack_no_sideband64k_with_update_ref_error(self):
  170. # No side-bank-64k reported by server shouldn't try to parse
  171. # side band data
  172. pkts = [b'55dcc6bf963f922e1ed5c4bbaaefcfacef57b1d7 capabilities^{}'
  173. b'\x00 report-status delete-refs ofs-delta\n',
  174. b'',
  175. b"unpack ok",
  176. b"ng refs/foo/bar pre-receive hook declined",
  177. b'']
  178. for pkt in pkts:
  179. if pkt == b'':
  180. self.rin.write(b"0000")
  181. else:
  182. self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
  183. self.rin.seek(0)
  184. tree = Tree()
  185. commit = Commit()
  186. commit.tree = tree
  187. commit.parents = []
  188. commit.author = commit.committer = b'test user'
  189. commit.commit_time = commit.author_time = 1174773719
  190. commit.commit_timezone = commit.author_timezone = 0
  191. commit.encoding = b'UTF-8'
  192. commit.message = b'test message'
  193. def update_refs(refs):
  194. return {b'refs/foo/bar': commit.id, }
  195. def generate_pack_data(have, want, ofs_delta=False):
  196. return pack_objects_to_data([(commit, None), (tree, ''), ])
  197. self.assertRaises(UpdateRefsError,
  198. self.client.send_pack, "blah",
  199. update_refs, generate_pack_data)
  200. def test_send_pack_none(self):
  201. # Set ref to current value
  202. self.rin.write(
  203. b'0078310ca9477129b8586fa2afc779c1f57cf64bba6c '
  204. b'refs/heads/master\x00 report-status delete-refs '
  205. b'side-band-64k quiet ofs-delta\n'
  206. b'0000')
  207. self.rin.seek(0)
  208. def update_refs(refs):
  209. return {
  210. b'refs/heads/master':
  211. b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
  212. }
  213. def generate_pack_data(have, want, ofs_delta=False):
  214. return 0, []
  215. self.client.send_pack(b'/', update_refs, generate_pack_data)
  216. self.assertEqual(self.rout.getvalue(), b'0000')
  217. def test_send_pack_keep_and_delete(self):
  218. self.rin.write(
  219. b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
  220. b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
  221. b'003f310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/keepme\n'
  222. b'0000000eunpack ok\n'
  223. b'0019ok refs/heads/master\n'
  224. b'0000')
  225. self.rin.seek(0)
  226. def update_refs(refs):
  227. return {b'refs/heads/master': b'0' * 40}
  228. def generate_pack_data(have, want, ofs_delta=False):
  229. return 0, []
  230. self.client.send_pack(b'/', update_refs, generate_pack_data)
  231. self.assertEqual(
  232. self.rout.getvalue(),
  233. b'008b310ca9477129b8586fa2afc779c1f57cf64bba6c '
  234. b'0000000000000000000000000000000000000000 '
  235. b'refs/heads/master\x00delete-refs ofs-delta report-status0000')
  236. def test_send_pack_delete_only(self):
  237. self.rin.write(
  238. b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
  239. b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
  240. b'0000000eunpack ok\n'
  241. b'0019ok refs/heads/master\n'
  242. b'0000')
  243. self.rin.seek(0)
  244. def update_refs(refs):
  245. return {b'refs/heads/master': b'0' * 40}
  246. def generate_pack_data(have, want, ofs_delta=False):
  247. return 0, []
  248. self.client.send_pack(b'/', update_refs, generate_pack_data)
  249. self.assertEqual(
  250. self.rout.getvalue(),
  251. b'008b310ca9477129b8586fa2afc779c1f57cf64bba6c '
  252. b'0000000000000000000000000000000000000000 '
  253. b'refs/heads/master\x00delete-refs ofs-delta report-status0000')
  254. def test_send_pack_new_ref_only(self):
  255. self.rin.write(
  256. b'0063310ca9477129b8586fa2afc779c1f57cf64bba6c '
  257. b'refs/heads/master\x00report-status delete-refs ofs-delta\n'
  258. b'0000000eunpack ok\n'
  259. b'0019ok refs/heads/blah12\n'
  260. b'0000')
  261. self.rin.seek(0)
  262. def update_refs(refs):
  263. return {
  264. b'refs/heads/blah12':
  265. b'310ca9477129b8586fa2afc779c1f57cf64bba6c',
  266. b'refs/heads/master':
  267. b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
  268. }
  269. def generate_pack_data(have, want, ofs_delta=False):
  270. return 0, []
  271. f = BytesIO()
  272. write_pack_objects(f, {})
  273. self.client.send_pack('/', update_refs, generate_pack_data)
  274. self.assertEqual(
  275. self.rout.getvalue(),
  276. b'008b0000000000000000000000000000000000000000 '
  277. b'310ca9477129b8586fa2afc779c1f57cf64bba6c '
  278. b'refs/heads/blah12\x00delete-refs ofs-delta report-status0000' +
  279. f.getvalue())
  280. def test_send_pack_new_ref(self):
  281. self.rin.write(
  282. b'0064310ca9477129b8586fa2afc779c1f57cf64bba6c '
  283. b'refs/heads/master\x00 report-status delete-refs ofs-delta\n'
  284. b'0000000eunpack ok\n'
  285. b'0019ok refs/heads/blah12\n'
  286. b'0000')
  287. self.rin.seek(0)
  288. tree = Tree()
  289. commit = Commit()
  290. commit.tree = tree
  291. commit.parents = []
  292. commit.author = commit.committer = b'test user'
  293. commit.commit_time = commit.author_time = 1174773719
  294. commit.commit_timezone = commit.author_timezone = 0
  295. commit.encoding = b'UTF-8'
  296. commit.message = b'test message'
  297. def update_refs(refs):
  298. return {
  299. b'refs/heads/blah12': commit.id,
  300. b'refs/heads/master':
  301. b'310ca9477129b8586fa2afc779c1f57cf64bba6c'
  302. }
  303. def generate_pack_data(have, want, ofs_delta=False):
  304. return pack_objects_to_data([(commit, None), (tree, b''), ])
  305. f = BytesIO()
  306. write_pack_data(f, *generate_pack_data(None, None))
  307. self.client.send_pack(b'/', update_refs, generate_pack_data)
  308. self.assertEqual(
  309. self.rout.getvalue(),
  310. b'008b0000000000000000000000000000000000000000 ' + commit.id +
  311. b' refs/heads/blah12\x00delete-refs ofs-delta report-status0000' +
  312. f.getvalue())
  313. def test_send_pack_no_deleteref_delete_only(self):
  314. pkts = [b'310ca9477129b8586fa2afc779c1f57cf64bba6c refs/heads/master'
  315. b'\x00 report-status ofs-delta\n',
  316. b'',
  317. b'']
  318. for pkt in pkts:
  319. if pkt == b'':
  320. self.rin.write(b"0000")
  321. else:
  322. self.rin.write(("%04x" % (len(pkt)+4)).encode('ascii') + pkt)
  323. self.rin.seek(0)
  324. def update_refs(refs):
  325. return {b'refs/heads/master': b'0' * 40}
  326. def generate_pack_data(have, want, ofs_delta=False):
  327. return 0, []
  328. self.assertRaises(UpdateRefsError,
  329. self.client.send_pack, b"/",
  330. update_refs, generate_pack_data)
  331. self.assertEqual(self.rout.getvalue(), b'0000')
  332. class TestGetTransportAndPath(TestCase):
  333. def test_tcp(self):
  334. c, path = get_transport_and_path('git://foo.com/bar/baz')
  335. self.assertTrue(isinstance(c, TCPGitClient))
  336. self.assertEqual('foo.com', c._host)
  337. self.assertEqual(TCP_GIT_PORT, c._port)
  338. self.assertEqual('/bar/baz', path)
  339. def test_tcp_port(self):
  340. c, path = get_transport_and_path('git://foo.com:1234/bar/baz')
  341. self.assertTrue(isinstance(c, TCPGitClient))
  342. self.assertEqual('foo.com', c._host)
  343. self.assertEqual(1234, c._port)
  344. self.assertEqual('/bar/baz', path)
  345. def test_git_ssh_explicit(self):
  346. c, path = get_transport_and_path('git+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_explicit(self):
  353. c, path = get_transport_and_path('ssh://foo.com/bar/baz')
  354. self.assertTrue(isinstance(c, SSHGitClient))
  355. self.assertEqual('foo.com', c.host)
  356. self.assertEqual(None, c.port)
  357. self.assertEqual(None, c.username)
  358. self.assertEqual('/bar/baz', path)
  359. def test_ssh_port_explicit(self):
  360. c, path = get_transport_and_path(
  361. 'git+ssh://foo.com:1234/bar/baz')
  362. self.assertTrue(isinstance(c, SSHGitClient))
  363. self.assertEqual('foo.com', c.host)
  364. self.assertEqual(1234, c.port)
  365. self.assertEqual('/bar/baz', path)
  366. def test_username_and_port_explicit_unknown_scheme(self):
  367. c, path = get_transport_and_path(
  368. 'unknown://git@server:7999/dply/stuff.git')
  369. self.assertTrue(isinstance(c, SSHGitClient))
  370. self.assertEqual('unknown', c.host)
  371. self.assertEqual('//git@server:7999/dply/stuff.git', path)
  372. def test_username_and_port_explicit(self):
  373. c, path = get_transport_and_path(
  374. 'ssh://git@server:7999/dply/stuff.git')
  375. self.assertTrue(isinstance(c, SSHGitClient))
  376. self.assertEqual('git', c.username)
  377. self.assertEqual('server', c.host)
  378. self.assertEqual(7999, c.port)
  379. self.assertEqual('/dply/stuff.git', path)
  380. def test_ssh_abspath_doubleslash(self):
  381. c, path = get_transport_and_path('git+ssh://foo.com//bar/baz')
  382. self.assertTrue(isinstance(c, SSHGitClient))
  383. self.assertEqual('foo.com', c.host)
  384. self.assertEqual(None, c.port)
  385. self.assertEqual(None, c.username)
  386. self.assertEqual('//bar/baz', path)
  387. def test_ssh_port(self):
  388. c, path = get_transport_and_path(
  389. 'git+ssh://foo.com:1234/bar/baz')
  390. self.assertTrue(isinstance(c, SSHGitClient))
  391. self.assertEqual('foo.com', c.host)
  392. self.assertEqual(1234, c.port)
  393. self.assertEqual('/bar/baz', path)
  394. def test_ssh_implicit(self):
  395. c, path = get_transport_and_path('foo:/bar/baz')
  396. self.assertTrue(isinstance(c, SSHGitClient))
  397. self.assertEqual('foo', c.host)
  398. self.assertEqual(None, c.port)
  399. self.assertEqual(None, c.username)
  400. self.assertEqual('/bar/baz', path)
  401. def test_ssh_host(self):
  402. c, path = get_transport_and_path('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(None, c.username)
  407. self.assertEqual('/bar/baz', path)
  408. def test_ssh_user_host(self):
  409. c, path = get_transport_and_path('user@foo.com:/bar/baz')
  410. self.assertTrue(isinstance(c, SSHGitClient))
  411. self.assertEqual('foo.com', c.host)
  412. self.assertEqual(None, c.port)
  413. self.assertEqual('user', c.username)
  414. self.assertEqual('/bar/baz', path)
  415. def test_ssh_relpath(self):
  416. c, path = get_transport_and_path('foo:bar/baz')
  417. self.assertTrue(isinstance(c, SSHGitClient))
  418. self.assertEqual('foo', c.host)
  419. self.assertEqual(None, c.port)
  420. self.assertEqual(None, c.username)
  421. self.assertEqual('bar/baz', path)
  422. def test_ssh_host_relpath(self):
  423. c, path = get_transport_and_path('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(None, c.username)
  428. self.assertEqual('bar/baz', path)
  429. def test_ssh_user_host_relpath(self):
  430. c, path = get_transport_and_path('user@foo.com:bar/baz')
  431. self.assertTrue(isinstance(c, SSHGitClient))
  432. self.assertEqual('foo.com', c.host)
  433. self.assertEqual(None, c.port)
  434. self.assertEqual('user', c.username)
  435. self.assertEqual('bar/baz', path)
  436. def test_local(self):
  437. c, path = get_transport_and_path('foo.bar/baz')
  438. self.assertTrue(isinstance(c, LocalGitClient))
  439. self.assertEqual('foo.bar/baz', path)
  440. @skipIf(sys.platform != 'win32', 'Behaviour only happens on windows.')
  441. def test_local_abs_windows_path(self):
  442. c, path = get_transport_and_path('C:\\foo.bar\\baz')
  443. self.assertTrue(isinstance(c, LocalGitClient))
  444. self.assertEqual('C:\\foo.bar\\baz', path)
  445. def test_error(self):
  446. # Need to use a known urlparse.uses_netloc URL scheme to get the
  447. # expected parsing of the URL on Python versions less than 2.6.5
  448. c, path = get_transport_and_path('prospero://bar/baz')
  449. self.assertTrue(isinstance(c, SSHGitClient))
  450. def test_http(self):
  451. url = 'https://github.com/jelmer/dulwich'
  452. c, path = get_transport_and_path(url)
  453. self.assertTrue(isinstance(c, HttpGitClient))
  454. self.assertEqual('/jelmer/dulwich', path)
  455. def test_http_auth(self):
  456. url = 'https://user:passwd@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.assertEqual('user', c._username)
  461. self.assertEqual('passwd', c._password)
  462. def test_http_auth_with_username(self):
  463. url = 'https://github.com/jelmer/dulwich'
  464. c, path = get_transport_and_path(
  465. url, username='user2', password='blah')
  466. self.assertTrue(isinstance(c, HttpGitClient))
  467. self.assertEqual('/jelmer/dulwich', path)
  468. self.assertEqual('user2', c._username)
  469. self.assertEqual('blah', c._password)
  470. def test_http_auth_with_username_and_in_url(self):
  471. url = 'https://user:passwd@github.com/jelmer/dulwich'
  472. c, path = get_transport_and_path(
  473. url, username='user2', password='blah')
  474. self.assertTrue(isinstance(c, HttpGitClient))
  475. self.assertEqual('/jelmer/dulwich', path)
  476. self.assertEqual('user', c._username)
  477. self.assertEqual('passwd', c._password)
  478. def test_http_no_auth(self):
  479. url = 'https://github.com/jelmer/dulwich'
  480. c, path = get_transport_and_path(url)
  481. self.assertTrue(isinstance(c, HttpGitClient))
  482. self.assertEqual('/jelmer/dulwich', path)
  483. self.assertIs(None, c._username)
  484. self.assertIs(None, c._password)
  485. class TestGetTransportAndPathFromUrl(TestCase):
  486. def test_tcp(self):
  487. c, path = get_transport_and_path_from_url('git://foo.com/bar/baz')
  488. self.assertTrue(isinstance(c, TCPGitClient))
  489. self.assertEqual('foo.com', c._host)
  490. self.assertEqual(TCP_GIT_PORT, c._port)
  491. self.assertEqual('/bar/baz', path)
  492. def test_tcp_port(self):
  493. c, path = get_transport_and_path_from_url('git://foo.com:1234/bar/baz')
  494. self.assertTrue(isinstance(c, TCPGitClient))
  495. self.assertEqual('foo.com', c._host)
  496. self.assertEqual(1234, c._port)
  497. self.assertEqual('/bar/baz', path)
  498. def test_ssh_explicit(self):
  499. c, path = get_transport_and_path_from_url('git+ssh://foo.com/bar/baz')
  500. self.assertTrue(isinstance(c, SSHGitClient))
  501. self.assertEqual('foo.com', c.host)
  502. self.assertEqual(None, c.port)
  503. self.assertEqual(None, c.username)
  504. self.assertEqual('/bar/baz', path)
  505. def test_ssh_port_explicit(self):
  506. c, path = get_transport_and_path_from_url(
  507. 'git+ssh://foo.com:1234/bar/baz')
  508. self.assertTrue(isinstance(c, SSHGitClient))
  509. self.assertEqual('foo.com', c.host)
  510. self.assertEqual(1234, c.port)
  511. self.assertEqual('/bar/baz', path)
  512. def test_ssh_homepath(self):
  513. c, path = get_transport_and_path_from_url(
  514. 'git+ssh://foo.com/~/bar/baz')
  515. self.assertTrue(isinstance(c, SSHGitClient))
  516. self.assertEqual('foo.com', c.host)
  517. self.assertEqual(None, c.port)
  518. self.assertEqual(None, c.username)
  519. self.assertEqual('/~/bar/baz', path)
  520. def test_ssh_port_homepath(self):
  521. c, path = get_transport_and_path_from_url(
  522. 'git+ssh://foo.com:1234/~/bar/baz')
  523. self.assertTrue(isinstance(c, SSHGitClient))
  524. self.assertEqual('foo.com', c.host)
  525. self.assertEqual(1234, c.port)
  526. self.assertEqual('/~/bar/baz', path)
  527. def test_ssh_host_relpath(self):
  528. self.assertRaises(
  529. ValueError, get_transport_and_path_from_url,
  530. 'foo.com:bar/baz')
  531. def test_ssh_user_host_relpath(self):
  532. self.assertRaises(
  533. ValueError, get_transport_and_path_from_url,
  534. 'user@foo.com:bar/baz')
  535. def test_local_path(self):
  536. self.assertRaises(
  537. ValueError, get_transport_and_path_from_url,
  538. 'foo.bar/baz')
  539. def test_error(self):
  540. # Need to use a known urlparse.uses_netloc URL scheme to get the
  541. # expected parsing of the URL on Python versions less than 2.6.5
  542. self.assertRaises(
  543. ValueError, get_transport_and_path_from_url,
  544. 'prospero://bar/baz')
  545. def test_http(self):
  546. url = 'https://github.com/jelmer/dulwich'
  547. c, path = get_transport_and_path_from_url(url)
  548. self.assertTrue(isinstance(c, HttpGitClient))
  549. self.assertEqual('https://github.com', c.get_url(b'/'))
  550. self.assertEqual('/jelmer/dulwich', path)
  551. def test_http_port(self):
  552. url = 'https://github.com:9090/jelmer/dulwich'
  553. c, path = get_transport_and_path_from_url(url)
  554. self.assertEqual('https://github.com:9090', c.get_url(b'/'))
  555. self.assertTrue(isinstance(c, HttpGitClient))
  556. self.assertEqual('/jelmer/dulwich', path)
  557. def test_file(self):
  558. c, path = get_transport_and_path_from_url('file:///home/jelmer/foo')
  559. self.assertTrue(isinstance(c, LocalGitClient))
  560. self.assertEqual('/home/jelmer/foo', path)
  561. class TestSSHVendor(object):
  562. def __init__(self):
  563. self.host = None
  564. self.command = ""
  565. self.username = None
  566. self.port = None
  567. self.password = None
  568. self.key_filename = None
  569. def run_command(self, host, command, username=None, port=None,
  570. password=None, key_filename=None):
  571. self.host = host
  572. self.command = command
  573. self.username = username
  574. self.port = port
  575. self.password = password
  576. self.key_filename = key_filename
  577. class Subprocess:
  578. pass
  579. setattr(Subprocess, 'read', lambda: None)
  580. setattr(Subprocess, 'write', lambda: None)
  581. setattr(Subprocess, 'close', lambda: None)
  582. setattr(Subprocess, 'can_read', lambda: None)
  583. return Subprocess()
  584. class SSHGitClientTests(TestCase):
  585. def setUp(self):
  586. super(SSHGitClientTests, self).setUp()
  587. self.server = TestSSHVendor()
  588. self.real_vendor = client.get_ssh_vendor
  589. client.get_ssh_vendor = lambda: self.server
  590. self.client = SSHGitClient('git.samba.org')
  591. def tearDown(self):
  592. super(SSHGitClientTests, self).tearDown()
  593. client.get_ssh_vendor = self.real_vendor
  594. def test_get_url(self):
  595. path = '/tmp/repo.git'
  596. c = SSHGitClient('git.samba.org')
  597. url = c.get_url(path)
  598. self.assertEqual('ssh://git.samba.org/tmp/repo.git', url)
  599. def test_get_url_with_username_and_port(self):
  600. path = '/tmp/repo.git'
  601. c = SSHGitClient('git.samba.org', port=2222, username='user')
  602. url = c.get_url(path)
  603. self.assertEqual('ssh://user@git.samba.org:2222/tmp/repo.git', url)
  604. def test_default_command(self):
  605. self.assertEqual(
  606. b'git-upload-pack',
  607. self.client._get_cmd_path(b'upload-pack'))
  608. def test_alternative_command_path(self):
  609. self.client.alternative_paths[b'upload-pack'] = (
  610. b'/usr/lib/git/git-upload-pack')
  611. self.assertEqual(
  612. b'/usr/lib/git/git-upload-pack',
  613. self.client._get_cmd_path(b'upload-pack'))
  614. def test_alternative_command_path_spaces(self):
  615. self.client.alternative_paths[b'upload-pack'] = (
  616. b'/usr/lib/git/git-upload-pack -ibla')
  617. self.assertEqual(b"/usr/lib/git/git-upload-pack -ibla",
  618. self.client._get_cmd_path(b'upload-pack'))
  619. def test_connect(self):
  620. server = self.server
  621. client = self.client
  622. client.username = b"username"
  623. client.port = 1337
  624. client._connect(b"command", b"/path/to/repo")
  625. self.assertEqual(b"username", server.username)
  626. self.assertEqual(1337, server.port)
  627. self.assertEqual("git-command '/path/to/repo'", server.command)
  628. client._connect(b"relative-command", b"/~/path/to/repo")
  629. self.assertEqual("git-relative-command '~/path/to/repo'",
  630. server.command)
  631. class ReportStatusParserTests(TestCase):
  632. def test_invalid_pack(self):
  633. parser = ReportStatusParser()
  634. parser.handle_packet(b"unpack error - foo bar")
  635. parser.handle_packet(b"ok refs/foo/bar")
  636. parser.handle_packet(None)
  637. self.assertRaises(SendPackError, parser.check)
  638. def test_update_refs_error(self):
  639. parser = ReportStatusParser()
  640. parser.handle_packet(b"unpack ok")
  641. parser.handle_packet(b"ng refs/foo/bar need to pull")
  642. parser.handle_packet(None)
  643. self.assertRaises(UpdateRefsError, parser.check)
  644. def test_ok(self):
  645. parser = ReportStatusParser()
  646. parser.handle_packet(b"unpack ok")
  647. parser.handle_packet(b"ok refs/foo/bar")
  648. parser.handle_packet(None)
  649. parser.check()
  650. class LocalGitClientTests(TestCase):
  651. def test_get_url(self):
  652. path = "/tmp/repo.git"
  653. c = LocalGitClient()
  654. url = c.get_url(path)
  655. self.assertEqual('file:///tmp/repo.git', url)
  656. def test_fetch_into_empty(self):
  657. c = LocalGitClient()
  658. t = MemoryRepo()
  659. s = open_repo('a.git')
  660. self.addCleanup(tear_down_repo, s)
  661. self.assertEqual(s.get_refs(), c.fetch(s.path, t).refs)
  662. def test_fetch_empty(self):
  663. c = LocalGitClient()
  664. s = open_repo('a.git')
  665. self.addCleanup(tear_down_repo, s)
  666. out = BytesIO()
  667. walker = {}
  668. ret = c.fetch_pack(
  669. s.path, lambda heads: [], graph_walker=walker, pack_data=out.write)
  670. self.assertEqual({
  671. b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  672. b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  673. b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
  674. b'refs/tags/mytag-packed':
  675. b'b0931cadc54336e78a1d980420e3268903b57a50'
  676. }, ret.refs)
  677. self.assertEqual(
  678. {b'HEAD': b'refs/heads/master'},
  679. ret.symrefs)
  680. self.assertEqual(
  681. b"PACK\x00\x00\x00\x02\x00\x00\x00\x00\x02\x9d\x08"
  682. b"\x82;\xd8\xa8\xea\xb5\x10\xadj\xc7\\\x82<\xfd>\xd3\x1e",
  683. out.getvalue())
  684. def test_fetch_pack_none(self):
  685. c = LocalGitClient()
  686. s = open_repo('a.git')
  687. self.addCleanup(tear_down_repo, s)
  688. out = BytesIO()
  689. walker = MemoryRepo().get_graph_walker()
  690. ret = c.fetch_pack(
  691. s.path,
  692. lambda heads: [b"a90fa2d900a17e99b433217e988c4eb4a2e9a097"],
  693. graph_walker=walker, pack_data=out.write)
  694. self.assertEqual({b'HEAD': b'refs/heads/master'}, ret.symrefs)
  695. self.assertEqual({
  696. b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  697. b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  698. b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
  699. b'refs/tags/mytag-packed':
  700. b'b0931cadc54336e78a1d980420e3268903b57a50'
  701. }, ret.refs)
  702. # Hardcoding is not ideal, but we'll fix that some other day..
  703. self.assertTrue(out.getvalue().startswith(
  704. b'PACK\x00\x00\x00\x02\x00\x00\x00\x07'))
  705. def test_send_pack_without_changes(self):
  706. local = open_repo('a.git')
  707. self.addCleanup(tear_down_repo, local)
  708. target = open_repo('a.git')
  709. self.addCleanup(tear_down_repo, target)
  710. self.send_and_verify(b"master", local, target)
  711. def test_send_pack_with_changes(self):
  712. local = open_repo('a.git')
  713. self.addCleanup(tear_down_repo, local)
  714. target_path = tempfile.mkdtemp()
  715. self.addCleanup(shutil.rmtree, target_path)
  716. with Repo.init_bare(target_path) as target:
  717. self.send_and_verify(b"master", local, target)
  718. def test_get_refs(self):
  719. local = open_repo('refs.git')
  720. self.addCleanup(tear_down_repo, local)
  721. client = LocalGitClient()
  722. refs = client.get_refs(local.path)
  723. self.assertDictEqual(local.refs.as_dict(), refs)
  724. def send_and_verify(self, branch, local, target):
  725. """Send branch from local to remote repository and verify it worked."""
  726. client = LocalGitClient()
  727. ref_name = b"refs/heads/" + branch
  728. new_refs = client.send_pack(target.path,
  729. lambda _: {ref_name: local.refs[ref_name]},
  730. local.generate_pack_data)
  731. self.assertEqual(local.refs[ref_name], new_refs[ref_name])
  732. obj_local = local.get_object(new_refs[ref_name])
  733. obj_target = target.get_object(new_refs[ref_name])
  734. self.assertEqual(obj_local, obj_target)
  735. class HttpGitClientTests(TestCase):
  736. def test_get_url(self):
  737. base_url = 'https://github.com/jelmer/dulwich'
  738. path = '/jelmer/dulwich'
  739. c = HttpGitClient(base_url)
  740. url = c.get_url(path)
  741. self.assertEqual('https://github.com/jelmer/dulwich', url)
  742. def test_get_url_bytes_path(self):
  743. base_url = 'https://github.com/jelmer/dulwich'
  744. path_bytes = b'/jelmer/dulwich'
  745. c = HttpGitClient(base_url)
  746. url = c.get_url(path_bytes)
  747. self.assertEqual('https://github.com/jelmer/dulwich', url)
  748. def test_get_url_with_username_and_passwd(self):
  749. base_url = 'https://github.com/jelmer/dulwich'
  750. path = '/jelmer/dulwich'
  751. c = HttpGitClient(base_url, username='USERNAME', password='PASSWD')
  752. url = c.get_url(path)
  753. self.assertEqual('https://github.com/jelmer/dulwich', url)
  754. def test_init_username_passwd_set(self):
  755. url = 'https://github.com/jelmer/dulwich'
  756. c = HttpGitClient(url, config=None, username='user', password='passwd')
  757. self.assertEqual('user', c._username)
  758. self.assertEqual('passwd', c._password)
  759. basic_auth = c.pool_manager.headers['authorization']
  760. auth_string = '%s:%s' % ('user', 'passwd')
  761. b64_credentials = base64.b64encode(auth_string.encode('latin1'))
  762. expected_basic_auth = 'Basic %s' % b64_credentials.decode('latin1')
  763. self.assertEqual(basic_auth, expected_basic_auth)
  764. def test_init_no_username_passwd(self):
  765. url = 'https://github.com/jelmer/dulwich'
  766. c = HttpGitClient(url, config=None)
  767. self.assertIs(None, c._username)
  768. self.assertIs(None, c._password)
  769. self.assertNotIn('authorization', c.pool_manager.headers)
  770. def test_from_parsedurl_on_url_with_quoted_credentials(self):
  771. original_username = 'john|the|first'
  772. quoted_username = urlquote(original_username)
  773. original_password = 'Ya#1$2%3'
  774. quoted_password = urlquote(original_password)
  775. url = 'https://{username}:{password}@github.com/jelmer/dulwich'.format(
  776. username=quoted_username,
  777. password=quoted_password
  778. )
  779. c = HttpGitClient.from_parsedurl(urlparse(url))
  780. self.assertEqual(original_username, c._username)
  781. self.assertEqual(original_password, c._password)
  782. basic_auth = c.pool_manager.headers['authorization']
  783. auth_string = '%s:%s' % (original_username, original_password)
  784. b64_credentials = base64.b64encode(auth_string.encode('latin1'))
  785. expected_basic_auth = 'Basic %s' % b64_credentials.decode('latin1')
  786. self.assertEqual(basic_auth, expected_basic_auth)
  787. def test_url_redirect_location(self):
  788. from urllib3.response import HTTPResponse
  789. test_data = {
  790. 'https://gitlab.com/inkscape/inkscape/': {
  791. 'redirect_url': 'https://gitlab.com/inkscape/inkscape.git/',
  792. 'refs_data': (b'001e# service=git-upload-pack\n00000032'
  793. b'fb2bebf4919a011f0fd7cec085443d0031228e76 '
  794. b'HEAD\n0000')
  795. },
  796. 'https://github.com/jelmer/dulwich/': {
  797. 'redirect_url': 'https://github.com/jelmer/dulwich/',
  798. 'refs_data': (b'001e# service=git-upload-pack\n00000032'
  799. b'3ff25e09724aa4d86ea5bca7d5dd0399a3c8bfcf '
  800. b'HEAD\n0000')
  801. }
  802. }
  803. tail = 'info/refs?service=git-upload-pack'
  804. # we need to mock urllib3.PoolManager as this test will fail
  805. # otherwise without an active internet connection
  806. class PoolManagerMock():
  807. def __init__(self):
  808. self.headers = {}
  809. def request(self, method, url, fields=None, headers=None,
  810. redirect=True):
  811. base_url = url[:-len(tail)]
  812. redirect_base_url = test_data[base_url]['redirect_url']
  813. redirect_url = redirect_base_url + tail
  814. headers = {
  815. 'Content-Type':
  816. 'application/x-git-upload-pack-advertisement'
  817. }
  818. body = test_data[base_url]['refs_data']
  819. # urllib3 handles automatic redirection by default
  820. status = 200
  821. request_url = redirect_url
  822. # simulate urllib3 behavior when redirect parameter is False
  823. if redirect is False:
  824. request_url = url
  825. if redirect_base_url != base_url:
  826. body = ''
  827. headers['location'] = redirect_url
  828. status = 301
  829. return HTTPResponse(body=body,
  830. headers=headers,
  831. request_method=method,
  832. request_url=request_url,
  833. status=status)
  834. pool_manager = PoolManagerMock()
  835. for base_url in test_data.keys():
  836. # instantiate HttpGitClient with mocked pool manager
  837. c = HttpGitClient(base_url, pool_manager=pool_manager,
  838. config=None)
  839. # call method that detects url redirection
  840. _, _, processed_url = c._discover_references(b'git-upload-pack',
  841. base_url)
  842. # send the same request as the method above without redirection
  843. resp = c.pool_manager.request('GET', base_url + tail,
  844. redirect=False)
  845. # check expected behavior of urllib3
  846. redirect_location = resp.get_redirect_location()
  847. if resp.status == 200:
  848. self.assertFalse(redirect_location)
  849. if redirect_location:
  850. # check that url redirection has been correctly detected
  851. self.assertEqual(processed_url, redirect_location[:-len(tail)])
  852. else:
  853. # check also the no redirection case
  854. self.assertEqual(processed_url, base_url)
  855. class TCPGitClientTests(TestCase):
  856. def test_get_url(self):
  857. host = 'github.com'
  858. path = '/jelmer/dulwich'
  859. c = TCPGitClient(host)
  860. url = c.get_url(path)
  861. self.assertEqual('git://github.com/jelmer/dulwich', url)
  862. def test_get_url_with_port(self):
  863. host = 'github.com'
  864. path = '/jelmer/dulwich'
  865. port = 9090
  866. c = TCPGitClient(host, port=port)
  867. url = c.get_url(path)
  868. self.assertEqual('git://github.com:9090/jelmer/dulwich', url)
  869. class DefaultUrllib3ManagerTest(TestCase):
  870. def test_no_config(self):
  871. manager = default_urllib3_manager(config=None)
  872. self.assertEqual(manager.connection_pool_kw['cert_reqs'],
  873. 'CERT_REQUIRED')
  874. def test_config_no_proxy(self):
  875. import urllib3
  876. manager = default_urllib3_manager(config=ConfigDict())
  877. self.assertNotIsInstance(manager, urllib3.ProxyManager)
  878. self.assertIsInstance(manager, urllib3.PoolManager)
  879. def test_config_no_proxy_custom_cls(self):
  880. import urllib3
  881. class CustomPoolManager(urllib3.PoolManager):
  882. pass
  883. manager = default_urllib3_manager(config=ConfigDict(),
  884. pool_manager_cls=CustomPoolManager)
  885. self.assertIsInstance(manager, CustomPoolManager)
  886. def test_config_ssl(self):
  887. config = ConfigDict()
  888. config.set(b'http', b'sslVerify', b'true')
  889. manager = default_urllib3_manager(config=config)
  890. self.assertEqual(manager.connection_pool_kw['cert_reqs'],
  891. 'CERT_REQUIRED')
  892. def test_config_no_ssl(self):
  893. config = ConfigDict()
  894. config.set(b'http', b'sslVerify', b'false')
  895. manager = default_urllib3_manager(config=config)
  896. self.assertEqual(manager.connection_pool_kw['cert_reqs'],
  897. 'CERT_NONE')
  898. def test_config_proxy(self):
  899. import urllib3
  900. config = ConfigDict()
  901. config.set(b'http', b'proxy', b'http://localhost:3128/')
  902. manager = default_urllib3_manager(config=config)
  903. self.assertIsInstance(manager, urllib3.ProxyManager)
  904. self.assertTrue(hasattr(manager, 'proxy'))
  905. self.assertEqual(manager.proxy.scheme, 'http')
  906. self.assertEqual(manager.proxy.host, 'localhost')
  907. self.assertEqual(manager.proxy.port, 3128)
  908. def test_config_proxy_custom_cls(self):
  909. import urllib3
  910. class CustomProxyManager(urllib3.ProxyManager):
  911. pass
  912. config = ConfigDict()
  913. config.set(b'http', b'proxy', b'http://localhost:3128/')
  914. manager = default_urllib3_manager(config=config,
  915. proxy_manager_cls=CustomProxyManager)
  916. self.assertIsInstance(manager, CustomProxyManager)
  917. def test_config_no_verify_ssl(self):
  918. manager = default_urllib3_manager(config=None, cert_reqs="CERT_NONE")
  919. self.assertEqual(manager.connection_pool_kw['cert_reqs'], 'CERT_NONE')
  920. class SubprocessSSHVendorTests(TestCase):
  921. def setUp(self):
  922. # Monkey Patch client subprocess popen
  923. self._orig_popen = dulwich.client.subprocess.Popen
  924. dulwich.client.subprocess.Popen = DummyPopen
  925. def tearDown(self):
  926. dulwich.client.subprocess.Popen = self._orig_popen
  927. def test_run_command_dashes(self):
  928. vendor = SubprocessSSHVendor()
  929. self.assertRaises(StrangeHostname, vendor.run_command, '--weird-host',
  930. 'git-clone-url')
  931. def test_run_command_password(self):
  932. vendor = SubprocessSSHVendor()
  933. self.assertRaises(NotImplementedError, vendor.run_command, 'host',
  934. 'git-clone-url', password='12345')
  935. def test_run_command_password_and_privkey(self):
  936. vendor = SubprocessSSHVendor()
  937. self.assertRaises(NotImplementedError, vendor.run_command,
  938. 'host', 'git-clone-url',
  939. password='12345', key_filename='/tmp/id_rsa')
  940. def test_run_command_with_port_username_and_privkey(self):
  941. expected = ['ssh', '-x', '-p', '2200',
  942. '-i', '/tmp/id_rsa', 'user@host', 'git-clone-url']
  943. vendor = SubprocessSSHVendor()
  944. command = vendor.run_command(
  945. 'host', 'git-clone-url',
  946. username='user', port='2200',
  947. key_filename='/tmp/id_rsa')
  948. args = command.proc.args
  949. self.assertListEqual(expected, args[0])
  950. class PLinkSSHVendorTests(TestCase):
  951. def setUp(self):
  952. # Monkey Patch client subprocess popen
  953. self._orig_popen = dulwich.client.subprocess.Popen
  954. dulwich.client.subprocess.Popen = DummyPopen
  955. def tearDown(self):
  956. dulwich.client.subprocess.Popen = self._orig_popen
  957. def test_run_command_dashes(self):
  958. vendor = PLinkSSHVendor()
  959. self.assertRaises(StrangeHostname, vendor.run_command, '--weird-host',
  960. 'git-clone-url')
  961. def test_run_command_password_and_privkey(self):
  962. vendor = PLinkSSHVendor()
  963. warnings.simplefilter("always", UserWarning)
  964. self.addCleanup(warnings.resetwarnings)
  965. warnings_list, restore_warnings = setup_warning_catcher()
  966. self.addCleanup(restore_warnings)
  967. command = vendor.run_command(
  968. 'host', 'git-clone-url', password='12345',
  969. key_filename='/tmp/id_rsa')
  970. expected_warning = UserWarning(
  971. 'Invoking PLink with a password exposes the password in the '
  972. 'process list.')
  973. for w in warnings_list:
  974. if (type(w) == type(expected_warning) and
  975. w.args == expected_warning.args):
  976. break
  977. else:
  978. raise AssertionError(
  979. 'Expected warning %r not in %r' %
  980. (expected_warning, warnings_list))
  981. args = command.proc.args
  982. if sys.platform == 'win32':
  983. binary = ['plink.exe', '-ssh']
  984. else:
  985. binary = ['plink', '-ssh']
  986. expected = binary + [
  987. '-pw', '12345', '-i', '/tmp/id_rsa', 'host', 'git-clone-url']
  988. self.assertListEqual(expected, args[0])
  989. def test_run_command_password(self):
  990. if sys.platform == 'win32':
  991. binary = ['plink.exe', '-ssh']
  992. else:
  993. binary = ['plink', '-ssh']
  994. expected = binary + ['-pw', '12345', 'host', 'git-clone-url']
  995. vendor = PLinkSSHVendor()
  996. warnings.simplefilter("always", UserWarning)
  997. self.addCleanup(warnings.resetwarnings)
  998. warnings_list, restore_warnings = setup_warning_catcher()
  999. self.addCleanup(restore_warnings)
  1000. command = vendor.run_command('host', 'git-clone-url', password='12345')
  1001. expected_warning = UserWarning(
  1002. 'Invoking PLink with a password exposes the password in the '
  1003. 'process list.')
  1004. for w in warnings_list:
  1005. if (type(w) == type(expected_warning) and
  1006. w.args == expected_warning.args):
  1007. break
  1008. else:
  1009. raise AssertionError(
  1010. 'Expected warning %r not in %r' %
  1011. (expected_warning, warnings_list))
  1012. args = command.proc.args
  1013. self.assertListEqual(expected, args[0])
  1014. def test_run_command_with_port_username_and_privkey(self):
  1015. if sys.platform == 'win32':
  1016. binary = ['plink.exe', '-ssh']
  1017. else:
  1018. binary = ['plink', '-ssh']
  1019. expected = binary + [
  1020. '-P', '2200', '-i', '/tmp/id_rsa',
  1021. 'user@host', 'git-clone-url']
  1022. vendor = PLinkSSHVendor()
  1023. command = vendor.run_command(
  1024. 'host', 'git-clone-url',
  1025. username='user', port='2200',
  1026. key_filename='/tmp/id_rsa')
  1027. args = command.proc.args
  1028. self.assertListEqual(expected, args[0])
  1029. class RsyncUrlTests(TestCase):
  1030. def test_simple(self):
  1031. self.assertEqual(
  1032. parse_rsync_url('foo:bar/path'),
  1033. (None, 'foo', 'bar/path'))
  1034. self.assertEqual(
  1035. parse_rsync_url('user@foo:bar/path'),
  1036. ('user', 'foo', 'bar/path'))
  1037. def test_path(self):
  1038. self.assertRaises(ValueError, parse_rsync_url, '/path')
  1039. class CheckWantsTests(TestCase):
  1040. def test_fine(self):
  1041. check_wants(
  1042. [b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'],
  1043. {b'refs/heads/blah': b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'})
  1044. def test_missing(self):
  1045. self.assertRaises(
  1046. InvalidWants, check_wants,
  1047. [b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'],
  1048. {b'refs/heads/blah': b'3f3dc7a53fb752a6961d3a56683df46d4d3bf262'})
  1049. def test_annotated(self):
  1050. self.assertRaises(
  1051. InvalidWants, check_wants,
  1052. [b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'],
  1053. {b'refs/heads/blah': b'3f3dc7a53fb752a6961d3a56683df46d4d3bf262',
  1054. b'refs/heads/blah^{}':
  1055. b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'})
  1056. class FetchPackResultTests(TestCase):
  1057. def test_eq(self):
  1058. self.assertEqual(
  1059. FetchPackResult(
  1060. {b'refs/heads/master':
  1061. b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'}, {},
  1062. b'user/agent'),
  1063. FetchPackResult(
  1064. {b'refs/heads/master':
  1065. b'2f3dc7a53fb752a6961d3a56683df46d4d3bf262'}, {},
  1066. b'user/agent'))
  1067. class GitCredentialStoreTests(TestCase):
  1068. @classmethod
  1069. def setUpClass(cls):
  1070. with tempfile.NamedTemporaryFile(delete=False) as f:
  1071. f.write(b'https://user:pass@example.org')
  1072. cls.fname = f.name
  1073. @classmethod
  1074. def tearDownClass(cls):
  1075. os.unlink(cls.fname)
  1076. def test_nonmatching_scheme(self):
  1077. self.assertEqual(
  1078. get_credentials_from_store(
  1079. b'http', b'example.org', fnames=[self.fname]),
  1080. None)
  1081. def test_nonmatching_hostname(self):
  1082. self.assertEqual(
  1083. get_credentials_from_store(
  1084. b'https', b'noentry.org', fnames=[self.fname]),
  1085. None)
  1086. def test_match_without_username(self):
  1087. self.assertEqual(
  1088. get_credentials_from_store(
  1089. b'https', b'example.org', fnames=[self.fname]),
  1090. (b'user', b'pass'))
  1091. def test_match_with_matching_username(self):
  1092. self.assertEqual(
  1093. get_credentials_from_store(
  1094. b'https', b'example.org', b'user', fnames=[self.fname]),
  1095. (b'user', b'pass'))
  1096. def test_no_match_with_nonmatching_username(self):
  1097. self.assertEqual(
  1098. get_credentials_from_store(
  1099. b'https', b'example.org', b'otheruser', fnames=[self.fname]),
  1100. None)