test_client.py 42 KB

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