test_server.py 37 KB

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