test_server.py 37 KB

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