test_server.py 37 KB

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