test_server.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215
  1. # test_server.py -- Tests for the git server
  2. # Copyright (C) 2010 Google, Inc.
  3. #
  4. # SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
  5. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  6. # General Public License as published by the Free Software Foundation; version 2.0
  7. # or (at your option) any later version. You can redistribute it and/or
  8. # modify it under the terms of either of these two licenses.
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. # You should have received a copy of the licenses; if not, see
  17. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  18. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  19. # License, Version 2.0.
  20. #
  21. """Tests for the smart protocol server."""
  22. import os
  23. import shutil
  24. import sys
  25. import tempfile
  26. from io import BytesIO
  27. from dulwich.errors import (
  28. GitProtocolError,
  29. HangupException,
  30. NotGitRepository,
  31. UnexpectedCommandError,
  32. )
  33. from dulwich.object_store import MemoryObjectStore, find_shallow
  34. from dulwich.objects import Tree
  35. from dulwich.protocol import ZERO_SHA, format_capability_line
  36. from dulwich.repo import MemoryRepo, Repo
  37. from dulwich.server import (
  38. Backend,
  39. DictBackend,
  40. FileSystemBackend,
  41. MultiAckDetailedGraphWalkerImpl,
  42. MultiAckGraphWalkerImpl,
  43. PackHandler,
  44. ReceivePackHandler,
  45. SingleAckGraphWalkerImpl,
  46. UploadPackHandler,
  47. _ProtocolGraphWalker,
  48. _split_proto_line,
  49. serve_command,
  50. update_server_info,
  51. )
  52. from dulwich.tests.utils import make_commit, make_tag
  53. from . import TestCase
  54. ONE = b"1" * 40
  55. TWO = b"2" * 40
  56. THREE = b"3" * 40
  57. FOUR = b"4" * 40
  58. FIVE = b"5" * 40
  59. SIX = b"6" * 40
  60. class TestProto:
  61. def __init__(self) -> None:
  62. self._output: list[bytes] = []
  63. self._received: dict[int, list[bytes]] = {0: [], 1: [], 2: [], 3: []}
  64. def set_output(self, output_lines) -> None:
  65. self._output = output_lines
  66. def read_pkt_line(self):
  67. if self._output:
  68. data = self._output.pop(0)
  69. if data is not None:
  70. return data.rstrip() + b"\n"
  71. else:
  72. # flush-pkt ('0000').
  73. return None
  74. else:
  75. raise HangupException
  76. def write_sideband(self, band, data) -> None:
  77. self._received[band].append(data)
  78. def write_pkt_line(self, data) -> None:
  79. self._received[0].append(data)
  80. def get_received_line(self, band=0):
  81. lines = self._received[band]
  82. return lines.pop(0)
  83. class TestGenericPackHandler(PackHandler):
  84. def __init__(self) -> None:
  85. PackHandler.__init__(self, Backend(), None)
  86. @classmethod
  87. def capabilities(cls):
  88. return [b"cap1", b"cap2", b"cap3"]
  89. @classmethod
  90. def required_capabilities(cls):
  91. return [b"cap2"]
  92. class HandlerTestCase(TestCase):
  93. def setUp(self) -> None:
  94. super().setUp()
  95. self._handler = TestGenericPackHandler()
  96. def assertSucceeds(self, func, *args, **kwargs) -> None:
  97. try:
  98. func(*args, **kwargs)
  99. except GitProtocolError as e:
  100. self.fail(e)
  101. def test_capability_line(self) -> None:
  102. self.assertEqual(
  103. b" cap1 cap2 cap3",
  104. format_capability_line([b"cap1", b"cap2", b"cap3"]),
  105. )
  106. def test_set_client_capabilities(self) -> None:
  107. set_caps = self._handler.set_client_capabilities
  108. self.assertSucceeds(set_caps, [b"cap2"])
  109. self.assertSucceeds(set_caps, [b"cap1", b"cap2"])
  110. # different order
  111. self.assertSucceeds(set_caps, [b"cap3", b"cap1", b"cap2"])
  112. # error cases
  113. self.assertRaises(GitProtocolError, set_caps, [b"capxxx", b"cap2"])
  114. self.assertRaises(GitProtocolError, set_caps, [b"cap1", b"cap3"])
  115. # ignore innocuous but unknown capabilities
  116. self.assertRaises(GitProtocolError, set_caps, [b"cap2", b"ignoreme"])
  117. self.assertNotIn(b"ignoreme", self._handler.capabilities())
  118. self._handler.innocuous_capabilities = lambda: (b"ignoreme",)
  119. self.assertSucceeds(set_caps, [b"cap2", b"ignoreme"])
  120. def test_has_capability(self) -> None:
  121. self.assertRaises(GitProtocolError, self._handler.has_capability, b"cap")
  122. caps = self._handler.capabilities()
  123. self._handler.set_client_capabilities(caps)
  124. for cap in caps:
  125. self.assertTrue(self._handler.has_capability(cap))
  126. self.assertFalse(self._handler.has_capability(b"capxxx"))
  127. class UploadPackHandlerTestCase(TestCase):
  128. def setUp(self) -> None:
  129. super().setUp()
  130. self.path = tempfile.mkdtemp()
  131. self.addCleanup(shutil.rmtree, self.path)
  132. self.repo = Repo.init(self.path)
  133. self._repo = Repo.init_bare(self.path)
  134. backend = DictBackend({b"/": self._repo})
  135. self._handler = UploadPackHandler(
  136. backend, [b"/", b"host=lolcathost"], TestProto()
  137. )
  138. def test_progress(self) -> None:
  139. caps = self._handler.required_capabilities()
  140. self._handler.set_client_capabilities(caps)
  141. self._handler._start_pack_send_phase()
  142. self._handler.progress(b"first message")
  143. self._handler.progress(b"second message")
  144. self.assertEqual(b"first message", self._handler.proto.get_received_line(2))
  145. self.assertEqual(b"second message", self._handler.proto.get_received_line(2))
  146. self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
  147. def test_no_progress(self) -> None:
  148. caps = [*list(self._handler.required_capabilities()), b"no-progress"]
  149. self._handler.set_client_capabilities(caps)
  150. self._handler.progress(b"first message")
  151. self._handler.progress(b"second message")
  152. self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
  153. def test_filter_capability_advertised(self) -> None:
  154. """Test that the filter capability is advertised by UploadPackHandler."""
  155. caps = self._handler.capabilities()
  156. self.assertIn(b"filter", caps)
  157. def test_filter_spec_parsed(self) -> None:
  158. """Test that filter specification is parsed from client capabilities."""
  159. from dulwich.object_filters import BlobNoneFilter
  160. caps = [b"filter=blob:none", *list(self._handler.required_capabilities())]
  161. self._handler.set_client_capabilities(caps)
  162. self.assertIsNotNone(self._handler.filter_spec)
  163. self.assertIsInstance(self._handler.filter_spec, BlobNoneFilter)
  164. def test_filter_spec_not_present(self) -> None:
  165. """Test that filter_spec is None when filter capability is not used."""
  166. caps = self._handler.required_capabilities()
  167. self._handler.set_client_capabilities(caps)
  168. self.assertIsNone(self._handler.filter_spec)
  169. def test_filter_spec_invalid(self) -> None:
  170. """Test that invalid filter spec raises GitProtocolError."""
  171. from dulwich.errors import GitProtocolError
  172. caps = [b"filter=invalid:spec", *list(self._handler.required_capabilities())]
  173. with self.assertRaises(GitProtocolError):
  174. self._handler.set_client_capabilities(caps)
  175. def test_get_tagged(self) -> None:
  176. refs = {
  177. b"refs/tags/tag1": ONE,
  178. b"refs/tags/tag2": TWO,
  179. b"refs/heads/master": FOUR, # not a tag, no peeled value
  180. }
  181. # repo needs to peel this object
  182. self._repo.object_store.add_object(make_commit(id=FOUR))
  183. for name, sha in refs.items():
  184. self._repo.refs[name] = sha
  185. peeled = {
  186. b"refs/tags/tag1": b"1234" * 10,
  187. b"refs/tags/tag2": b"5678" * 10,
  188. }
  189. self._repo.refs._peeled_refs = peeled
  190. self._repo.refs.add_packed_refs(refs)
  191. caps = [*list(self._handler.required_capabilities()), b"include-tag"]
  192. self._handler.set_client_capabilities(caps)
  193. self.assertEqual(
  194. {b"1234" * 10: ONE, b"5678" * 10: TWO},
  195. self._handler.get_tagged(refs, repo=self._repo),
  196. )
  197. # non-include-tag case
  198. caps = self._handler.required_capabilities()
  199. self._handler.set_client_capabilities(caps)
  200. self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
  201. def test_nothing_to_do_but_wants(self) -> None:
  202. # Just the fact that the client claims to want an object is enough
  203. # for sending a pack. Even if there turns out to be nothing.
  204. refs = {b"refs/tags/tag1": ONE}
  205. tree = Tree()
  206. self._repo.object_store.add_object(tree)
  207. self._repo.object_store.add_object(make_commit(id=ONE, tree=tree))
  208. for name, sha in refs.items():
  209. self._repo.refs[name] = sha
  210. self._handler.proto.set_output(
  211. [
  212. b"want " + ONE + b" side-band-64k thin-pack ofs-delta",
  213. None,
  214. b"have " + ONE,
  215. b"done",
  216. None,
  217. ]
  218. )
  219. self._handler.handle()
  220. # The server should always send a pack, even if it's empty.
  221. self.assertTrue(self._handler.proto.get_received_line(1).startswith(b"PACK"))
  222. def test_nothing_to_do_no_wants(self) -> None:
  223. # Don't send a pack if the client didn't ask for anything.
  224. refs = {b"refs/tags/tag1": ONE}
  225. tree = Tree()
  226. self._repo.object_store.add_object(tree)
  227. self._repo.object_store.add_object(make_commit(id=ONE, tree=tree))
  228. for ref, sha in refs.items():
  229. self._repo.refs[ref] = sha
  230. self._handler.proto.set_output([None])
  231. self._handler.handle()
  232. # The server should not send a pack, since the client didn't ask for
  233. # anything.
  234. self.assertEqual([], self._handler.proto._received[1])
  235. class FindShallowTests(TestCase):
  236. def setUp(self) -> None:
  237. super().setUp()
  238. self._store = MemoryObjectStore()
  239. def make_commit(self, **attrs):
  240. commit = make_commit(**attrs)
  241. self._store.add_object(commit)
  242. return commit
  243. def make_linear_commits(self, n, message=b""):
  244. commits = []
  245. parents = []
  246. for _ in range(n):
  247. commits.append(self.make_commit(parents=parents, message=message))
  248. parents = [commits[-1].id]
  249. return commits
  250. def assertSameElements(self, expected, actual) -> None:
  251. self.assertEqual(set(expected), set(actual))
  252. def test_linear(self) -> None:
  253. c1, c2, c3 = self.make_linear_commits(3)
  254. self.assertEqual(({c3.id}, set()), find_shallow(self._store, [c3.id], 1))
  255. self.assertEqual(
  256. ({c2.id}, {c3.id}),
  257. find_shallow(self._store, [c3.id], 2),
  258. )
  259. self.assertEqual(
  260. ({c1.id}, {c2.id, c3.id}),
  261. find_shallow(self._store, [c3.id], 3),
  262. )
  263. self.assertEqual(
  264. (set(), {c1.id, c2.id, c3.id}),
  265. find_shallow(self._store, [c3.id], 4),
  266. )
  267. def test_multiple_independent(self) -> None:
  268. a = self.make_linear_commits(2, message=b"a")
  269. b = self.make_linear_commits(2, message=b"b")
  270. c = self.make_linear_commits(2, message=b"c")
  271. heads = [a[1].id, b[1].id, c[1].id]
  272. self.assertEqual(
  273. ({a[0].id, b[0].id, c[0].id}, set(heads)),
  274. find_shallow(self._store, heads, 2),
  275. )
  276. def test_multiple_overlapping(self) -> None:
  277. # Create the following commit tree:
  278. # 1--2
  279. # \
  280. # 3--4
  281. c1, c2 = self.make_linear_commits(2)
  282. c3 = self.make_commit(parents=[c1.id])
  283. c4 = self.make_commit(parents=[c3.id])
  284. # 1 is shallow along the path from 4, but not along the path from 2.
  285. self.assertEqual(
  286. ({c1.id}, {c1.id, c2.id, c3.id, c4.id}),
  287. find_shallow(self._store, [c2.id, c4.id], 3),
  288. )
  289. def test_merge(self) -> None:
  290. c1 = self.make_commit()
  291. c2 = self.make_commit()
  292. c3 = self.make_commit(parents=[c1.id, c2.id])
  293. self.assertEqual(
  294. ({c1.id, c2.id}, {c3.id}),
  295. find_shallow(self._store, [c3.id], 2),
  296. )
  297. def test_tag(self) -> None:
  298. c1, c2 = self.make_linear_commits(2)
  299. tag = make_tag(c2, name=b"tag")
  300. self._store.add_object(tag)
  301. self.assertEqual(
  302. ({c1.id}, {c2.id}),
  303. find_shallow(self._store, [tag.id], 2),
  304. )
  305. class TestUploadPackHandler(UploadPackHandler):
  306. @classmethod
  307. def required_capabilities(self):
  308. return []
  309. class ReceivePackHandlerTestCase(TestCase):
  310. def setUp(self) -> None:
  311. super().setUp()
  312. self._repo = MemoryRepo.init_bare([], {})
  313. backend = DictBackend({b"/": self._repo})
  314. self._handler = ReceivePackHandler(
  315. backend, [b"/", b"host=lolcathost"], TestProto()
  316. )
  317. def test_apply_pack_del_ref(self) -> None:
  318. refs = {b"refs/heads/master": TWO, b"refs/heads/fake-branch": ONE}
  319. self._repo.refs._update(refs)
  320. update_refs = [
  321. [ONE, ZERO_SHA, b"refs/heads/fake-branch"],
  322. ]
  323. self._handler.set_client_capabilities([b"delete-refs"])
  324. status = list(self._handler._apply_pack(update_refs))
  325. self.assertEqual(status[0][0], b"unpack")
  326. self.assertEqual(status[0][1], b"ok")
  327. self.assertEqual(status[1][0], b"refs/heads/fake-branch")
  328. self.assertEqual(status[1][1], b"ok")
  329. class ProtocolGraphWalkerEmptyTestCase(TestCase):
  330. def setUp(self) -> None:
  331. super().setUp()
  332. self._repo = MemoryRepo.init_bare([], {})
  333. backend = DictBackend({b"/": self._repo})
  334. self._walker = _ProtocolGraphWalker(
  335. TestUploadPackHandler(backend, [b"/", b"host=lolcats"], TestProto()),
  336. self._repo.object_store,
  337. self._repo.get_peeled,
  338. self._repo.refs.get_symrefs,
  339. )
  340. def test_empty_repository(self) -> None:
  341. # The server should wait for a flush packet.
  342. self._walker.proto.set_output([])
  343. self.assertRaises(HangupException, self._walker.determine_wants, {})
  344. self.assertEqual(None, self._walker.proto.get_received_line())
  345. self._walker.proto.set_output([None])
  346. self.assertEqual([], self._walker.determine_wants({}))
  347. self.assertEqual(None, self._walker.proto.get_received_line())
  348. class ProtocolGraphWalkerTestCase(TestCase):
  349. def setUp(self) -> None:
  350. super().setUp()
  351. # Create the following commit tree:
  352. # 3---5
  353. # /
  354. # 1---2---4
  355. commits = [
  356. make_commit(id=ONE, parents=[], commit_time=111),
  357. make_commit(id=TWO, parents=[ONE], commit_time=222),
  358. make_commit(id=THREE, parents=[ONE], commit_time=333),
  359. make_commit(id=FOUR, parents=[TWO], commit_time=444),
  360. make_commit(id=FIVE, parents=[THREE], commit_time=555),
  361. ]
  362. self._repo = MemoryRepo.init_bare(commits, {})
  363. backend = DictBackend({b"/": self._repo})
  364. self._walker = _ProtocolGraphWalker(
  365. TestUploadPackHandler(backend, [b"/", b"host=lolcats"], TestProto()),
  366. self._repo.object_store,
  367. self._repo.get_peeled,
  368. self._repo.refs.get_symrefs,
  369. )
  370. def test_all_wants_satisfied_no_haves(self) -> None:
  371. self._walker.set_wants([ONE])
  372. self.assertFalse(self._walker.all_wants_satisfied([]))
  373. self._walker.set_wants([TWO])
  374. self.assertFalse(self._walker.all_wants_satisfied([]))
  375. self._walker.set_wants([THREE])
  376. self.assertFalse(self._walker.all_wants_satisfied([]))
  377. def test_all_wants_satisfied_have_root(self) -> None:
  378. self._walker.set_wants([ONE])
  379. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  380. self._walker.set_wants([TWO])
  381. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  382. self._walker.set_wants([THREE])
  383. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  384. def test_all_wants_satisfied_have_branch(self) -> None:
  385. self._walker.set_wants([TWO])
  386. self.assertTrue(self._walker.all_wants_satisfied([TWO]))
  387. # wrong branch
  388. self._walker.set_wants([THREE])
  389. self.assertFalse(self._walker.all_wants_satisfied([TWO]))
  390. def test_all_wants_satisfied(self) -> None:
  391. self._walker.set_wants([FOUR, FIVE])
  392. # trivial case: wants == haves
  393. self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
  394. # cases that require walking the commit tree
  395. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  396. self.assertFalse(self._walker.all_wants_satisfied([TWO]))
  397. self.assertFalse(self._walker.all_wants_satisfied([THREE]))
  398. self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
  399. def test_split_proto_line(self) -> None:
  400. allowed = (b"want", b"done", None)
  401. self.assertEqual(
  402. (b"want", ONE), _split_proto_line(b"want " + ONE + b"\n", allowed)
  403. )
  404. self.assertEqual(
  405. (b"want", TWO), _split_proto_line(b"want " + TWO + b"\n", allowed)
  406. )
  407. self.assertRaises(GitProtocolError, _split_proto_line, b"want xxxx\n", allowed)
  408. self.assertRaises(
  409. UnexpectedCommandError,
  410. _split_proto_line,
  411. b"have " + THREE + b"\n",
  412. allowed,
  413. )
  414. self.assertRaises(
  415. GitProtocolError,
  416. _split_proto_line,
  417. b"foo " + FOUR + b"\n",
  418. allowed,
  419. )
  420. self.assertRaises(GitProtocolError, _split_proto_line, b"bar", allowed)
  421. self.assertEqual((b"done", None), _split_proto_line(b"done\n", allowed))
  422. self.assertEqual((None, None), _split_proto_line(b"", allowed))
  423. def test_determine_wants(self) -> None:
  424. self._walker.proto.set_output([None])
  425. self.assertEqual([], self._walker.determine_wants({}))
  426. self.assertEqual(None, self._walker.proto.get_received_line())
  427. self._walker.proto.set_output(
  428. [
  429. b"want " + ONE + b" multi_ack",
  430. b"want " + TWO,
  431. None,
  432. ]
  433. )
  434. heads = {
  435. b"refs/heads/ref1": ONE,
  436. b"refs/heads/ref2": TWO,
  437. b"refs/heads/ref3": THREE,
  438. }
  439. self._repo.refs._update(heads)
  440. self.assertEqual([ONE, TWO], self._walker.determine_wants(heads))
  441. self._walker.advertise_refs = True
  442. self.assertEqual([], self._walker.determine_wants(heads))
  443. self._walker.advertise_refs = False
  444. self._walker.proto.set_output([b"want " + FOUR + b" multi_ack", None])
  445. self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
  446. self._walker.proto.set_output([None])
  447. self.assertEqual([], self._walker.determine_wants(heads))
  448. self._walker.proto.set_output([b"want " + ONE + b" multi_ack", b"foo", None])
  449. self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
  450. self._walker.proto.set_output([b"want " + FOUR + b" multi_ack", None])
  451. self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
  452. def test_determine_wants_advertisement(self) -> None:
  453. self._walker.proto.set_output([None])
  454. # advertise branch tips plus tag
  455. heads = {
  456. b"refs/heads/ref4": FOUR,
  457. b"refs/heads/ref5": FIVE,
  458. b"refs/heads/tag6": SIX,
  459. }
  460. self._repo.refs._update(heads)
  461. self._repo.refs._update_peeled(heads)
  462. self._repo.refs._update_peeled({b"refs/heads/tag6": FIVE})
  463. self._walker.determine_wants(heads)
  464. lines = []
  465. while True:
  466. line = self._walker.proto.get_received_line()
  467. if line is None:
  468. break
  469. # strip capabilities list if present
  470. if b"\x00" in line:
  471. line = line[: line.index(b"\x00")]
  472. lines.append(line.rstrip())
  473. self.assertEqual(
  474. [
  475. FOUR + b" refs/heads/ref4",
  476. FIVE + b" refs/heads/ref5",
  477. FIVE + b" refs/heads/tag6^{}",
  478. SIX + b" refs/heads/tag6",
  479. ],
  480. sorted(lines),
  481. )
  482. # ensure peeled tag was advertised immediately following tag
  483. for i, line in enumerate(lines):
  484. if line.endswith(b" refs/heads/tag6"):
  485. self.assertEqual(FIVE + b" refs/heads/tag6^{}", lines[i + 1])
  486. # TODO: test commit time cutoff
  487. def _handle_shallow_request(self, lines, heads) -> None:
  488. self._walker.proto.set_output([*lines, None])
  489. self._walker._handle_shallow_request(heads)
  490. def assertReceived(self, expected) -> None:
  491. self.assertEqual(
  492. expected, list(iter(self._walker.proto.get_received_line, None))
  493. )
  494. def test_handle_shallow_request_no_client_shallows(self) -> None:
  495. self._handle_shallow_request([b"deepen 2\n"], [FOUR, FIVE])
  496. self.assertEqual({TWO, THREE}, self._walker.shallow)
  497. self.assertReceived(
  498. [
  499. b"shallow " + TWO,
  500. b"shallow " + THREE,
  501. ]
  502. )
  503. def test_handle_shallow_request_no_new_shallows(self) -> None:
  504. lines = [
  505. b"shallow " + TWO + b"\n",
  506. b"shallow " + THREE + b"\n",
  507. b"deepen 2\n",
  508. ]
  509. self._handle_shallow_request(lines, [FOUR, FIVE])
  510. self.assertEqual({TWO, THREE}, self._walker.shallow)
  511. self.assertReceived([])
  512. def test_handle_shallow_request_unshallows(self) -> None:
  513. lines = [
  514. b"shallow " + TWO + b"\n",
  515. b"deepen 3\n",
  516. ]
  517. self._handle_shallow_request(lines, [FOUR, FIVE])
  518. self.assertEqual({ONE}, self._walker.shallow)
  519. self.assertReceived(
  520. [
  521. b"shallow " + ONE,
  522. b"unshallow " + TWO,
  523. # THREE is unshallow but was is not shallow in the client
  524. ]
  525. )
  526. class TestProtocolGraphWalker:
  527. def __init__(self) -> None:
  528. self.acks: list[bytes] = []
  529. self.lines: list[bytes] = []
  530. self.wants_satisified = False
  531. self.stateless_rpc = None
  532. self.advertise_refs = False
  533. self._impl = None
  534. self.done_required = True
  535. self.done_received = False
  536. self._empty = False
  537. self.pack_sent = False
  538. def read_proto_line(self, allowed):
  539. command, sha = self.lines.pop(0)
  540. if allowed is not None:
  541. assert command in allowed
  542. return command, sha
  543. def send_ack(self, sha, ack_type=b"") -> None:
  544. self.acks.append((sha, ack_type))
  545. def send_nak(self) -> None:
  546. self.acks.append((None, b"nak"))
  547. def all_wants_satisfied(self, haves):
  548. if haves:
  549. return self.wants_satisified
  550. def pop_ack(self):
  551. if not self.acks:
  552. return None
  553. return self.acks.pop(0)
  554. def handle_done(self):
  555. if not self._impl:
  556. return
  557. # Whether or not PACK is sent after is determined by this, so
  558. # record this value.
  559. self.pack_sent = self._impl.handle_done(self.done_required, self.done_received)
  560. return self.pack_sent
  561. def notify_done(self) -> None:
  562. self.done_received = True
  563. class AckGraphWalkerImplTestCase(TestCase):
  564. """Base setup and asserts for AckGraphWalker tests."""
  565. def setUp(self) -> None:
  566. super().setUp()
  567. self._walker = TestProtocolGraphWalker()
  568. self._walker.lines = [
  569. (b"have", TWO),
  570. (b"have", ONE),
  571. (b"have", THREE),
  572. (b"done", None),
  573. ]
  574. self._impl = self.impl_cls(self._walker)
  575. self._walker._impl = self._impl
  576. def assertNoAck(self) -> None:
  577. self.assertEqual(None, self._walker.pop_ack())
  578. def assertAcks(self, acks) -> None:
  579. for sha, ack_type in acks:
  580. self.assertEqual((sha, ack_type), self._walker.pop_ack())
  581. self.assertNoAck()
  582. def assertAck(self, sha, ack_type=b"") -> None:
  583. self.assertAcks([(sha, ack_type)])
  584. def assertNak(self) -> None:
  585. self.assertAck(None, b"nak")
  586. def assertNextEquals(self, sha) -> None:
  587. self.assertEqual(sha, next(self._impl))
  588. def assertNextEmpty(self) -> None:
  589. # This is necessary because of no-done - the assumption that it
  590. # it safe to immediately send out the final ACK is no longer
  591. # true but the test is still needed for it. TestProtocolWalker
  592. # does implement the handle_done which will determine whether
  593. # the final confirmation can be sent.
  594. self.assertRaises(IndexError, next, self._impl)
  595. self._walker.handle_done()
  596. class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
  597. impl_cls = SingleAckGraphWalkerImpl
  598. def test_single_ack(self) -> None:
  599. self.assertNextEquals(TWO)
  600. self.assertNoAck()
  601. self.assertNextEquals(ONE)
  602. self._impl.ack(ONE)
  603. self.assertAck(ONE)
  604. self.assertNextEquals(THREE)
  605. self._impl.ack(THREE)
  606. self.assertNoAck()
  607. self.assertNextEquals(None)
  608. self.assertNoAck()
  609. def test_single_ack_flush(self) -> None:
  610. # same as ack test but ends with a flush-pkt instead of done
  611. self._walker.lines[-1] = (None, None)
  612. self.assertNextEquals(TWO)
  613. self.assertNoAck()
  614. self.assertNextEquals(ONE)
  615. self._impl.ack(ONE)
  616. self.assertAck(ONE)
  617. self.assertNextEquals(THREE)
  618. self.assertNoAck()
  619. self.assertNextEquals(None)
  620. self.assertNoAck()
  621. def test_single_ack_nak(self) -> None:
  622. self.assertNextEquals(TWO)
  623. self.assertNoAck()
  624. self.assertNextEquals(ONE)
  625. self.assertNoAck()
  626. self.assertNextEquals(THREE)
  627. self.assertNoAck()
  628. self.assertNextEquals(None)
  629. self.assertNextEmpty()
  630. self.assertNak()
  631. def test_single_ack_nak_flush(self) -> None:
  632. # same as nak test but ends with a flush-pkt instead of done
  633. self._walker.lines[-1] = (None, None)
  634. self.assertNextEquals(TWO)
  635. self.assertNoAck()
  636. self.assertNextEquals(ONE)
  637. self.assertNoAck()
  638. self.assertNextEquals(THREE)
  639. self.assertNoAck()
  640. self.assertNextEquals(None)
  641. self.assertNextEmpty()
  642. self.assertNak()
  643. class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
  644. impl_cls = MultiAckGraphWalkerImpl
  645. def test_multi_ack(self) -> None:
  646. self.assertNextEquals(TWO)
  647. self.assertNoAck()
  648. self.assertNextEquals(ONE)
  649. self._impl.ack(ONE)
  650. self.assertAck(ONE, b"continue")
  651. self.assertNextEquals(THREE)
  652. self._impl.ack(THREE)
  653. self.assertAck(THREE, b"continue")
  654. self.assertNextEquals(None)
  655. self.assertNextEmpty()
  656. self.assertAck(THREE)
  657. def test_multi_ack_partial(self) -> None:
  658. self.assertNextEquals(TWO)
  659. self.assertNoAck()
  660. self.assertNextEquals(ONE)
  661. self._impl.ack(ONE)
  662. self.assertAck(ONE, b"continue")
  663. self.assertNextEquals(THREE)
  664. self.assertNoAck()
  665. self.assertNextEquals(None)
  666. self.assertNextEmpty()
  667. self.assertAck(ONE)
  668. def test_multi_ack_flush(self) -> None:
  669. self._walker.lines = [
  670. (b"have", TWO),
  671. (None, None),
  672. (b"have", ONE),
  673. (b"have", THREE),
  674. (b"done", None),
  675. ]
  676. self.assertNextEquals(TWO)
  677. self.assertNoAck()
  678. self.assertNextEquals(ONE)
  679. self.assertNak() # nak the flush-pkt
  680. self._impl.ack(ONE)
  681. self.assertAck(ONE, b"continue")
  682. self.assertNextEquals(THREE)
  683. self._impl.ack(THREE)
  684. self.assertAck(THREE, b"continue")
  685. self.assertNextEquals(None)
  686. self.assertNextEmpty()
  687. self.assertAck(THREE)
  688. def test_multi_ack_nak(self) -> None:
  689. self.assertNextEquals(TWO)
  690. self.assertNoAck()
  691. self.assertNextEquals(ONE)
  692. self.assertNoAck()
  693. self.assertNextEquals(THREE)
  694. self.assertNoAck()
  695. self.assertNextEquals(None)
  696. self.assertNextEmpty()
  697. self.assertNak()
  698. class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
  699. impl_cls = MultiAckDetailedGraphWalkerImpl
  700. def test_multi_ack(self) -> None:
  701. self.assertNextEquals(TWO)
  702. self.assertNoAck()
  703. self.assertNextEquals(ONE)
  704. self._impl.ack(ONE)
  705. self.assertAck(ONE, b"common")
  706. self.assertNextEquals(THREE)
  707. self._impl.ack(THREE)
  708. self.assertAck(THREE, b"common")
  709. # done is read.
  710. self._walker.wants_satisified = True
  711. self.assertNextEquals(None)
  712. self._walker.lines.append((None, None))
  713. self.assertNextEmpty()
  714. self.assertAcks([(THREE, b"ready"), (None, b"nak"), (THREE, b"")])
  715. # PACK is sent
  716. self.assertTrue(self._walker.pack_sent)
  717. def test_multi_ack_nodone(self) -> None:
  718. self._walker.done_required = False
  719. self.assertNextEquals(TWO)
  720. self.assertNoAck()
  721. self.assertNextEquals(ONE)
  722. self._impl.ack(ONE)
  723. self.assertAck(ONE, b"common")
  724. self.assertNextEquals(THREE)
  725. self._impl.ack(THREE)
  726. self.assertAck(THREE, b"common")
  727. # done is read.
  728. self._walker.wants_satisified = True
  729. self.assertNextEquals(None)
  730. self._walker.lines.append((None, None))
  731. self.assertNextEmpty()
  732. self.assertAcks([(THREE, b"ready"), (None, b"nak"), (THREE, b"")])
  733. # PACK is sent
  734. self.assertTrue(self._walker.pack_sent)
  735. def test_multi_ack_flush_end(self) -> None:
  736. # transmission ends with a flush-pkt without a done but no-done is
  737. # assumed.
  738. self._walker.lines[-1] = (None, None)
  739. self.assertNextEquals(TWO)
  740. self.assertNoAck()
  741. self.assertNextEquals(ONE)
  742. self._impl.ack(ONE)
  743. self.assertAck(ONE, b"common")
  744. self.assertNextEquals(THREE)
  745. self._impl.ack(THREE)
  746. self.assertAck(THREE, b"common")
  747. # no done is read
  748. self._walker.wants_satisified = True
  749. self.assertNextEmpty()
  750. self.assertAcks([(THREE, b"ready"), (None, b"nak")])
  751. # PACK is NOT sent
  752. self.assertFalse(self._walker.pack_sent)
  753. def test_multi_ack_flush_end_nodone(self) -> None:
  754. # transmission ends with a flush-pkt without a done but no-done is
  755. # assumed.
  756. self._walker.lines[-1] = (None, None)
  757. self._walker.done_required = False
  758. self.assertNextEquals(TWO)
  759. self.assertNoAck()
  760. self.assertNextEquals(ONE)
  761. self._impl.ack(ONE)
  762. self.assertAck(ONE, b"common")
  763. self.assertNextEquals(THREE)
  764. self._impl.ack(THREE)
  765. self.assertAck(THREE, b"common")
  766. # no done is read, but pretend it is (last 'ACK 'commit_id' '')
  767. self._walker.wants_satisified = True
  768. self.assertNextEmpty()
  769. self.assertAcks([(THREE, b"ready"), (None, b"nak"), (THREE, b"")])
  770. # PACK is sent
  771. self.assertTrue(self._walker.pack_sent)
  772. def test_multi_ack_partial(self) -> None:
  773. self.assertNextEquals(TWO)
  774. self.assertNoAck()
  775. self.assertNextEquals(ONE)
  776. self._impl.ack(ONE)
  777. self.assertAck(ONE, b"common")
  778. self.assertNextEquals(THREE)
  779. self.assertNoAck()
  780. self.assertNextEquals(None)
  781. self.assertNextEmpty()
  782. self.assertAck(ONE)
  783. def test_multi_ack_flush(self) -> None:
  784. # same as ack test but contains a flush-pkt in the middle
  785. self._walker.lines = [
  786. (b"have", TWO),
  787. (None, None),
  788. (b"have", ONE),
  789. (b"have", THREE),
  790. (b"done", None),
  791. (None, None),
  792. ]
  793. self.assertNextEquals(TWO)
  794. self.assertNoAck()
  795. self.assertNextEquals(ONE)
  796. self.assertNak() # nak the flush-pkt
  797. self._impl.ack(ONE)
  798. self.assertAck(ONE, b"common")
  799. self.assertNextEquals(THREE)
  800. self._impl.ack(THREE)
  801. self.assertAck(THREE, b"common")
  802. self._walker.wants_satisified = True
  803. self.assertNextEquals(None)
  804. self.assertNextEmpty()
  805. self.assertAcks([(THREE, b"ready"), (None, b"nak"), (THREE, b"")])
  806. def test_multi_ack_nak(self) -> None:
  807. self.assertNextEquals(TWO)
  808. self.assertNoAck()
  809. self.assertNextEquals(ONE)
  810. self.assertNoAck()
  811. self.assertNextEquals(THREE)
  812. self.assertNoAck()
  813. # Done is sent here.
  814. self.assertNextEquals(None)
  815. self.assertNextEmpty()
  816. self.assertNak()
  817. self.assertNextEmpty()
  818. self.assertTrue(self._walker.pack_sent)
  819. def test_multi_ack_nak_nodone(self) -> None:
  820. self._walker.done_required = False
  821. self.assertNextEquals(TWO)
  822. self.assertNoAck()
  823. self.assertNextEquals(ONE)
  824. self.assertNoAck()
  825. self.assertNextEquals(THREE)
  826. self.assertNoAck()
  827. # Done is sent here.
  828. self.assertFalse(self._walker.pack_sent)
  829. self.assertNextEquals(None)
  830. self.assertNextEmpty()
  831. self.assertTrue(self._walker.pack_sent)
  832. self.assertNak()
  833. self.assertNextEmpty()
  834. def test_multi_ack_nak_flush(self) -> None:
  835. # same as nak test but contains a flush-pkt in the middle
  836. self._walker.lines = [
  837. (b"have", TWO),
  838. (None, None),
  839. (b"have", ONE),
  840. (b"have", THREE),
  841. (b"done", None),
  842. ]
  843. self.assertNextEquals(TWO)
  844. self.assertNoAck()
  845. self.assertNextEquals(ONE)
  846. self.assertNak()
  847. self.assertNextEquals(THREE)
  848. self.assertNoAck()
  849. self.assertNextEquals(None)
  850. self.assertNextEmpty()
  851. self.assertNak()
  852. def test_multi_ack_stateless(self) -> None:
  853. # transmission ends with a flush-pkt
  854. self._walker.lines[-1] = (None, None)
  855. self._walker.stateless_rpc = True
  856. self.assertNextEquals(TWO)
  857. self.assertNoAck()
  858. self.assertNextEquals(ONE)
  859. self.assertNoAck()
  860. self.assertNextEquals(THREE)
  861. self.assertNoAck()
  862. self.assertFalse(self._walker.pack_sent)
  863. self.assertNextEquals(None)
  864. self.assertNak()
  865. self.assertNextEmpty()
  866. self.assertNoAck()
  867. self.assertFalse(self._walker.pack_sent)
  868. def test_multi_ack_stateless_nodone(self) -> None:
  869. self._walker.done_required = False
  870. # transmission ends with a flush-pkt
  871. self._walker.lines[-1] = (None, None)
  872. self._walker.stateless_rpc = True
  873. self.assertNextEquals(TWO)
  874. self.assertNoAck()
  875. self.assertNextEquals(ONE)
  876. self.assertNoAck()
  877. self.assertNextEquals(THREE)
  878. self.assertNoAck()
  879. self.assertFalse(self._walker.pack_sent)
  880. self.assertNextEquals(None)
  881. self.assertNak()
  882. self.assertNextEmpty()
  883. self.assertNoAck()
  884. # PACK will still not be sent.
  885. self.assertFalse(self._walker.pack_sent)
  886. class FileSystemBackendTests(TestCase):
  887. """Tests for FileSystemBackend."""
  888. def setUp(self) -> None:
  889. super().setUp()
  890. self.path = tempfile.mkdtemp()
  891. self.addCleanup(shutil.rmtree, self.path)
  892. self.repo = Repo.init(self.path)
  893. if sys.platform == "win32":
  894. self.backend = FileSystemBackend(self.path[0] + ":" + os.sep)
  895. else:
  896. self.backend = FileSystemBackend()
  897. def test_nonexistant(self) -> None:
  898. self.assertRaises(
  899. NotGitRepository,
  900. self.backend.open_repository,
  901. "/does/not/exist/unless/foo",
  902. )
  903. def test_absolute(self) -> None:
  904. repo = self.backend.open_repository(self.path)
  905. self.assertTrue(
  906. os.path.samefile(
  907. os.path.abspath(repo.path), os.path.abspath(self.repo.path)
  908. )
  909. )
  910. def test_child(self) -> None:
  911. self.assertRaises(
  912. NotGitRepository,
  913. self.backend.open_repository,
  914. os.path.join(self.path, "foo"),
  915. )
  916. def test_bad_repo_path(self) -> None:
  917. backend = FileSystemBackend()
  918. self.assertRaises(NotGitRepository, lambda: backend.open_repository("/ups"))
  919. def test_bytes_path(self) -> None:
  920. # Test that FileSystemBackend can handle bytes paths (issue #973)
  921. repo = self.backend.open_repository(self.path.encode("utf-8"))
  922. self.assertTrue(
  923. os.path.samefile(
  924. os.path.abspath(repo.path), os.path.abspath(self.repo.path)
  925. )
  926. )
  927. class DictBackendTests(TestCase):
  928. """Tests for DictBackend."""
  929. def test_nonexistant(self) -> None:
  930. repo = MemoryRepo.init_bare([], {})
  931. backend = DictBackend({b"/": repo})
  932. self.assertRaises(
  933. NotGitRepository,
  934. backend.open_repository,
  935. "/does/not/exist/unless/foo",
  936. )
  937. def test_bad_repo_path(self) -> None:
  938. repo = MemoryRepo.init_bare([], {})
  939. backend = DictBackend({b"/": repo})
  940. self.assertRaises(NotGitRepository, lambda: backend.open_repository("/ups"))
  941. class ServeCommandTests(TestCase):
  942. """Tests for serve_command."""
  943. def setUp(self) -> None:
  944. super().setUp()
  945. self.backend = DictBackend({})
  946. def serve_command(self, handler_cls, args, inf, outf):
  947. return serve_command(
  948. handler_cls,
  949. [b"test", *args],
  950. backend=self.backend,
  951. inf=inf,
  952. outf=outf,
  953. )
  954. def test_receive_pack(self) -> None:
  955. commit = make_commit(id=ONE, parents=[], commit_time=111)
  956. self.backend.repos[b"/"] = MemoryRepo.init_bare(
  957. [commit], {b"refs/heads/master": commit.id}
  958. )
  959. outf = BytesIO()
  960. exitcode = self.serve_command(
  961. ReceivePackHandler, [b"/"], BytesIO(b"0000"), outf
  962. )
  963. outlines = outf.getvalue().splitlines()
  964. self.assertEqual(2, len(outlines))
  965. self.assertEqual(
  966. b"1111111111111111111111111111111111111111 refs/heads/master",
  967. outlines[0][4:].split(b"\x00")[0],
  968. )
  969. self.assertEqual(b"0000", outlines[-1])
  970. self.assertEqual(0, exitcode)
  971. class UpdateServerInfoTests(TestCase):
  972. """Tests for update_server_info."""
  973. def setUp(self) -> None:
  974. super().setUp()
  975. self.path = tempfile.mkdtemp()
  976. self.addCleanup(shutil.rmtree, self.path)
  977. self.repo = Repo.init(self.path)
  978. def test_empty(self) -> None:
  979. update_server_info(self.repo)
  980. with open(os.path.join(self.path, ".git", "info", "refs"), "rb") as f:
  981. self.assertEqual(b"", f.read())
  982. p = os.path.join(self.path, ".git", "objects", "info", "packs")
  983. with open(p, "rb") as f:
  984. self.assertEqual(b"", f.read())
  985. def test_simple(self) -> None:
  986. commit_id = self.repo.get_worktree().commit(
  987. message=b"foo",
  988. committer=b"Joe Example <joe@example.com>",
  989. ref=b"refs/heads/foo",
  990. )
  991. update_server_info(self.repo)
  992. with open(os.path.join(self.path, ".git", "info", "refs"), "rb") as f:
  993. self.assertEqual(f.read(), commit_id + b"\trefs/heads/foo\n")
  994. p = os.path.join(self.path, ".git", "objects", "info", "packs")
  995. with open(p, "rb") as f:
  996. self.assertEqual(f.read(), b"")