test_server.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179
  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 typing import Dict, List
  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):
  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):
  78. self._received[band].append(data)
  79. def write_pkt_line(self, data):
  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):
  95. super().setUp()
  96. self._handler = TestGenericPackHandler()
  97. def assertSucceeds(self, func, *args, **kwargs):
  98. try:
  99. func(*args, **kwargs)
  100. except GitProtocolError as e:
  101. self.fail(e)
  102. def test_capability_line(self):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  230. self.assertEqual(set(expected), set(actual))
  231. def test_linear(self):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  467. self._walker.proto.set_output([*lines, None])
  468. self._walker._handle_shallow_request(heads)
  469. def assertReceived(self, expected):
  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):
  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):
  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):
  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""):
  523. self.acks.append((sha, ack_type))
  524. def send_nak(self):
  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):
  541. self.done_received = True
  542. class AckGraphWalkerImplTestCase(TestCase):
  543. """Base setup and asserts for AckGraphWalker tests."""
  544. def setUp(self):
  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):
  556. self.assertEqual(None, self._walker.pop_ack())
  557. def assertAcks(self, acks):
  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""):
  562. self.assertAcks([(sha, ack_type)])
  563. def assertNak(self):
  564. self.assertAck(None, b"nak")
  565. def assertNextEquals(self, sha):
  566. self.assertEqual(sha, next(self._impl))
  567. def assertNextEmpty(self):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  877. self.assertRaises(
  878. NotGitRepository,
  879. self.backend.open_repository,
  880. "/does/not/exist/unless/foo",
  881. )
  882. def test_absolute(self):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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):
  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"")