2
0

test_server.py 37 KB

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