test_client.py 52 KB

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