test_server.py 37 KB

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