2
0

test_server.py 38 KB

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