test_repository.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. # -*- coding: utf-8 -*-
  2. # test_repository.py -- tests for repository.py
  3. # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; version 2
  8. # of the License or (at your option) any later version of
  9. # the License.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  19. # MA 02110-1301, USA.
  20. """Tests for the repository."""
  21. from contextlib import closing
  22. import locale
  23. import os
  24. import stat
  25. import shutil
  26. import tempfile
  27. import warnings
  28. import sys
  29. from dulwich import errors
  30. from dulwich.object_store import (
  31. tree_lookup_path,
  32. )
  33. from dulwich import objects
  34. from dulwich.config import Config
  35. from dulwich.repo import (
  36. Repo,
  37. MemoryRepo,
  38. )
  39. from dulwich.tests import (
  40. TestCase,
  41. skipIf,
  42. )
  43. from dulwich.tests.utils import (
  44. open_repo,
  45. tear_down_repo,
  46. setup_warning_catcher,
  47. )
  48. missing_sha = b'b91fa4d900e17e99b433218e988c4eb4a3e9a097'
  49. def mkdtemp_unicode():
  50. suffix = u'déłwíçh'
  51. return tempfile.mkdtemp(suffix=suffix)
  52. class CreateRepositoryTests(TestCase):
  53. def assertFileContentsEqual(self, expected, repo, path):
  54. f = repo.get_named_file(path)
  55. if not f:
  56. self.assertEqual(expected, None)
  57. else:
  58. with f:
  59. self.assertEqual(expected, f.read())
  60. def _check_repo_contents(self, repo, expect_bare):
  61. self.assertEqual(expect_bare, repo.bare)
  62. self.assertFileContentsEqual(b'Unnamed repository', repo, 'description')
  63. self.assertFileContentsEqual(b'', repo, os.path.join('info', 'exclude'))
  64. self.assertFileContentsEqual(None, repo, 'nonexistent file')
  65. barestr = b'bare = ' + str(expect_bare).lower().encode('ascii')
  66. with repo.get_named_file('config') as f:
  67. config_text = f.read()
  68. self.assertTrue(barestr in config_text, "%r" % config_text)
  69. def test_create_memory(self):
  70. repo = MemoryRepo.init_bare([], {})
  71. self._check_repo_contents(repo, True)
  72. def test_create_disk_bare(self):
  73. tmp_dir = mkdtemp_unicode()
  74. self.addCleanup(shutil.rmtree, tmp_dir)
  75. repo = Repo.init_bare(tmp_dir)
  76. self.assertEqual(tmp_dir, repo._controldir)
  77. self._check_repo_contents(repo, True)
  78. def test_create_disk_non_bare(self):
  79. tmp_dir = mkdtemp_unicode()
  80. self.addCleanup(shutil.rmtree, tmp_dir)
  81. repo = Repo.init(tmp_dir)
  82. self.assertEqual(os.path.join(tmp_dir, '.git'), repo._controldir)
  83. self._check_repo_contents(repo, False)
  84. class RepositoryRootTests(TestCase):
  85. def setUp(self):
  86. super(RepositoryRootTests, self).setUp()
  87. self._repo = None
  88. def tearDown(self):
  89. if self._repo is not None:
  90. tear_down_repo(self._repo)
  91. super(RepositoryRootTests, self).tearDown()
  92. def mkdtemp(self):
  93. return mkdtemp_unicode()
  94. def open_repo(self, name):
  95. temp_dir = self.mkdtemp()
  96. return open_repo(name, temp_dir)
  97. def test_simple_props(self):
  98. r = self._repo = self.open_repo('a.git')
  99. self.assertEqual(r.controldir(), r.path)
  100. def test_setitem(self):
  101. r = self._repo = self.open_repo('a.git')
  102. r[b"refs/tags/foo"] = b'a90fa2d900a17e99b433217e988c4eb4a2e9a097'
  103. self.assertEqual(b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  104. r[b"refs/tags/foo"].id)
  105. def test_getitem_unicode(self):
  106. r = self._repo = self.open_repo('a.git')
  107. test_keys = [
  108. (b'refs/heads/master', True),
  109. (b'a90fa2d900a17e99b433217e988c4eb4a2e9a097', True),
  110. (b'11' * 19 + b'--', False),
  111. ]
  112. for k, contained in test_keys:
  113. self.assertEqual(k in r, contained)
  114. for k, _ in test_keys:
  115. self.assertRaisesRegexp(
  116. TypeError, "'name' must be bytestring, not int",
  117. r.__getitem__, 12
  118. )
  119. def test_delitem(self):
  120. r = self._repo = self.open_repo('a.git')
  121. del r[b'refs/heads/master']
  122. self.assertRaises(KeyError, lambda: r[b'refs/heads/master'])
  123. del r[b'HEAD']
  124. self.assertRaises(KeyError, lambda: r[b'HEAD'])
  125. self.assertRaises(ValueError, r.__delitem__, b'notrefs/foo')
  126. def test_get_refs(self):
  127. r = self._repo = self.open_repo('a.git')
  128. self.assertEqual({
  129. b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  130. b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  131. b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
  132. b'refs/tags/mytag-packed': b'b0931cadc54336e78a1d980420e3268903b57a50',
  133. }, r.get_refs())
  134. def test_head(self):
  135. r = self._repo = self.open_repo('a.git')
  136. self.assertEqual(r.head(), b'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
  137. def test_get_object(self):
  138. r = self._repo = self.open_repo('a.git')
  139. obj = r.get_object(r.head())
  140. self.assertEqual(obj.type_name, b'commit')
  141. def test_get_object_non_existant(self):
  142. r = self._repo = self.open_repo('a.git')
  143. self.assertRaises(KeyError, r.get_object, missing_sha)
  144. def test_contains_object(self):
  145. r = self._repo = self.open_repo('a.git')
  146. self.assertTrue(r.head() in r)
  147. def test_contains_ref(self):
  148. r = self._repo = self.open_repo('a.git')
  149. self.assertTrue(b"HEAD" in r)
  150. def test_get_no_description(self):
  151. r = self._repo = self.open_repo('a.git')
  152. self.assertIs(None, r.get_description())
  153. def test_get_description(self):
  154. r = self._repo = self.open_repo('a.git')
  155. with open(os.path.join(r.path, 'description'), 'wb') as f:
  156. f.write(b"Some description")
  157. self.assertEqual(b"Some description", r.get_description())
  158. def test_set_description(self):
  159. r = self._repo = self.open_repo('a.git')
  160. description = b"Some description"
  161. r.set_description(description)
  162. self.assertEqual(description, r.get_description())
  163. def test_contains_missing(self):
  164. r = self._repo = self.open_repo('a.git')
  165. self.assertFalse(b"bar" in r)
  166. def test_get_peeled(self):
  167. # unpacked ref
  168. r = self._repo = self.open_repo('a.git')
  169. tag_sha = b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a'
  170. self.assertNotEqual(r[tag_sha].sha().hexdigest(), r.head())
  171. self.assertEqual(r.get_peeled(b'refs/tags/mytag'), r.head())
  172. # packed ref with cached peeled value
  173. packed_tag_sha = b'b0931cadc54336e78a1d980420e3268903b57a50'
  174. parent_sha = r[r.head()].parents[0]
  175. self.assertNotEqual(r[packed_tag_sha].sha().hexdigest(), parent_sha)
  176. self.assertEqual(r.get_peeled(b'refs/tags/mytag-packed'), parent_sha)
  177. # TODO: add more corner cases to test repo
  178. def test_get_peeled_not_tag(self):
  179. r = self._repo = self.open_repo('a.git')
  180. self.assertEqual(r.get_peeled(b'HEAD'), r.head())
  181. def test_get_walker(self):
  182. r = self._repo = self.open_repo('a.git')
  183. # include defaults to [r.head()]
  184. self.assertEqual([e.commit.id for e in r.get_walker()],
  185. [r.head(), b'2a72d929692c41d8554c07f6301757ba18a65d91'])
  186. self.assertEqual(
  187. [e.commit.id for e in r.get_walker([b'2a72d929692c41d8554c07f6301757ba18a65d91'])],
  188. [b'2a72d929692c41d8554c07f6301757ba18a65d91'])
  189. self.assertEqual(
  190. [e.commit.id for e in r.get_walker(b'2a72d929692c41d8554c07f6301757ba18a65d91')],
  191. [b'2a72d929692c41d8554c07f6301757ba18a65d91'])
  192. def test_clone(self):
  193. r = self._repo = self.open_repo('a.git')
  194. tmp_dir = self.mkdtemp()
  195. self.addCleanup(shutil.rmtree, tmp_dir)
  196. with closing(r.clone(tmp_dir, mkdir=False)) as t:
  197. self.assertEqual({
  198. b'HEAD': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  199. b'refs/remotes/origin/master':
  200. b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  201. b'refs/heads/master': b'a90fa2d900a17e99b433217e988c4eb4a2e9a097',
  202. b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
  203. b'refs/tags/mytag-packed':
  204. b'b0931cadc54336e78a1d980420e3268903b57a50',
  205. }, t.refs.as_dict())
  206. shas = [e.commit.id for e in r.get_walker()]
  207. self.assertEqual(shas, [t.head(),
  208. b'2a72d929692c41d8554c07f6301757ba18a65d91'])
  209. def test_clone_no_head(self):
  210. temp_dir = self.mkdtemp()
  211. if isinstance(temp_dir, bytes):
  212. temp_dir_str = temp_dir.decode(sys.getfilesystemencoding())
  213. else:
  214. temp_dir_str = temp_dir
  215. self.addCleanup(shutil.rmtree, temp_dir)
  216. repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos')
  217. dest_dir = os.path.join(temp_dir_str, 'a.git')
  218. shutil.copytree(os.path.join(repo_dir, 'a.git'),
  219. dest_dir, symlinks=True)
  220. r = Repo(dest_dir)
  221. del r.refs[b"refs/heads/master"]
  222. del r.refs[b"HEAD"]
  223. t = r.clone(os.path.join(temp_dir_str, 'b.git'), mkdir=True)
  224. self.assertEqual({
  225. b'refs/tags/mytag': b'28237f4dc30d0d462658d6b937b08a0f0b6ef55a',
  226. b'refs/tags/mytag-packed':
  227. b'b0931cadc54336e78a1d980420e3268903b57a50',
  228. }, t.refs.as_dict())
  229. def test_clone_empty(self):
  230. """Test clone() doesn't crash if HEAD points to a non-existing ref.
  231. This simulates cloning server-side bare repository either when it is
  232. still empty or if user renames master branch and pushes private repo
  233. to the server.
  234. Non-bare repo HEAD always points to an existing ref.
  235. """
  236. r = self._repo = self.open_repo('empty.git')
  237. tmp_dir = self.mkdtemp()
  238. self.addCleanup(shutil.rmtree, tmp_dir)
  239. r.clone(tmp_dir, mkdir=False, bare=True)
  240. def test_merge_history(self):
  241. r = self._repo = self.open_repo('simple_merge.git')
  242. shas = [e.commit.id for e in r.get_walker()]
  243. self.assertEqual(shas, [b'5dac377bdded4c9aeb8dff595f0faeebcc8498cc',
  244. b'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd',
  245. b'4cffe90e0a41ad3f5190079d7c8f036bde29cbe6',
  246. b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
  247. b'0d89f20333fbb1d2f3a94da77f4981373d8f4310'])
  248. def test_out_of_order_merge(self):
  249. """Test that revision history is ordered by date, not parent order."""
  250. r = self._repo = self.open_repo('ooo_merge.git')
  251. shas = [e.commit.id for e in r.get_walker()]
  252. self.assertEqual(shas, [b'7601d7f6231db6a57f7bbb79ee52e4d462fd44d1',
  253. b'f507291b64138b875c28e03469025b1ea20bc614',
  254. b'fb5b0425c7ce46959bec94d54b9a157645e114f5',
  255. b'f9e39b120c68182a4ba35349f832d0e4e61f485c'])
  256. def test_get_tags_empty(self):
  257. r = self._repo = self.open_repo('ooo_merge.git')
  258. self.assertEqual({}, r.refs.as_dict(b'refs/tags'))
  259. def test_get_config(self):
  260. r = self._repo = self.open_repo('ooo_merge.git')
  261. self.assertIsInstance(r.get_config(), Config)
  262. def test_get_config_stack(self):
  263. r = self._repo = self.open_repo('ooo_merge.git')
  264. self.assertIsInstance(r.get_config_stack(), Config)
  265. @skipIf(sys.platform == 'win32', 'Requires symlink support')
  266. def test_submodule(self):
  267. temp_dir = self.mkdtemp()
  268. repo_dir = os.path.join(os.path.dirname(__file__), 'data', 'repos')
  269. if isinstance(temp_dir, bytes):
  270. temp_dir_str = temp_dir.decode(sys.getfilesystemencoding())
  271. else:
  272. temp_dir_str = temp_dir
  273. shutil.copytree(os.path.join(repo_dir, 'a.git'),
  274. os.path.join(temp_dir_str, 'a.git'), symlinks=True)
  275. rel = os.path.relpath(os.path.join(repo_dir, 'submodule'), temp_dir_str)
  276. os.symlink(os.path.join(rel, 'dotgit'), os.path.join(temp_dir_str, '.git'))
  277. r = Repo(temp_dir)
  278. self.assertEqual(r.head(), b'a90fa2d900a17e99b433217e988c4eb4a2e9a097')
  279. def test_common_revisions(self):
  280. """
  281. This test demonstrates that ``find_common_revisions()`` actually returns
  282. common heads, not revisions; dulwich already uses
  283. ``find_common_revisions()`` in such a manner (see
  284. ``Repo.fetch_objects()``).
  285. """
  286. expected_shas = set([b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e'])
  287. # Source for objects.
  288. r_base = self.open_repo('simple_merge.git')
  289. # Re-create each-side of the merge in simple_merge.git.
  290. #
  291. # Since the trees and blobs are missing, the repository created is
  292. # corrupted, but we're only checking for commits for the purpose of this
  293. # test, so it's immaterial.
  294. r1_dir = self.mkdtemp()
  295. r1_commits = [b'ab64bbdcc51b170d21588e5c5d391ee5c0c96dfd', # HEAD
  296. b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
  297. b'0d89f20333fbb1d2f3a94da77f4981373d8f4310']
  298. r2_dir = self.mkdtemp()
  299. r2_commits = [b'4cffe90e0a41ad3f5190079d7c8f036bde29cbe6', # HEAD
  300. b'60dacdc733de308bb77bb76ce0fb0f9b44c9769e',
  301. b'0d89f20333fbb1d2f3a94da77f4981373d8f4310']
  302. try:
  303. r1 = Repo.init_bare(r1_dir)
  304. for c in r1_commits:
  305. r1.object_store.add_object(r_base.get_object(c))
  306. r1.refs[b'HEAD'] = r1_commits[0]
  307. r2 = Repo.init_bare(r2_dir)
  308. for c in r2_commits:
  309. r2.object_store.add_object(r_base.get_object(c))
  310. r2.refs[b'HEAD'] = r2_commits[0]
  311. # Finally, the 'real' testing!
  312. shas = r2.object_store.find_common_revisions(r1.get_graph_walker())
  313. self.assertEqual(set(shas), expected_shas)
  314. shas = r1.object_store.find_common_revisions(r2.get_graph_walker())
  315. self.assertEqual(set(shas), expected_shas)
  316. finally:
  317. shutil.rmtree(r1_dir)
  318. shutil.rmtree(r2_dir)
  319. def test_shell_hook_pre_commit(self):
  320. if os.name != 'posix':
  321. self.skipTest('shell hook tests requires POSIX shell')
  322. pre_commit_fail = """#!/bin/sh
  323. exit 1
  324. """
  325. pre_commit_success = """#!/bin/sh
  326. exit 0
  327. """
  328. repo_dir = os.path.join(self.mkdtemp())
  329. r = Repo.init(repo_dir)
  330. self.addCleanup(shutil.rmtree, repo_dir)
  331. pre_commit = os.path.join(r.controldir(), 'hooks', 'pre-commit')
  332. with open(pre_commit, 'w') as f:
  333. f.write(pre_commit_fail)
  334. os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  335. self.assertRaises(errors.CommitError, r.do_commit, 'failed commit',
  336. committer='Test Committer <test@nodomain.com>',
  337. author='Test Author <test@nodomain.com>',
  338. commit_timestamp=12345, commit_timezone=0,
  339. author_timestamp=12345, author_timezone=0)
  340. with open(pre_commit, 'w') as f:
  341. f.write(pre_commit_success)
  342. os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  343. commit_sha = r.do_commit(
  344. b'empty commit',
  345. committer=b'Test Committer <test@nodomain.com>',
  346. author=b'Test Author <test@nodomain.com>',
  347. commit_timestamp=12395, commit_timezone=0,
  348. author_timestamp=12395, author_timezone=0)
  349. self.assertEqual([], r[commit_sha].parents)
  350. def test_shell_hook_commit_msg(self):
  351. if os.name != 'posix':
  352. self.skipTest('shell hook tests requires POSIX shell')
  353. commit_msg_fail = """#!/bin/sh
  354. exit 1
  355. """
  356. commit_msg_success = """#!/bin/sh
  357. exit 0
  358. """
  359. repo_dir = os.path.join(self.mkdtemp())
  360. r = Repo.init(repo_dir)
  361. self.addCleanup(shutil.rmtree, repo_dir)
  362. commit_msg = os.path.join(r.controldir(), 'hooks', 'commit-msg')
  363. with open(commit_msg, 'w') as f:
  364. f.write(commit_msg_fail)
  365. os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  366. self.assertRaises(errors.CommitError, r.do_commit, b'failed commit',
  367. committer=b'Test Committer <test@nodomain.com>',
  368. author=b'Test Author <test@nodomain.com>',
  369. commit_timestamp=12345, commit_timezone=0,
  370. author_timestamp=12345, author_timezone=0)
  371. with open(commit_msg, 'w') as f:
  372. f.write(commit_msg_success)
  373. os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  374. commit_sha = r.do_commit(
  375. b'empty commit',
  376. committer=b'Test Committer <test@nodomain.com>',
  377. author=b'Test Author <test@nodomain.com>',
  378. commit_timestamp=12395, commit_timezone=0,
  379. author_timestamp=12395, author_timezone=0)
  380. self.assertEqual([], r[commit_sha].parents)
  381. def test_shell_hook_post_commit(self):
  382. if os.name != 'posix':
  383. self.skipTest('shell hook tests requires POSIX shell')
  384. repo_dir = self.mkdtemp()
  385. r = Repo.init(repo_dir)
  386. self.addCleanup(shutil.rmtree, repo_dir)
  387. (fd, path) = tempfile.mkstemp(dir=repo_dir)
  388. os.close(fd)
  389. post_commit_msg = """#!/bin/sh
  390. rm """ + path + """
  391. """
  392. root_sha = r.do_commit(
  393. b'empty commit',
  394. committer=b'Test Committer <test@nodomain.com>',
  395. author=b'Test Author <test@nodomain.com>',
  396. commit_timestamp=12345, commit_timezone=0,
  397. author_timestamp=12345, author_timezone=0)
  398. self.assertEqual([], r[root_sha].parents)
  399. post_commit = os.path.join(r.controldir(), 'hooks', 'post-commit')
  400. with open(post_commit, 'wb') as f:
  401. f.write(post_commit_msg.encode(locale.getpreferredencoding()))
  402. os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  403. commit_sha = r.do_commit(
  404. b'empty commit',
  405. committer=b'Test Committer <test@nodomain.com>',
  406. author=b'Test Author <test@nodomain.com>',
  407. commit_timestamp=12345, commit_timezone=0,
  408. author_timestamp=12345, author_timezone=0)
  409. self.assertEqual([root_sha], r[commit_sha].parents)
  410. self.assertFalse(os.path.exists(path))
  411. post_commit_msg_fail = """#!/bin/sh
  412. exit 1
  413. """
  414. with open(post_commit, 'w') as f:
  415. f.write(post_commit_msg_fail)
  416. os.chmod(post_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  417. warnings.simplefilter("always", UserWarning)
  418. self.addCleanup(warnings.resetwarnings)
  419. warnings_list, restore_warnings = setup_warning_catcher()
  420. self.addCleanup(restore_warnings)
  421. commit_sha2 = r.do_commit(
  422. b'empty commit',
  423. committer=b'Test Committer <test@nodomain.com>',
  424. author=b'Test Author <test@nodomain.com>',
  425. commit_timestamp=12345, commit_timezone=0,
  426. author_timestamp=12345, author_timezone=0)
  427. self.assertEqual(len(warnings_list), 1, warnings_list)
  428. self.assertIsInstance(warnings_list[-1], UserWarning)
  429. self.assertTrue("post-commit hook failed: " in str(warnings_list[-1]))
  430. self.assertEqual([commit_sha], r[commit_sha2].parents)
  431. class BuildRepoRootTests(TestCase):
  432. """Tests that build on-disk repos from scratch.
  433. Repos live in a temp dir and are torn down after each test. They start with
  434. a single commit in master having single file named 'a'.
  435. """
  436. def get_repo_dir(self):
  437. return os.path.join(mkdtemp_unicode(), 'test')
  438. def setUp(self):
  439. super(BuildRepoRootTests, self).setUp()
  440. self._repo_dir = self.get_repo_dir()
  441. os.makedirs(self._repo_dir)
  442. r = self._repo = Repo.init(self._repo_dir)
  443. self.assertFalse(r.bare)
  444. self.assertEqual(b'ref: refs/heads/master', r.refs.read_ref(b'HEAD'))
  445. self.assertRaises(KeyError, lambda: r.refs[b'refs/heads/master'])
  446. with open(os.path.join(r.path, 'a'), 'wb') as f:
  447. f.write(b'file contents')
  448. r.stage(['a'])
  449. commit_sha = r.do_commit(b'msg',
  450. committer=b'Test Committer <test@nodomain.com>',
  451. author=b'Test Author <test@nodomain.com>',
  452. commit_timestamp=12345, commit_timezone=0,
  453. author_timestamp=12345, author_timezone=0)
  454. self.assertEqual([], r[commit_sha].parents)
  455. self._root_commit = commit_sha
  456. def tearDown(self):
  457. tear_down_repo(self._repo)
  458. super(BuildRepoRootTests, self).tearDown()
  459. def test_build_repo(self):
  460. r = self._repo
  461. self.assertEqual(b'ref: refs/heads/master', r.refs.read_ref(b'HEAD'))
  462. self.assertEqual(self._root_commit, r.refs[b'refs/heads/master'])
  463. expected_blob = objects.Blob.from_string(b'file contents')
  464. self.assertEqual(expected_blob.data, r[expected_blob.id].data)
  465. actual_commit = r[self._root_commit]
  466. self.assertEqual(b'msg', actual_commit.message)
  467. def test_commit_modified(self):
  468. r = self._repo
  469. with open(os.path.join(r.path, 'a'), 'wb') as f:
  470. f.write(b'new contents')
  471. r.stage(['a'])
  472. commit_sha = r.do_commit(b'modified a',
  473. committer=b'Test Committer <test@nodomain.com>',
  474. author=b'Test Author <test@nodomain.com>',
  475. commit_timestamp=12395, commit_timezone=0,
  476. author_timestamp=12395, author_timezone=0)
  477. self.assertEqual([self._root_commit], r[commit_sha].parents)
  478. a_mode, a_id = tree_lookup_path(r.get_object, r[commit_sha].tree, b'a')
  479. self.assertEqual(stat.S_IFREG | 0o644, a_mode)
  480. self.assertEqual(b'new contents', r[a_id].data)
  481. @skipIf(sys.platform == 'win32', 'Requires symlink support')
  482. def test_commit_symlink(self):
  483. r = self._repo
  484. os.symlink('a', os.path.join(r.path, 'b'))
  485. r.stage(['a', 'b'])
  486. commit_sha = r.do_commit(b'Symlink b',
  487. committer=b'Test Committer <test@nodomain.com>',
  488. author=b'Test Author <test@nodomain.com>',
  489. commit_timestamp=12395, commit_timezone=0,
  490. author_timestamp=12395, author_timezone=0)
  491. self.assertEqual([self._root_commit], r[commit_sha].parents)
  492. b_mode, b_id = tree_lookup_path(r.get_object, r[commit_sha].tree, b'b')
  493. self.assertTrue(stat.S_ISLNK(b_mode))
  494. self.assertEqual(b'a', r[b_id].data)
  495. def test_commit_deleted(self):
  496. r = self._repo
  497. os.remove(os.path.join(r.path, 'a'))
  498. r.stage(['a'])
  499. commit_sha = r.do_commit(b'deleted a',
  500. committer=b'Test Committer <test@nodomain.com>',
  501. author=b'Test Author <test@nodomain.com>',
  502. commit_timestamp=12395, commit_timezone=0,
  503. author_timestamp=12395, author_timezone=0)
  504. self.assertEqual([self._root_commit], r[commit_sha].parents)
  505. self.assertEqual([], list(r.open_index()))
  506. tree = r[r[commit_sha].tree]
  507. self.assertEqual([], list(tree.iteritems()))
  508. def test_commit_encoding(self):
  509. r = self._repo
  510. commit_sha = r.do_commit(b'commit with strange character \xee',
  511. committer=b'Test Committer <test@nodomain.com>',
  512. author=b'Test Author <test@nodomain.com>',
  513. commit_timestamp=12395, commit_timezone=0,
  514. author_timestamp=12395, author_timezone=0,
  515. encoding=b"iso8859-1")
  516. self.assertEqual(b"iso8859-1", r[commit_sha].encoding)
  517. def test_commit_config_identity(self):
  518. # commit falls back to the users' identity if it wasn't specified
  519. r = self._repo
  520. c = r.get_config()
  521. c.set((b"user", ), b"name", b"Jelmer")
  522. c.set((b"user", ), b"email", b"jelmer@apache.org")
  523. c.write_to_path()
  524. commit_sha = r.do_commit(b'message')
  525. self.assertEqual(
  526. b"Jelmer <jelmer@apache.org>",
  527. r[commit_sha].author)
  528. self.assertEqual(
  529. b"Jelmer <jelmer@apache.org>",
  530. r[commit_sha].committer)
  531. def test_commit_config_identity_in_memoryrepo(self):
  532. # commit falls back to the users' identity if it wasn't specified
  533. r = MemoryRepo.init_bare([], {})
  534. c = r.get_config()
  535. c.set((b"user", ), b"name", b"Jelmer")
  536. c.set((b"user", ), b"email", b"jelmer@apache.org")
  537. commit_sha = r.do_commit(b'message', tree=objects.Tree().id)
  538. self.assertEqual(
  539. b"Jelmer <jelmer@apache.org>",
  540. r[commit_sha].author)
  541. self.assertEqual(
  542. b"Jelmer <jelmer@apache.org>",
  543. r[commit_sha].committer)
  544. def test_commit_fail_ref(self):
  545. r = self._repo
  546. def set_if_equals(name, old_ref, new_ref):
  547. return False
  548. r.refs.set_if_equals = set_if_equals
  549. def add_if_new(name, new_ref):
  550. self.fail('Unexpected call to add_if_new')
  551. r.refs.add_if_new = add_if_new
  552. old_shas = set(r.object_store)
  553. self.assertRaises(errors.CommitError, r.do_commit, b'failed commit',
  554. committer=b'Test Committer <test@nodomain.com>',
  555. author=b'Test Author <test@nodomain.com>',
  556. commit_timestamp=12345, commit_timezone=0,
  557. author_timestamp=12345, author_timezone=0)
  558. new_shas = set(r.object_store) - old_shas
  559. self.assertEqual(1, len(new_shas))
  560. # Check that the new commit (now garbage) was added.
  561. new_commit = r[new_shas.pop()]
  562. self.assertEqual(r[self._root_commit].tree, new_commit.tree)
  563. self.assertEqual(b'failed commit', new_commit.message)
  564. def test_commit_branch(self):
  565. r = self._repo
  566. commit_sha = r.do_commit(b'commit to branch',
  567. committer=b'Test Committer <test@nodomain.com>',
  568. author=b'Test Author <test@nodomain.com>',
  569. commit_timestamp=12395, commit_timezone=0,
  570. author_timestamp=12395, author_timezone=0,
  571. ref=b"refs/heads/new_branch")
  572. self.assertEqual(self._root_commit, r[b"HEAD"].id)
  573. self.assertEqual(commit_sha, r[b"refs/heads/new_branch"].id)
  574. self.assertEqual([], r[commit_sha].parents)
  575. self.assertTrue(b"refs/heads/new_branch" in r)
  576. new_branch_head = commit_sha
  577. commit_sha = r.do_commit(b'commit to branch 2',
  578. committer=b'Test Committer <test@nodomain.com>',
  579. author=b'Test Author <test@nodomain.com>',
  580. commit_timestamp=12395, commit_timezone=0,
  581. author_timestamp=12395, author_timezone=0,
  582. ref=b"refs/heads/new_branch")
  583. self.assertEqual(self._root_commit, r[b"HEAD"].id)
  584. self.assertEqual(commit_sha, r[b"refs/heads/new_branch"].id)
  585. self.assertEqual([new_branch_head], r[commit_sha].parents)
  586. def test_commit_merge_heads(self):
  587. r = self._repo
  588. merge_1 = r.do_commit(b'commit to branch 2',
  589. committer=b'Test Committer <test@nodomain.com>',
  590. author=b'Test Author <test@nodomain.com>',
  591. commit_timestamp=12395, commit_timezone=0,
  592. author_timestamp=12395, author_timezone=0,
  593. ref=b"refs/heads/new_branch")
  594. commit_sha = r.do_commit(b'commit with merge',
  595. committer=b'Test Committer <test@nodomain.com>',
  596. author=b'Test Author <test@nodomain.com>',
  597. commit_timestamp=12395, commit_timezone=0,
  598. author_timestamp=12395, author_timezone=0,
  599. merge_heads=[merge_1])
  600. self.assertEqual(
  601. [self._root_commit, merge_1],
  602. r[commit_sha].parents)
  603. def test_commit_dangling_commit(self):
  604. r = self._repo
  605. old_shas = set(r.object_store)
  606. old_refs = r.get_refs()
  607. commit_sha = r.do_commit(b'commit with no ref',
  608. committer=b'Test Committer <test@nodomain.com>',
  609. author=b'Test Author <test@nodomain.com>',
  610. commit_timestamp=12395, commit_timezone=0,
  611. author_timestamp=12395, author_timezone=0,
  612. ref=None)
  613. new_shas = set(r.object_store) - old_shas
  614. # New sha is added, but no new refs
  615. self.assertEqual(1, len(new_shas))
  616. new_commit = r[new_shas.pop()]
  617. self.assertEqual(r[self._root_commit].tree, new_commit.tree)
  618. self.assertEqual([], r[commit_sha].parents)
  619. self.assertEqual(old_refs, r.get_refs())
  620. def test_commit_dangling_commit_with_parents(self):
  621. r = self._repo
  622. old_shas = set(r.object_store)
  623. old_refs = r.get_refs()
  624. commit_sha = r.do_commit(b'commit with no ref',
  625. committer=b'Test Committer <test@nodomain.com>',
  626. author=b'Test Author <test@nodomain.com>',
  627. commit_timestamp=12395, commit_timezone=0,
  628. author_timestamp=12395, author_timezone=0,
  629. ref=None, merge_heads=[self._root_commit])
  630. new_shas = set(r.object_store) - old_shas
  631. # New sha is added, but no new refs
  632. self.assertEqual(1, len(new_shas))
  633. new_commit = r[new_shas.pop()]
  634. self.assertEqual(r[self._root_commit].tree, new_commit.tree)
  635. self.assertEqual([self._root_commit], r[commit_sha].parents)
  636. self.assertEqual(old_refs, r.get_refs())
  637. def test_stage_deleted(self):
  638. r = self._repo
  639. os.remove(os.path.join(r.path, 'a'))
  640. r.stage(['a'])
  641. r.stage(['a']) # double-stage a deleted path