test_porcelain.py 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875
  1. # test_porcelain.py -- porcelain tests
  2. # Copyright (C) 2013 Jelmer Vernooij <jelmer@jelmer.uk>
  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 dulwich.porcelain."""
  21. from io import BytesIO
  22. try:
  23. from StringIO import StringIO
  24. except ImportError:
  25. from io import StringIO
  26. import errno
  27. import os
  28. import shutil
  29. import tarfile
  30. import tempfile
  31. import time
  32. from dulwich import porcelain
  33. from dulwich.diff_tree import tree_changes
  34. from dulwich.objects import (
  35. Blob,
  36. Tag,
  37. Tree,
  38. ZERO_SHA,
  39. )
  40. from dulwich.repo import (
  41. NoIndexPresent,
  42. Repo,
  43. )
  44. from dulwich.tests import (
  45. TestCase,
  46. )
  47. from dulwich.tests.utils import (
  48. build_commit_graph,
  49. make_commit,
  50. make_object,
  51. )
  52. from dulwich.tests.compat.utils import (
  53. run_git_or_fail,
  54. )
  55. def flat_walk_dir(dir_to_walk):
  56. for dirpath, _, filenames in os.walk(dir_to_walk):
  57. rel_dirpath = os.path.relpath(dirpath, dir_to_walk)
  58. if not dirpath == dir_to_walk:
  59. yield rel_dirpath
  60. for filename in filenames:
  61. if dirpath == dir_to_walk:
  62. yield filename
  63. else:
  64. yield os.path.join(rel_dirpath, filename)
  65. class PorcelainTestCase(TestCase):
  66. def setUp(self):
  67. super(PorcelainTestCase, self).setUp()
  68. self.test_dir = tempfile.mkdtemp()
  69. self.addCleanup(shutil.rmtree, self.test_dir)
  70. self.repo_path = os.path.join(self.test_dir, 'repo')
  71. self.repo = Repo.init(self.repo_path, mkdir=True)
  72. self.addCleanup(self.repo.close)
  73. class ArchiveTests(PorcelainTestCase):
  74. """Tests for the archive command."""
  75. def test_simple(self):
  76. c1, c2, c3 = build_commit_graph(
  77. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  78. self.repo.refs[b"refs/heads/master"] = c3.id
  79. out = BytesIO()
  80. err = BytesIO()
  81. porcelain.archive(self.repo.path, b"refs/heads/master", outstream=out,
  82. errstream=err)
  83. self.assertEqual(b"", err.getvalue())
  84. tf = tarfile.TarFile(fileobj=out)
  85. self.addCleanup(tf.close)
  86. self.assertEqual([], tf.getnames())
  87. class UpdateServerInfoTests(PorcelainTestCase):
  88. def test_simple(self):
  89. c1, c2, c3 = build_commit_graph(
  90. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  91. self.repo.refs[b"refs/heads/foo"] = c3.id
  92. porcelain.update_server_info(self.repo.path)
  93. self.assertTrue(os.path.exists(
  94. os.path.join(self.repo.controldir(), 'info', 'refs')))
  95. class CommitTests(PorcelainTestCase):
  96. def test_custom_author(self):
  97. c1, c2, c3 = build_commit_graph(
  98. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  99. self.repo.refs[b"refs/heads/foo"] = c3.id
  100. sha = porcelain.commit(
  101. self.repo.path, message=b"Some message",
  102. author=b"Joe <joe@example.com>",
  103. committer=b"Bob <bob@example.com>")
  104. self.assertTrue(isinstance(sha, bytes))
  105. self.assertEqual(len(sha), 40)
  106. def test_unicode(self):
  107. c1, c2, c3 = build_commit_graph(
  108. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  109. self.repo.refs[b"refs/heads/foo"] = c3.id
  110. sha = porcelain.commit(
  111. self.repo.path, message="Some message",
  112. author="Joe <joe@example.com>",
  113. committer="Bob <bob@example.com>")
  114. self.assertTrue(isinstance(sha, bytes))
  115. self.assertEqual(len(sha), 40)
  116. class CleanTests(PorcelainTestCase):
  117. def put_files(self, tracked, ignored, untracked, empty_dirs):
  118. """Put the described files in the wd
  119. """
  120. all_files = tracked | ignored | untracked
  121. for file_path in all_files:
  122. abs_path = os.path.join(self.repo.path, file_path)
  123. # File may need to be written in a dir that doesn't exist yet, so
  124. # create the parent dir(s) as necessary
  125. parent_dir = os.path.dirname(abs_path)
  126. try:
  127. os.makedirs(parent_dir)
  128. except OSError as err:
  129. if not err.errno == errno.EEXIST:
  130. raise err
  131. with open(abs_path, 'w') as f:
  132. f.write('')
  133. with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f:
  134. f.writelines(ignored)
  135. for dir_path in empty_dirs:
  136. os.mkdir(os.path.join(self.repo.path, 'empty_dir'))
  137. files_to_add = [os.path.join(self.repo.path, t) for t in tracked]
  138. porcelain.add(repo=self.repo.path, paths=files_to_add)
  139. porcelain.commit(repo=self.repo.path, message="init commit")
  140. def assert_wd(self, expected_paths):
  141. """Assert paths of files and dirs in wd are same as expected_paths
  142. """
  143. control_dir_rel = os.path.relpath(
  144. self.repo._controldir, self.repo.path)
  145. # normalize paths to simplify comparison across platforms
  146. found_paths = {
  147. os.path.normpath(p)
  148. for p in flat_walk_dir(self.repo.path)
  149. if not p.split(os.sep)[0] == control_dir_rel}
  150. norm_expected_paths = {os.path.normpath(p) for p in expected_paths}
  151. self.assertEqual(found_paths, norm_expected_paths)
  152. def test_from_root(self):
  153. self.put_files(
  154. tracked={
  155. 'tracked_file',
  156. 'tracked_dir/tracked_file',
  157. '.gitignore'},
  158. ignored={
  159. 'ignored_file'},
  160. untracked={
  161. 'untracked_file',
  162. 'tracked_dir/untracked_dir/untracked_file',
  163. 'untracked_dir/untracked_dir/untracked_file'},
  164. empty_dirs={
  165. 'empty_dir'})
  166. porcelain.clean(repo=self.repo.path, target_dir=self.repo.path)
  167. self.assert_wd({
  168. 'tracked_file',
  169. 'tracked_dir/tracked_file',
  170. '.gitignore',
  171. 'ignored_file',
  172. 'tracked_dir'})
  173. def test_from_subdir(self):
  174. self.put_files(
  175. tracked={
  176. 'tracked_file',
  177. 'tracked_dir/tracked_file',
  178. '.gitignore'},
  179. ignored={
  180. 'ignored_file'},
  181. untracked={
  182. 'untracked_file',
  183. 'tracked_dir/untracked_dir/untracked_file',
  184. 'untracked_dir/untracked_dir/untracked_file'},
  185. empty_dirs={
  186. 'empty_dir'})
  187. porcelain.clean(
  188. repo=self.repo,
  189. target_dir=os.path.join(self.repo.path, 'untracked_dir'))
  190. self.assert_wd({
  191. 'tracked_file',
  192. 'tracked_dir/tracked_file',
  193. '.gitignore',
  194. 'ignored_file',
  195. 'untracked_file',
  196. 'tracked_dir/untracked_dir/untracked_file',
  197. 'empty_dir',
  198. 'untracked_dir',
  199. 'tracked_dir',
  200. 'tracked_dir/untracked_dir'})
  201. class CloneTests(PorcelainTestCase):
  202. def test_simple_local(self):
  203. f1_1 = make_object(Blob, data=b'f1')
  204. commit_spec = [[1], [2, 1], [3, 1, 2]]
  205. trees = {1: [(b'f1', f1_1), (b'f2', f1_1)],
  206. 2: [(b'f1', f1_1), (b'f2', f1_1)],
  207. 3: [(b'f1', f1_1), (b'f2', f1_1)], }
  208. c1, c2, c3 = build_commit_graph(self.repo.object_store,
  209. commit_spec, trees)
  210. self.repo.refs[b"refs/heads/master"] = c3.id
  211. self.repo.refs[b"refs/tags/foo"] = c3.id
  212. target_path = tempfile.mkdtemp()
  213. errstream = BytesIO()
  214. self.addCleanup(shutil.rmtree, target_path)
  215. r = porcelain.clone(self.repo.path, target_path,
  216. checkout=False, errstream=errstream)
  217. self.addCleanup(r.close)
  218. self.assertEqual(r.path, target_path)
  219. target_repo = Repo(target_path)
  220. self.assertEqual(0, len(target_repo.open_index()))
  221. self.assertEqual(c3.id, target_repo.refs[b'refs/tags/foo'])
  222. self.assertTrue(b'f1' not in os.listdir(target_path))
  223. self.assertTrue(b'f2' not in os.listdir(target_path))
  224. c = r.get_config()
  225. encoded_path = self.repo.path
  226. if not isinstance(encoded_path, bytes):
  227. encoded_path = encoded_path.encode('utf-8')
  228. self.assertEqual(encoded_path, c.get((b'remote', b'origin'), b'url'))
  229. self.assertEqual(
  230. b'+refs/heads/*:refs/remotes/origin/*',
  231. c.get((b'remote', b'origin'), b'fetch'))
  232. def test_simple_local_with_checkout(self):
  233. f1_1 = make_object(Blob, data=b'f1')
  234. commit_spec = [[1], [2, 1], [3, 1, 2]]
  235. trees = {1: [(b'f1', f1_1), (b'f2', f1_1)],
  236. 2: [(b'f1', f1_1), (b'f2', f1_1)],
  237. 3: [(b'f1', f1_1), (b'f2', f1_1)], }
  238. c1, c2, c3 = build_commit_graph(self.repo.object_store,
  239. commit_spec, trees)
  240. self.repo.refs[b"refs/heads/master"] = c3.id
  241. target_path = tempfile.mkdtemp()
  242. errstream = BytesIO()
  243. self.addCleanup(shutil.rmtree, target_path)
  244. with porcelain.clone(self.repo.path, target_path,
  245. checkout=True,
  246. errstream=errstream) as r:
  247. self.assertEqual(r.path, target_path)
  248. with Repo(target_path) as r:
  249. self.assertEqual(r.head(), c3.id)
  250. self.assertTrue('f1' in os.listdir(target_path))
  251. self.assertTrue('f2' in os.listdir(target_path))
  252. def test_bare_local_with_checkout(self):
  253. f1_1 = make_object(Blob, data=b'f1')
  254. commit_spec = [[1], [2, 1], [3, 1, 2]]
  255. trees = {1: [(b'f1', f1_1), (b'f2', f1_1)],
  256. 2: [(b'f1', f1_1), (b'f2', f1_1)],
  257. 3: [(b'f1', f1_1), (b'f2', f1_1)], }
  258. c1, c2, c3 = build_commit_graph(self.repo.object_store,
  259. commit_spec, trees)
  260. self.repo.refs[b"refs/heads/master"] = c3.id
  261. target_path = tempfile.mkdtemp()
  262. errstream = BytesIO()
  263. self.addCleanup(shutil.rmtree, target_path)
  264. with porcelain.clone(
  265. self.repo.path, target_path, bare=True,
  266. errstream=errstream) as r:
  267. self.assertEqual(r.path, target_path)
  268. with Repo(target_path) as r:
  269. r.head()
  270. self.assertRaises(NoIndexPresent, r.open_index)
  271. self.assertFalse(b'f1' in os.listdir(target_path))
  272. self.assertFalse(b'f2' in os.listdir(target_path))
  273. def test_no_checkout_with_bare(self):
  274. f1_1 = make_object(Blob, data=b'f1')
  275. commit_spec = [[1]]
  276. trees = {1: [(b'f1', f1_1), (b'f2', f1_1)]}
  277. (c1, ) = build_commit_graph(self.repo.object_store, commit_spec, trees)
  278. self.repo.refs[b"refs/heads/master"] = c1.id
  279. self.repo.refs[b"HEAD"] = c1.id
  280. target_path = tempfile.mkdtemp()
  281. errstream = BytesIO()
  282. self.addCleanup(shutil.rmtree, target_path)
  283. self.assertRaises(
  284. ValueError, porcelain.clone, self.repo.path,
  285. target_path, checkout=True, bare=True, errstream=errstream)
  286. def test_no_head_no_checkout(self):
  287. f1_1 = make_object(Blob, data=b'f1')
  288. commit_spec = [[1]]
  289. trees = {1: [(b'f1', f1_1), (b'f2', f1_1)]}
  290. (c1, ) = build_commit_graph(self.repo.object_store, commit_spec, trees)
  291. self.repo.refs[b"refs/heads/master"] = c1.id
  292. target_path = tempfile.mkdtemp()
  293. self.addCleanup(shutil.rmtree, target_path)
  294. errstream = BytesIO()
  295. r = porcelain.clone(
  296. self.repo.path, target_path, checkout=True, errstream=errstream)
  297. r.close()
  298. def test_no_head_no_checkout_outstream_errstream_autofallback(self):
  299. f1_1 = make_object(Blob, data=b'f1')
  300. commit_spec = [[1]]
  301. trees = {1: [(b'f1', f1_1), (b'f2', f1_1)]}
  302. (c1, ) = build_commit_graph(self.repo.object_store, commit_spec, trees)
  303. self.repo.refs[b"refs/heads/master"] = c1.id
  304. target_path = tempfile.mkdtemp()
  305. self.addCleanup(shutil.rmtree, target_path)
  306. errstream = porcelain.NoneStream()
  307. r = porcelain.clone(
  308. self.repo.path, target_path, checkout=True, errstream=errstream)
  309. r.close()
  310. class InitTests(TestCase):
  311. def test_non_bare(self):
  312. repo_dir = tempfile.mkdtemp()
  313. self.addCleanup(shutil.rmtree, repo_dir)
  314. porcelain.init(repo_dir)
  315. def test_bare(self):
  316. repo_dir = tempfile.mkdtemp()
  317. self.addCleanup(shutil.rmtree, repo_dir)
  318. porcelain.init(repo_dir, bare=True)
  319. class AddTests(PorcelainTestCase):
  320. def test_add_default_paths(self):
  321. # create a file for initial commit
  322. fullpath = os.path.join(self.repo.path, 'blah')
  323. with open(fullpath, 'w') as f:
  324. f.write("\n")
  325. porcelain.add(repo=self.repo.path, paths=[fullpath])
  326. porcelain.commit(repo=self.repo.path, message=b'test',
  327. author=b'test <email>', committer=b'test <email>')
  328. # Add a second test file and a file in a directory
  329. with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
  330. f.write("\n")
  331. os.mkdir(os.path.join(self.repo.path, 'adir'))
  332. with open(os.path.join(self.repo.path, 'adir', 'afile'), 'w') as f:
  333. f.write("\n")
  334. cwd = os.getcwd()
  335. try:
  336. os.chdir(self.repo.path)
  337. porcelain.add(self.repo.path)
  338. finally:
  339. os.chdir(cwd)
  340. # Check that foo was added and nothing in .git was modified
  341. index = self.repo.open_index()
  342. self.assertEqual(sorted(index), [b'adir/afile', b'blah', b'foo'])
  343. def test_add_default_paths_subdir(self):
  344. os.mkdir(os.path.join(self.repo.path, 'foo'))
  345. with open(os.path.join(self.repo.path, 'blah'), 'w') as f:
  346. f.write("\n")
  347. with open(os.path.join(self.repo.path, 'foo', 'blie'), 'w') as f:
  348. f.write("\n")
  349. cwd = os.getcwd()
  350. try:
  351. os.chdir(os.path.join(self.repo.path, 'foo'))
  352. porcelain.add(repo=self.repo.path)
  353. porcelain.commit(repo=self.repo.path, message=b'test',
  354. author=b'test <email>',
  355. committer=b'test <email>')
  356. finally:
  357. os.chdir(cwd)
  358. index = self.repo.open_index()
  359. self.assertEqual(sorted(index), [b'foo/blie'])
  360. def test_add_file(self):
  361. fullpath = os.path.join(self.repo.path, 'foo')
  362. with open(fullpath, 'w') as f:
  363. f.write("BAR")
  364. porcelain.add(self.repo.path, paths=[fullpath])
  365. self.assertIn(b"foo", self.repo.open_index())
  366. def test_add_ignored(self):
  367. with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f:
  368. f.write("foo")
  369. with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
  370. f.write("BAR")
  371. with open(os.path.join(self.repo.path, 'bar'), 'w') as f:
  372. f.write("BAR")
  373. (added, ignored) = porcelain.add(self.repo.path, paths=[
  374. os.path.join(self.repo.path, "foo"),
  375. os.path.join(self.repo.path, "bar")])
  376. self.assertIn(b"bar", self.repo.open_index())
  377. self.assertEqual(set(['bar']), set(added))
  378. self.assertEqual(set(['foo']), ignored)
  379. def test_add_file_absolute_path(self):
  380. # Absolute paths are (not yet) supported
  381. with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
  382. f.write("BAR")
  383. porcelain.add(self.repo, paths=[os.path.join(self.repo.path, "foo")])
  384. self.assertIn(b"foo", self.repo.open_index())
  385. def test_add_not_in_repo(self):
  386. with open(os.path.join(self.test_dir, 'foo'), 'w') as f:
  387. f.write("BAR")
  388. self.assertRaises(
  389. ValueError,
  390. porcelain.add, self.repo,
  391. paths=[os.path.join(self.test_dir, "foo")])
  392. self.assertRaises(
  393. ValueError,
  394. porcelain.add, self.repo,
  395. paths=["../foo"])
  396. self.assertEqual([], list(self.repo.open_index()))
  397. def test_add_file_clrf_conversion(self):
  398. # Set the right configuration to the repo
  399. c = self.repo.get_config()
  400. c.set("core", "autocrlf", "input")
  401. c.write_to_path()
  402. # Add a file with CRLF line-ending
  403. fullpath = os.path.join(self.repo.path, 'foo')
  404. with open(fullpath, 'wb') as f:
  405. f.write(b"line1\r\nline2")
  406. porcelain.add(self.repo.path, paths=[fullpath])
  407. # The line-endings should have been converted to LF
  408. index = self.repo.open_index()
  409. self.assertIn(b"foo", index)
  410. entry = index[b"foo"]
  411. blob = self.repo[entry.sha]
  412. self.assertEqual(blob.data, b"line1\nline2")
  413. class RemoveTests(PorcelainTestCase):
  414. def test_remove_file(self):
  415. fullpath = os.path.join(self.repo.path, 'foo')
  416. with open(fullpath, 'w') as f:
  417. f.write("BAR")
  418. porcelain.add(self.repo.path, paths=[fullpath])
  419. porcelain.commit(repo=self.repo, message=b'test',
  420. author=b'test <email>',
  421. committer=b'test <email>')
  422. self.assertTrue(os.path.exists(os.path.join(self.repo.path, 'foo')))
  423. cwd = os.getcwd()
  424. try:
  425. os.chdir(self.repo.path)
  426. porcelain.remove(self.repo.path, paths=["foo"])
  427. finally:
  428. os.chdir(cwd)
  429. self.assertFalse(os.path.exists(os.path.join(self.repo.path, 'foo')))
  430. def test_remove_file_staged(self):
  431. fullpath = os.path.join(self.repo.path, 'foo')
  432. with open(fullpath, 'w') as f:
  433. f.write("BAR")
  434. cwd = os.getcwd()
  435. try:
  436. os.chdir(self.repo.path)
  437. porcelain.add(self.repo.path, paths=[fullpath])
  438. self.assertRaises(Exception, porcelain.rm, self.repo.path,
  439. paths=["foo"])
  440. finally:
  441. os.chdir(cwd)
  442. class LogTests(PorcelainTestCase):
  443. def test_simple(self):
  444. c1, c2, c3 = build_commit_graph(
  445. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  446. self.repo.refs[b"HEAD"] = c3.id
  447. outstream = StringIO()
  448. porcelain.log(self.repo.path, outstream=outstream)
  449. self.assertEqual(3, outstream.getvalue().count("-" * 50))
  450. def test_max_entries(self):
  451. c1, c2, c3 = build_commit_graph(
  452. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  453. self.repo.refs[b"HEAD"] = c3.id
  454. outstream = StringIO()
  455. porcelain.log(self.repo.path, outstream=outstream, max_entries=1)
  456. self.assertEqual(1, outstream.getvalue().count("-" * 50))
  457. class ShowTests(PorcelainTestCase):
  458. def test_nolist(self):
  459. c1, c2, c3 = build_commit_graph(
  460. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  461. self.repo.refs[b"HEAD"] = c3.id
  462. outstream = StringIO()
  463. porcelain.show(self.repo.path, objects=c3.id, outstream=outstream)
  464. self.assertTrue(outstream.getvalue().startswith("-" * 50))
  465. def test_simple(self):
  466. c1, c2, c3 = build_commit_graph(
  467. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  468. self.repo.refs[b"HEAD"] = c3.id
  469. outstream = StringIO()
  470. porcelain.show(self.repo.path, objects=[c3.id], outstream=outstream)
  471. self.assertTrue(outstream.getvalue().startswith("-" * 50))
  472. def test_blob(self):
  473. b = Blob.from_string(b"The Foo\n")
  474. self.repo.object_store.add_object(b)
  475. outstream = StringIO()
  476. porcelain.show(self.repo.path, objects=[b.id], outstream=outstream)
  477. self.assertEqual(outstream.getvalue(), "The Foo\n")
  478. def test_commit_no_parent(self):
  479. a = Blob.from_string(b"The Foo\n")
  480. ta = Tree()
  481. ta.add(b"somename", 0o100644, a.id)
  482. ca = make_commit(tree=ta.id)
  483. self.repo.object_store.add_objects([(a, None), (ta, None), (ca, None)])
  484. outstream = StringIO()
  485. porcelain.show(self.repo.path, objects=[ca.id], outstream=outstream)
  486. self.assertMultiLineEqual(outstream.getvalue(), """\
  487. --------------------------------------------------
  488. commit: 344da06c1bb85901270b3e8875c988a027ec087d
  489. Author: Test Author <test@nodomain.com>
  490. Committer: Test Committer <test@nodomain.com>
  491. Date: Fri Jan 01 2010 00:00:00 +0000
  492. Test message.
  493. diff --git a/somename b/somename
  494. new file mode 100644
  495. index 0000000..ea5c7bf
  496. --- /dev/null
  497. +++ b/somename
  498. @@ -0,0 +1 @@
  499. +The Foo
  500. """)
  501. def test_tag(self):
  502. a = Blob.from_string(b"The Foo\n")
  503. ta = Tree()
  504. ta.add(b"somename", 0o100644, a.id)
  505. ca = make_commit(tree=ta.id)
  506. self.repo.object_store.add_objects([(a, None), (ta, None), (ca, None)])
  507. porcelain.tag_create(
  508. self.repo.path, b"tryme", b'foo <foo@bar.com>', b'bar',
  509. annotated=True, objectish=ca.id, tag_time=1552854211,
  510. tag_timezone=0)
  511. outstream = StringIO()
  512. porcelain.show(self.repo, objects=[b'refs/tags/tryme'],
  513. outstream=outstream)
  514. self.maxDiff = None
  515. self.assertMultiLineEqual(outstream.getvalue(), """\
  516. Tagger: foo <foo@bar.com>
  517. Date: Sun Mar 17 2019 20:23:31 +0000
  518. bar
  519. --------------------------------------------------
  520. commit: 344da06c1bb85901270b3e8875c988a027ec087d
  521. Author: Test Author <test@nodomain.com>
  522. Committer: Test Committer <test@nodomain.com>
  523. Date: Fri Jan 01 2010 00:00:00 +0000
  524. Test message.
  525. diff --git a/somename b/somename
  526. new file mode 100644
  527. index 0000000..ea5c7bf
  528. --- /dev/null
  529. +++ b/somename
  530. @@ -0,0 +1 @@
  531. +The Foo
  532. """)
  533. def test_commit_with_change(self):
  534. a = Blob.from_string(b"The Foo\n")
  535. ta = Tree()
  536. ta.add(b"somename", 0o100644, a.id)
  537. ca = make_commit(tree=ta.id)
  538. b = Blob.from_string(b"The Bar\n")
  539. tb = Tree()
  540. tb.add(b"somename", 0o100644, b.id)
  541. cb = make_commit(tree=tb.id, parents=[ca.id])
  542. self.repo.object_store.add_objects(
  543. [(a, None), (b, None), (ta, None), (tb, None),
  544. (ca, None), (cb, None)])
  545. outstream = StringIO()
  546. porcelain.show(self.repo.path, objects=[cb.id], outstream=outstream)
  547. self.assertMultiLineEqual(outstream.getvalue(), """\
  548. --------------------------------------------------
  549. commit: 2c6b6c9cb72c130956657e1fdae58e5b103744fa
  550. Author: Test Author <test@nodomain.com>
  551. Committer: Test Committer <test@nodomain.com>
  552. Date: Fri Jan 01 2010 00:00:00 +0000
  553. Test message.
  554. diff --git a/somename b/somename
  555. index ea5c7bf..fd38bcb 100644
  556. --- a/somename
  557. +++ b/somename
  558. @@ -1 +1 @@
  559. -The Foo
  560. +The Bar
  561. """)
  562. class SymbolicRefTests(PorcelainTestCase):
  563. def test_set_wrong_symbolic_ref(self):
  564. c1, c2, c3 = build_commit_graph(
  565. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  566. self.repo.refs[b"HEAD"] = c3.id
  567. self.assertRaises(ValueError, porcelain.symbolic_ref, self.repo.path,
  568. b'foobar')
  569. def test_set_force_wrong_symbolic_ref(self):
  570. c1, c2, c3 = build_commit_graph(
  571. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  572. self.repo.refs[b"HEAD"] = c3.id
  573. porcelain.symbolic_ref(self.repo.path, b'force_foobar', force=True)
  574. # test if we actually changed the file
  575. with self.repo.get_named_file('HEAD') as f:
  576. new_ref = f.read()
  577. self.assertEqual(new_ref, b'ref: refs/heads/force_foobar\n')
  578. def test_set_symbolic_ref(self):
  579. c1, c2, c3 = build_commit_graph(
  580. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  581. self.repo.refs[b"HEAD"] = c3.id
  582. porcelain.symbolic_ref(self.repo.path, b'master')
  583. def test_set_symbolic_ref_other_than_master(self):
  584. c1, c2, c3 = build_commit_graph(
  585. self.repo.object_store, [[1], [2, 1], [3, 1, 2]],
  586. attrs=dict(refs='develop'))
  587. self.repo.refs[b"HEAD"] = c3.id
  588. self.repo.refs[b"refs/heads/develop"] = c3.id
  589. porcelain.symbolic_ref(self.repo.path, b'develop')
  590. # test if we actually changed the file
  591. with self.repo.get_named_file('HEAD') as f:
  592. new_ref = f.read()
  593. self.assertEqual(new_ref, b'ref: refs/heads/develop\n')
  594. class DiffTreeTests(PorcelainTestCase):
  595. def test_empty(self):
  596. c1, c2, c3 = build_commit_graph(
  597. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  598. self.repo.refs[b"HEAD"] = c3.id
  599. outstream = BytesIO()
  600. porcelain.diff_tree(self.repo.path, c2.tree, c3.tree,
  601. outstream=outstream)
  602. self.assertEqual(outstream.getvalue(), b"")
  603. def test_diff_apply(self):
  604. # Prepare the repository
  605. # Create some files and commit them
  606. file_list = ["to_exists", "to_modify", "to_delete"]
  607. for file in file_list:
  608. file_path = os.path.join(self.repo_path, file)
  609. # Touch the files
  610. with open(file_path, "w"):
  611. pass
  612. self.repo.stage(file_list)
  613. first_commit = self.repo.do_commit(b"The first commit")
  614. # Make a copy of the repository so we can apply the diff later
  615. copy_path = os.path.join(self.test_dir, "copy")
  616. shutil.copytree(self.repo_path, copy_path)
  617. # Do some changes
  618. with open(os.path.join(self.repo_path, "to_modify"), "w") as f:
  619. f.write("Modified!")
  620. os.remove(os.path.join(self.repo_path, "to_delete"))
  621. with open(os.path.join(self.repo_path, "to_add"), "w"):
  622. pass
  623. self.repo.stage(["to_modify", "to_delete", "to_add"])
  624. second_commit = self.repo.do_commit(b"The second commit")
  625. # Get the patch
  626. first_tree = self.repo[first_commit].tree
  627. second_tree = self.repo[second_commit].tree
  628. outstream = BytesIO()
  629. porcelain.diff_tree(self.repo.path, first_tree, second_tree,
  630. outstream=outstream)
  631. # Save it on disk
  632. patch_path = os.path.join(self.test_dir, "patch.patch")
  633. with open(patch_path, "wb") as patch:
  634. patch.write(outstream.getvalue())
  635. # And try to apply it to the copy directory
  636. git_command = ["-C", copy_path, "apply", patch_path]
  637. run_git_or_fail(git_command)
  638. # And now check that the files contents are exactly the same between
  639. # the two repositories
  640. original_files = set(os.listdir(self.repo_path))
  641. new_files = set(os.listdir(copy_path))
  642. # Check that we have the exact same files in both repositories
  643. assert original_files == new_files
  644. for file in original_files:
  645. if file == ".git":
  646. continue
  647. original_file_path = os.path.join(self.repo_path, file)
  648. copy_file_path = os.path.join(copy_path, file)
  649. assert os.path.isfile(copy_file_path)
  650. with open(original_file_path, "rb") as original_file:
  651. original_content = original_file.read()
  652. with open(copy_file_path, "rb") as copy_file:
  653. copy_content = copy_file.read()
  654. assert original_content == copy_content
  655. class CommitTreeTests(PorcelainTestCase):
  656. def test_simple(self):
  657. c1, c2, c3 = build_commit_graph(
  658. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  659. b = Blob()
  660. b.data = b"foo the bar"
  661. t = Tree()
  662. t.add(b"somename", 0o100644, b.id)
  663. self.repo.object_store.add_object(t)
  664. self.repo.object_store.add_object(b)
  665. sha = porcelain.commit_tree(
  666. self.repo.path, t.id, message=b"Withcommit.",
  667. author=b"Joe <joe@example.com>",
  668. committer=b"Jane <jane@example.com>")
  669. self.assertTrue(isinstance(sha, bytes))
  670. self.assertEqual(len(sha), 40)
  671. class RevListTests(PorcelainTestCase):
  672. def test_simple(self):
  673. c1, c2, c3 = build_commit_graph(
  674. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  675. outstream = BytesIO()
  676. porcelain.rev_list(
  677. self.repo.path, [c3.id], outstream=outstream)
  678. self.assertEqual(
  679. c3.id + b"\n" +
  680. c2.id + b"\n" +
  681. c1.id + b"\n",
  682. outstream.getvalue())
  683. class TagCreateTests(PorcelainTestCase):
  684. def test_annotated(self):
  685. c1, c2, c3 = build_commit_graph(
  686. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  687. self.repo.refs[b"HEAD"] = c3.id
  688. porcelain.tag_create(self.repo.path, b"tryme", b'foo <foo@bar.com>',
  689. b'bar', annotated=True)
  690. tags = self.repo.refs.as_dict(b"refs/tags")
  691. self.assertEqual(list(tags.keys()), [b"tryme"])
  692. tag = self.repo[b'refs/tags/tryme']
  693. self.assertTrue(isinstance(tag, Tag))
  694. self.assertEqual(b"foo <foo@bar.com>", tag.tagger)
  695. self.assertEqual(b"bar", tag.message)
  696. self.assertLess(time.time() - tag.tag_time, 5)
  697. def test_unannotated(self):
  698. c1, c2, c3 = build_commit_graph(
  699. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  700. self.repo.refs[b"HEAD"] = c3.id
  701. porcelain.tag_create(self.repo.path, b"tryme", annotated=False)
  702. tags = self.repo.refs.as_dict(b"refs/tags")
  703. self.assertEqual(list(tags.keys()), [b"tryme"])
  704. self.repo[b'refs/tags/tryme']
  705. self.assertEqual(list(tags.values()), [self.repo.head()])
  706. def test_unannotated_unicode(self):
  707. c1, c2, c3 = build_commit_graph(
  708. self.repo.object_store, [[1], [2, 1], [3, 1, 2]])
  709. self.repo.refs[b"HEAD"] = c3.id
  710. porcelain.tag_create(self.repo.path, "tryme", annotated=False)
  711. tags = self.repo.refs.as_dict(b"refs/tags")
  712. self.assertEqual(list(tags.keys()), [b"tryme"])
  713. self.repo[b'refs/tags/tryme']
  714. self.assertEqual(list(tags.values()), [self.repo.head()])
  715. class TagListTests(PorcelainTestCase):
  716. def test_empty(self):
  717. tags = porcelain.tag_list(self.repo.path)
  718. self.assertEqual([], tags)
  719. def test_simple(self):
  720. self.repo.refs[b"refs/tags/foo"] = b"aa" * 20
  721. self.repo.refs[b"refs/tags/bar/bla"] = b"bb" * 20
  722. tags = porcelain.tag_list(self.repo.path)
  723. self.assertEqual([b"bar/bla", b"foo"], tags)
  724. class TagDeleteTests(PorcelainTestCase):
  725. def test_simple(self):
  726. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  727. self.repo[b"HEAD"] = c1.id
  728. porcelain.tag_create(self.repo, b'foo')
  729. self.assertTrue(b"foo" in porcelain.tag_list(self.repo))
  730. porcelain.tag_delete(self.repo, b'foo')
  731. self.assertFalse(b"foo" in porcelain.tag_list(self.repo))
  732. class ResetTests(PorcelainTestCase):
  733. def test_hard_head(self):
  734. fullpath = os.path.join(self.repo.path, 'foo')
  735. with open(fullpath, 'w') as f:
  736. f.write("BAR")
  737. porcelain.add(self.repo.path, paths=[fullpath])
  738. porcelain.commit(self.repo.path, message=b"Some message",
  739. committer=b"Jane <jane@example.com>",
  740. author=b"John <john@example.com>")
  741. with open(os.path.join(self.repo.path, 'foo'), 'wb') as f:
  742. f.write(b"OOH")
  743. porcelain.reset(self.repo, "hard", b"HEAD")
  744. index = self.repo.open_index()
  745. changes = list(tree_changes(self.repo,
  746. index.commit(self.repo.object_store),
  747. self.repo[b'HEAD'].tree))
  748. self.assertEqual([], changes)
  749. def test_hard_commit(self):
  750. fullpath = os.path.join(self.repo.path, 'foo')
  751. with open(fullpath, 'w') as f:
  752. f.write("BAR")
  753. porcelain.add(self.repo.path, paths=[fullpath])
  754. sha = porcelain.commit(self.repo.path, message=b"Some message",
  755. committer=b"Jane <jane@example.com>",
  756. author=b"John <john@example.com>")
  757. with open(fullpath, 'wb') as f:
  758. f.write(b"BAZ")
  759. porcelain.add(self.repo.path, paths=[fullpath])
  760. porcelain.commit(self.repo.path, message=b"Some other message",
  761. committer=b"Jane <jane@example.com>",
  762. author=b"John <john@example.com>")
  763. porcelain.reset(self.repo, "hard", sha)
  764. index = self.repo.open_index()
  765. changes = list(tree_changes(self.repo,
  766. index.commit(self.repo.object_store),
  767. self.repo[sha].tree))
  768. self.assertEqual([], changes)
  769. class PushTests(PorcelainTestCase):
  770. def test_simple(self):
  771. """
  772. Basic test of porcelain push where self.repo is the remote. First
  773. clone the remote, commit a file to the clone, then push the changes
  774. back to the remote.
  775. """
  776. outstream = BytesIO()
  777. errstream = BytesIO()
  778. porcelain.commit(repo=self.repo.path, message=b'init',
  779. author=b'author <email>',
  780. committer=b'committer <email>')
  781. # Setup target repo cloned from temp test repo
  782. clone_path = tempfile.mkdtemp()
  783. self.addCleanup(shutil.rmtree, clone_path)
  784. target_repo = porcelain.clone(self.repo.path, target=clone_path,
  785. errstream=errstream)
  786. try:
  787. self.assertEqual(target_repo[b'HEAD'], self.repo[b'HEAD'])
  788. finally:
  789. target_repo.close()
  790. # create a second file to be pushed back to origin
  791. handle, fullpath = tempfile.mkstemp(dir=clone_path)
  792. os.close(handle)
  793. porcelain.add(repo=clone_path, paths=[fullpath])
  794. porcelain.commit(repo=clone_path, message=b'push',
  795. author=b'author <email>',
  796. committer=b'committer <email>')
  797. # Setup a non-checked out branch in the remote
  798. refs_path = b"refs/heads/foo"
  799. new_id = self.repo[b'HEAD'].id
  800. self.assertNotEqual(new_id, ZERO_SHA)
  801. self.repo.refs[refs_path] = new_id
  802. # Push to the remote
  803. porcelain.push(clone_path, self.repo.path, b"HEAD:" + refs_path,
  804. outstream=outstream, errstream=errstream)
  805. # Check that the target and source
  806. with Repo(clone_path) as r_clone:
  807. self.assertEqual({
  808. b'HEAD': new_id,
  809. b'refs/heads/foo': r_clone[b'HEAD'].id,
  810. b'refs/heads/master': new_id,
  811. }, self.repo.get_refs())
  812. self.assertEqual(r_clone[b'HEAD'].id, self.repo[refs_path].id)
  813. # Get the change in the target repo corresponding to the add
  814. # this will be in the foo branch.
  815. change = list(tree_changes(self.repo, self.repo[b'HEAD'].tree,
  816. self.repo[b'refs/heads/foo'].tree))[0]
  817. self.assertEqual(os.path.basename(fullpath),
  818. change.new.path.decode('ascii'))
  819. def test_delete(self):
  820. """Basic test of porcelain push, removing a branch.
  821. """
  822. outstream = BytesIO()
  823. errstream = BytesIO()
  824. porcelain.commit(repo=self.repo.path, message=b'init',
  825. author=b'author <email>',
  826. committer=b'committer <email>')
  827. # Setup target repo cloned from temp test repo
  828. clone_path = tempfile.mkdtemp()
  829. self.addCleanup(shutil.rmtree, clone_path)
  830. target_repo = porcelain.clone(self.repo.path, target=clone_path,
  831. errstream=errstream)
  832. target_repo.close()
  833. # Setup a non-checked out branch in the remote
  834. refs_path = b"refs/heads/foo"
  835. new_id = self.repo[b'HEAD'].id
  836. self.assertNotEqual(new_id, ZERO_SHA)
  837. self.repo.refs[refs_path] = new_id
  838. # Push to the remote
  839. porcelain.push(clone_path, self.repo.path, b":" + refs_path,
  840. outstream=outstream, errstream=errstream)
  841. self.assertEqual({
  842. b'HEAD': new_id,
  843. b'refs/heads/master': new_id,
  844. }, self.repo.get_refs())
  845. class PullTests(PorcelainTestCase):
  846. def setUp(self):
  847. super(PullTests, self).setUp()
  848. # create a file for initial commit
  849. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  850. os.close(handle)
  851. porcelain.add(repo=self.repo.path, paths=fullpath)
  852. porcelain.commit(repo=self.repo.path, message=b'test',
  853. author=b'test <email>',
  854. committer=b'test <email>')
  855. # Setup target repo
  856. self.target_path = tempfile.mkdtemp()
  857. self.addCleanup(shutil.rmtree, self.target_path)
  858. target_repo = porcelain.clone(self.repo.path, target=self.target_path,
  859. errstream=BytesIO())
  860. target_repo.close()
  861. # create a second file to be pushed
  862. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  863. os.close(handle)
  864. porcelain.add(repo=self.repo.path, paths=fullpath)
  865. porcelain.commit(repo=self.repo.path, message=b'test2',
  866. author=b'test2 <email>',
  867. committer=b'test2 <email>')
  868. self.assertTrue(b'refs/heads/master' in self.repo.refs)
  869. self.assertTrue(b'refs/heads/master' in target_repo.refs)
  870. def test_simple(self):
  871. outstream = BytesIO()
  872. errstream = BytesIO()
  873. # Pull changes into the cloned repo
  874. porcelain.pull(self.target_path, self.repo.path, b'refs/heads/master',
  875. outstream=outstream, errstream=errstream)
  876. # Check the target repo for pushed changes
  877. with Repo(self.target_path) as r:
  878. self.assertEqual(r[b'HEAD'].id, self.repo[b'HEAD'].id)
  879. def test_no_refspec(self):
  880. outstream = BytesIO()
  881. errstream = BytesIO()
  882. # Pull changes into the cloned repo
  883. porcelain.pull(self.target_path, self.repo.path, outstream=outstream,
  884. errstream=errstream)
  885. # Check the target repo for pushed changes
  886. with Repo(self.target_path) as r:
  887. self.assertEqual(r[b'HEAD'].id, self.repo[b'HEAD'].id)
  888. class StatusTests(PorcelainTestCase):
  889. def test_empty(self):
  890. results = porcelain.status(self.repo)
  891. self.assertEqual(
  892. {'add': [], 'delete': [], 'modify': []},
  893. results.staged)
  894. self.assertEqual([], results.unstaged)
  895. def test_status_base(self):
  896. """Integration test for `status` functionality."""
  897. # Commit a dummy file then modify it
  898. fullpath = os.path.join(self.repo.path, 'foo')
  899. with open(fullpath, 'w') as f:
  900. f.write('origstuff')
  901. porcelain.add(repo=self.repo.path, paths=[fullpath])
  902. porcelain.commit(repo=self.repo.path, message=b'test status',
  903. author=b'author <email>',
  904. committer=b'committer <email>')
  905. # modify access and modify time of path
  906. os.utime(fullpath, (0, 0))
  907. with open(fullpath, 'wb') as f:
  908. f.write(b'stuff')
  909. # Make a dummy file and stage it
  910. filename_add = 'bar'
  911. fullpath = os.path.join(self.repo.path, filename_add)
  912. with open(fullpath, 'w') as f:
  913. f.write('stuff')
  914. porcelain.add(repo=self.repo.path, paths=fullpath)
  915. results = porcelain.status(self.repo)
  916. self.assertEqual(results.staged['add'][0],
  917. filename_add.encode('ascii'))
  918. self.assertEqual(results.unstaged, [b'foo'])
  919. def test_status_all(self):
  920. del_path = os.path.join(self.repo.path, 'foo')
  921. mod_path = os.path.join(self.repo.path, 'bar')
  922. add_path = os.path.join(self.repo.path, 'baz')
  923. us_path = os.path.join(self.repo.path, 'blye')
  924. ut_path = os.path.join(self.repo.path, 'blyat')
  925. with open(del_path, 'w') as f:
  926. f.write('origstuff')
  927. with open(mod_path, 'w') as f:
  928. f.write('origstuff')
  929. with open(us_path, 'w') as f:
  930. f.write('origstuff')
  931. porcelain.add(repo=self.repo.path, paths=[del_path, mod_path, us_path])
  932. porcelain.commit(repo=self.repo.path, message=b'test status',
  933. author=b'author <email>',
  934. committer=b'committer <email>')
  935. porcelain.remove(self.repo.path, [del_path])
  936. with open(add_path, 'w') as f:
  937. f.write('origstuff')
  938. with open(mod_path, 'w') as f:
  939. f.write('more_origstuff')
  940. with open(us_path, 'w') as f:
  941. f.write('more_origstuff')
  942. porcelain.add(repo=self.repo.path, paths=[add_path, mod_path])
  943. with open(us_path, 'w') as f:
  944. f.write('\norigstuff')
  945. with open(ut_path, 'w') as f:
  946. f.write('origstuff')
  947. results = porcelain.status(self.repo.path)
  948. self.assertDictEqual(
  949. {'add': [b'baz'], 'delete': [b'foo'], 'modify': [b'bar']},
  950. results.staged)
  951. self.assertListEqual(results.unstaged, [b'blye'])
  952. self.assertListEqual(results.untracked, ['blyat'])
  953. def test_status_crlf_mismatch(self):
  954. # First make a commit as if the file has been added on a Linux system
  955. # or with core.autocrlf=True
  956. file_path = os.path.join(self.repo.path, 'crlf')
  957. with open(file_path, 'wb') as f:
  958. f.write(b'line1\nline2')
  959. porcelain.add(repo=self.repo.path, paths=[file_path])
  960. porcelain.commit(repo=self.repo.path, message=b'test status',
  961. author=b'author <email>',
  962. committer=b'committer <email>')
  963. # Then update the file as if it was created by CGit on a Windows
  964. # system with core.autocrlf=true
  965. with open(file_path, 'wb') as f:
  966. f.write(b'line1\r\nline2')
  967. results = porcelain.status(self.repo)
  968. self.assertDictEqual(
  969. {'add': [], 'delete': [], 'modify': []},
  970. results.staged)
  971. self.assertListEqual(results.unstaged, [b'crlf'])
  972. self.assertListEqual(results.untracked, [])
  973. def test_status_crlf_convert(self):
  974. # First make a commit as if the file has been added on a Linux system
  975. # or with core.autocrlf=True
  976. file_path = os.path.join(self.repo.path, 'crlf')
  977. with open(file_path, 'wb') as f:
  978. f.write(b'line1\nline2')
  979. porcelain.add(repo=self.repo.path, paths=[file_path])
  980. porcelain.commit(repo=self.repo.path, message=b'test status',
  981. author=b'author <email>',
  982. committer=b'committer <email>')
  983. # Then update the file as if it was created by CGit on a Windows
  984. # system with core.autocrlf=true
  985. with open(file_path, 'wb') as f:
  986. f.write(b'line1\r\nline2')
  987. # TODO: It should be set automatically by looking at the configuration
  988. c = self.repo.get_config()
  989. c.set("core", "autocrlf", True)
  990. c.write_to_path()
  991. results = porcelain.status(self.repo)
  992. self.assertDictEqual(
  993. {'add': [], 'delete': [], 'modify': []},
  994. results.staged)
  995. self.assertListEqual(results.unstaged, [])
  996. self.assertListEqual(results.untracked, [])
  997. def test_get_tree_changes_add(self):
  998. """Unit test for get_tree_changes add."""
  999. # Make a dummy file, stage
  1000. filename = 'bar'
  1001. fullpath = os.path.join(self.repo.path, filename)
  1002. with open(fullpath, 'w') as f:
  1003. f.write('stuff')
  1004. porcelain.add(repo=self.repo.path, paths=fullpath)
  1005. porcelain.commit(repo=self.repo.path, message=b'test status',
  1006. author=b'author <email>',
  1007. committer=b'committer <email>')
  1008. filename = 'foo'
  1009. fullpath = os.path.join(self.repo.path, filename)
  1010. with open(fullpath, 'w') as f:
  1011. f.write('stuff')
  1012. porcelain.add(repo=self.repo.path, paths=fullpath)
  1013. changes = porcelain.get_tree_changes(self.repo.path)
  1014. self.assertEqual(changes['add'][0], filename.encode('ascii'))
  1015. self.assertEqual(len(changes['add']), 1)
  1016. self.assertEqual(len(changes['modify']), 0)
  1017. self.assertEqual(len(changes['delete']), 0)
  1018. def test_get_tree_changes_modify(self):
  1019. """Unit test for get_tree_changes modify."""
  1020. # Make a dummy file, stage, commit, modify
  1021. filename = 'foo'
  1022. fullpath = os.path.join(self.repo.path, filename)
  1023. with open(fullpath, 'w') as f:
  1024. f.write('stuff')
  1025. porcelain.add(repo=self.repo.path, paths=fullpath)
  1026. porcelain.commit(repo=self.repo.path, message=b'test status',
  1027. author=b'author <email>',
  1028. committer=b'committer <email>')
  1029. with open(fullpath, 'w') as f:
  1030. f.write('otherstuff')
  1031. porcelain.add(repo=self.repo.path, paths=fullpath)
  1032. changes = porcelain.get_tree_changes(self.repo.path)
  1033. self.assertEqual(changes['modify'][0], filename.encode('ascii'))
  1034. self.assertEqual(len(changes['add']), 0)
  1035. self.assertEqual(len(changes['modify']), 1)
  1036. self.assertEqual(len(changes['delete']), 0)
  1037. def test_get_tree_changes_delete(self):
  1038. """Unit test for get_tree_changes delete."""
  1039. # Make a dummy file, stage, commit, remove
  1040. filename = 'foo'
  1041. fullpath = os.path.join(self.repo.path, filename)
  1042. with open(fullpath, 'w') as f:
  1043. f.write('stuff')
  1044. porcelain.add(repo=self.repo.path, paths=fullpath)
  1045. porcelain.commit(repo=self.repo.path, message=b'test status',
  1046. author=b'author <email>',
  1047. committer=b'committer <email>')
  1048. cwd = os.getcwd()
  1049. try:
  1050. os.chdir(self.repo.path)
  1051. porcelain.remove(repo=self.repo.path, paths=[filename])
  1052. finally:
  1053. os.chdir(cwd)
  1054. changes = porcelain.get_tree_changes(self.repo.path)
  1055. self.assertEqual(changes['delete'][0], filename.encode('ascii'))
  1056. self.assertEqual(len(changes['add']), 0)
  1057. self.assertEqual(len(changes['modify']), 0)
  1058. self.assertEqual(len(changes['delete']), 1)
  1059. def test_get_untracked_paths(self):
  1060. with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f:
  1061. f.write('ignored\n')
  1062. with open(os.path.join(self.repo.path, 'ignored'), 'w') as f:
  1063. f.write('blah\n')
  1064. with open(os.path.join(self.repo.path, 'notignored'), 'w') as f:
  1065. f.write('blah\n')
  1066. self.assertEqual(
  1067. set(['ignored', 'notignored', '.gitignore']),
  1068. set(porcelain.get_untracked_paths(self.repo.path, self.repo.path,
  1069. self.repo.open_index())))
  1070. self.assertEqual(set(['.gitignore', 'notignored']),
  1071. set(porcelain.status(self.repo).untracked))
  1072. self.assertEqual(set(['.gitignore', 'notignored', 'ignored']),
  1073. set(porcelain.status(self.repo, ignored=True)
  1074. .untracked))
  1075. def test_get_untracked_paths_nested(self):
  1076. with open(os.path.join(self.repo.path, 'notignored'), 'w') as f:
  1077. f.write('blah\n')
  1078. subrepo = Repo.init(os.path.join(self.repo.path, 'nested'), mkdir=True)
  1079. with open(os.path.join(subrepo.path, 'another'), 'w') as f:
  1080. f.write('foo\n')
  1081. self.assertEqual(
  1082. set(['notignored']),
  1083. set(porcelain.get_untracked_paths(self.repo.path, self.repo.path,
  1084. self.repo.open_index())))
  1085. self.assertEqual(
  1086. set(['another']),
  1087. set(porcelain.get_untracked_paths(subrepo.path, subrepo.path,
  1088. subrepo.open_index())))
  1089. # TODO(jelmer): Add test for dulwich.porcelain.daemon
  1090. class UploadPackTests(PorcelainTestCase):
  1091. """Tests for upload_pack."""
  1092. def test_upload_pack(self):
  1093. outf = BytesIO()
  1094. exitcode = porcelain.upload_pack(
  1095. self.repo.path, BytesIO(b"0000"), outf)
  1096. outlines = outf.getvalue().splitlines()
  1097. self.assertEqual([b"0000"], outlines)
  1098. self.assertEqual(0, exitcode)
  1099. class ReceivePackTests(PorcelainTestCase):
  1100. """Tests for receive_pack."""
  1101. def test_receive_pack(self):
  1102. filename = 'foo'
  1103. fullpath = os.path.join(self.repo.path, filename)
  1104. with open(fullpath, 'w') as f:
  1105. f.write('stuff')
  1106. porcelain.add(repo=self.repo.path, paths=fullpath)
  1107. self.repo.do_commit(message=b'test status',
  1108. author=b'author <email>',
  1109. committer=b'committer <email>',
  1110. author_timestamp=1402354300,
  1111. commit_timestamp=1402354300, author_timezone=0,
  1112. commit_timezone=0)
  1113. outf = BytesIO()
  1114. exitcode = porcelain.receive_pack(
  1115. self.repo.path, BytesIO(b"0000"), outf)
  1116. outlines = outf.getvalue().splitlines()
  1117. self.assertEqual([
  1118. b'0091319b56ce3aee2d489f759736a79cc552c9bb86d9 HEAD\x00 report-status ' # noqa: E501
  1119. b'delete-refs quiet ofs-delta side-band-64k '
  1120. b'no-done symref=HEAD:refs/heads/master',
  1121. b'003f319b56ce3aee2d489f759736a79cc552c9bb86d9 refs/heads/master',
  1122. b'0000'], outlines)
  1123. self.assertEqual(0, exitcode)
  1124. class BranchListTests(PorcelainTestCase):
  1125. def test_standard(self):
  1126. self.assertEqual(set([]), set(porcelain.branch_list(self.repo)))
  1127. def test_new_branch(self):
  1128. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1129. self.repo[b"HEAD"] = c1.id
  1130. porcelain.branch_create(self.repo, b"foo")
  1131. self.assertEqual(
  1132. set([b"master", b"foo"]),
  1133. set(porcelain.branch_list(self.repo)))
  1134. class BranchCreateTests(PorcelainTestCase):
  1135. def test_branch_exists(self):
  1136. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1137. self.repo[b"HEAD"] = c1.id
  1138. porcelain.branch_create(self.repo, b"foo")
  1139. self.assertRaises(KeyError, porcelain.branch_create, self.repo, b"foo")
  1140. porcelain.branch_create(self.repo, b"foo", force=True)
  1141. def test_new_branch(self):
  1142. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1143. self.repo[b"HEAD"] = c1.id
  1144. porcelain.branch_create(self.repo, b"foo")
  1145. self.assertEqual(
  1146. set([b"master", b"foo"]),
  1147. set(porcelain.branch_list(self.repo)))
  1148. class BranchDeleteTests(PorcelainTestCase):
  1149. def test_simple(self):
  1150. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1151. self.repo[b"HEAD"] = c1.id
  1152. porcelain.branch_create(self.repo, b'foo')
  1153. self.assertTrue(b"foo" in porcelain.branch_list(self.repo))
  1154. porcelain.branch_delete(self.repo, b'foo')
  1155. self.assertFalse(b"foo" in porcelain.branch_list(self.repo))
  1156. def test_simple_unicode(self):
  1157. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1158. self.repo[b"HEAD"] = c1.id
  1159. porcelain.branch_create(self.repo, 'foo')
  1160. self.assertTrue(b"foo" in porcelain.branch_list(self.repo))
  1161. porcelain.branch_delete(self.repo, 'foo')
  1162. self.assertFalse(b"foo" in porcelain.branch_list(self.repo))
  1163. class FetchTests(PorcelainTestCase):
  1164. def test_simple(self):
  1165. outstream = BytesIO()
  1166. errstream = BytesIO()
  1167. # create a file for initial commit
  1168. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1169. os.close(handle)
  1170. porcelain.add(repo=self.repo.path, paths=fullpath)
  1171. porcelain.commit(repo=self.repo.path, message=b'test',
  1172. author=b'test <email>',
  1173. committer=b'test <email>')
  1174. # Setup target repo
  1175. target_path = tempfile.mkdtemp()
  1176. self.addCleanup(shutil.rmtree, target_path)
  1177. target_repo = porcelain.clone(self.repo.path, target=target_path,
  1178. errstream=errstream)
  1179. # create a second file to be pushed
  1180. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1181. os.close(handle)
  1182. porcelain.add(repo=self.repo.path, paths=fullpath)
  1183. porcelain.commit(repo=self.repo.path, message=b'test2',
  1184. author=b'test2 <email>',
  1185. committer=b'test2 <email>')
  1186. self.assertFalse(self.repo[b'HEAD'].id in target_repo)
  1187. target_repo.close()
  1188. # Fetch changes into the cloned repo
  1189. porcelain.fetch(target_path, self.repo.path,
  1190. outstream=outstream, errstream=errstream)
  1191. # Assert that fetch updated the local image of the remote
  1192. self.assert_correct_remote_refs(
  1193. target_repo.get_refs(), self.repo.get_refs())
  1194. # Check the target repo for pushed changes
  1195. with Repo(target_path) as r:
  1196. self.assertTrue(self.repo[b'HEAD'].id in r)
  1197. def test_with_remote_name(self):
  1198. remote_name = b'origin'
  1199. outstream = BytesIO()
  1200. errstream = BytesIO()
  1201. # create a file for initial commit
  1202. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1203. os.close(handle)
  1204. porcelain.add(repo=self.repo.path, paths=fullpath)
  1205. porcelain.commit(repo=self.repo.path, message=b'test',
  1206. author=b'test <email>',
  1207. committer=b'test <email>')
  1208. # Setup target repo
  1209. target_path = tempfile.mkdtemp()
  1210. self.addCleanup(shutil.rmtree, target_path)
  1211. target_repo = porcelain.clone(self.repo.path, target=target_path,
  1212. errstream=errstream)
  1213. # Capture current refs
  1214. target_refs = target_repo.get_refs()
  1215. # create a second file to be pushed
  1216. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1217. os.close(handle)
  1218. porcelain.add(repo=self.repo.path, paths=fullpath)
  1219. porcelain.commit(repo=self.repo.path, message=b'test2',
  1220. author=b'test2 <email>',
  1221. committer=b'test2 <email>')
  1222. self.assertFalse(self.repo[b'HEAD'].id in target_repo)
  1223. target_repo.close()
  1224. # Fetch changes into the cloned repo
  1225. porcelain.fetch(target_path, self.repo.path, remote_name=remote_name,
  1226. outstream=outstream, errstream=errstream)
  1227. # Assert that fetch updated the local image of the remote
  1228. self.assert_correct_remote_refs(
  1229. target_repo.get_refs(), self.repo.get_refs())
  1230. # Check the target repo for pushed changes, as well as updates
  1231. # for the refs
  1232. with Repo(target_path) as r:
  1233. self.assertTrue(self.repo[b'HEAD'].id in r)
  1234. self.assertNotEqual(self.repo.get_refs(), target_refs)
  1235. def assert_correct_remote_refs(
  1236. self, local_refs, remote_refs, remote_name=b'origin'):
  1237. """Assert that known remote refs corresponds to actual remote refs."""
  1238. local_ref_prefix = b'refs/heads'
  1239. remote_ref_prefix = b'refs/remotes/' + remote_name
  1240. locally_known_remote_refs = {
  1241. k[len(remote_ref_prefix) + 1:]: v for k, v in local_refs.items()
  1242. if k.startswith(remote_ref_prefix)}
  1243. normalized_remote_refs = {
  1244. k[len(local_ref_prefix) + 1:]: v for k, v in remote_refs.items()
  1245. if k.startswith(local_ref_prefix)}
  1246. self.assertEqual(locally_known_remote_refs, normalized_remote_refs)
  1247. class RepackTests(PorcelainTestCase):
  1248. def test_empty(self):
  1249. porcelain.repack(self.repo)
  1250. def test_simple(self):
  1251. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1252. os.close(handle)
  1253. porcelain.add(repo=self.repo.path, paths=fullpath)
  1254. porcelain.repack(self.repo)
  1255. class LsTreeTests(PorcelainTestCase):
  1256. def test_empty(self):
  1257. porcelain.commit(repo=self.repo.path, message=b'test status',
  1258. author=b'author <email>',
  1259. committer=b'committer <email>')
  1260. f = StringIO()
  1261. porcelain.ls_tree(self.repo, b"HEAD", outstream=f)
  1262. self.assertEqual(f.getvalue(), "")
  1263. def test_simple(self):
  1264. # Commit a dummy file then modify it
  1265. fullpath = os.path.join(self.repo.path, 'foo')
  1266. with open(fullpath, 'w') as f:
  1267. f.write('origstuff')
  1268. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1269. porcelain.commit(repo=self.repo.path, message=b'test status',
  1270. author=b'author <email>',
  1271. committer=b'committer <email>')
  1272. f = StringIO()
  1273. porcelain.ls_tree(self.repo, b"HEAD", outstream=f)
  1274. self.assertEqual(
  1275. f.getvalue(),
  1276. '100644 blob 8b82634d7eae019850bb883f06abf428c58bc9aa\tfoo\n')
  1277. def test_recursive(self):
  1278. # Create a directory then write a dummy file in it
  1279. dirpath = os.path.join(self.repo.path, 'adir')
  1280. filepath = os.path.join(dirpath, 'afile')
  1281. os.mkdir(dirpath)
  1282. with open(filepath, 'w') as f:
  1283. f.write('origstuff')
  1284. porcelain.add(repo=self.repo.path, paths=[filepath])
  1285. porcelain.commit(repo=self.repo.path, message=b'test status',
  1286. author=b'author <email>',
  1287. committer=b'committer <email>')
  1288. f = StringIO()
  1289. porcelain.ls_tree(self.repo, b"HEAD", outstream=f)
  1290. self.assertEqual(
  1291. f.getvalue(),
  1292. '40000 tree b145cc69a5e17693e24d8a7be0016ed8075de66d\tadir\n')
  1293. f = StringIO()
  1294. porcelain.ls_tree(self.repo, b"HEAD", outstream=f, recursive=True)
  1295. self.assertEqual(
  1296. f.getvalue(),
  1297. '40000 tree b145cc69a5e17693e24d8a7be0016ed8075de66d\tadir\n'
  1298. '100644 blob 8b82634d7eae019850bb883f06abf428c58bc9aa\tadir'
  1299. '/afile\n')
  1300. class LsRemoteTests(PorcelainTestCase):
  1301. def test_empty(self):
  1302. self.assertEqual({}, porcelain.ls_remote(self.repo.path))
  1303. def test_some(self):
  1304. cid = porcelain.commit(repo=self.repo.path, message=b'test status',
  1305. author=b'author <email>',
  1306. committer=b'committer <email>')
  1307. self.assertEqual({
  1308. b'refs/heads/master': cid,
  1309. b'HEAD': cid},
  1310. porcelain.ls_remote(self.repo.path))
  1311. class LsFilesTests(PorcelainTestCase):
  1312. def test_empty(self):
  1313. self.assertEqual([], list(porcelain.ls_files(self.repo)))
  1314. def test_simple(self):
  1315. # Commit a dummy file then modify it
  1316. fullpath = os.path.join(self.repo.path, 'foo')
  1317. with open(fullpath, 'w') as f:
  1318. f.write('origstuff')
  1319. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1320. self.assertEqual([b'foo'], list(porcelain.ls_files(self.repo)))
  1321. class RemoteAddTests(PorcelainTestCase):
  1322. def test_new(self):
  1323. porcelain.remote_add(
  1324. self.repo, 'jelmer', 'git://jelmer.uk/code/dulwich')
  1325. c = self.repo.get_config()
  1326. self.assertEqual(
  1327. c.get((b'remote', b'jelmer'), b'url'),
  1328. b'git://jelmer.uk/code/dulwich')
  1329. def test_exists(self):
  1330. porcelain.remote_add(
  1331. self.repo, 'jelmer', 'git://jelmer.uk/code/dulwich')
  1332. self.assertRaises(porcelain.RemoteExists, porcelain.remote_add,
  1333. self.repo, 'jelmer', 'git://jelmer.uk/code/dulwich')
  1334. class CheckIgnoreTests(PorcelainTestCase):
  1335. def test_check_ignored(self):
  1336. with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f:
  1337. f.write('foo')
  1338. foo_path = os.path.join(self.repo.path, 'foo')
  1339. with open(foo_path, 'w') as f:
  1340. f.write('BAR')
  1341. bar_path = os.path.join(self.repo.path, 'bar')
  1342. with open(bar_path, 'w') as f:
  1343. f.write('BAR')
  1344. self.assertEqual(
  1345. ['foo'],
  1346. list(porcelain.check_ignore(self.repo, [foo_path])))
  1347. self.assertEqual(
  1348. [], list(porcelain.check_ignore(self.repo, [bar_path])))
  1349. def test_check_added_abs(self):
  1350. path = os.path.join(self.repo.path, 'foo')
  1351. with open(path, 'w') as f:
  1352. f.write('BAR')
  1353. self.repo.stage(['foo'])
  1354. with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f:
  1355. f.write('foo\n')
  1356. self.assertEqual(
  1357. [], list(porcelain.check_ignore(self.repo, [path])))
  1358. self.assertEqual(
  1359. ['foo'],
  1360. list(porcelain.check_ignore(self.repo, [path], no_index=True)))
  1361. def test_check_added_rel(self):
  1362. with open(os.path.join(self.repo.path, 'foo'), 'w') as f:
  1363. f.write('BAR')
  1364. self.repo.stage(['foo'])
  1365. with open(os.path.join(self.repo.path, '.gitignore'), 'w') as f:
  1366. f.write('foo\n')
  1367. cwd = os.getcwd()
  1368. os.mkdir(os.path.join(self.repo.path, 'bar'))
  1369. os.chdir(os.path.join(self.repo.path, 'bar'))
  1370. try:
  1371. self.assertEqual(
  1372. list(porcelain.check_ignore(self.repo, ['../foo'])), [])
  1373. self.assertEqual(['../foo'], list(
  1374. porcelain.check_ignore(self.repo, ['../foo'], no_index=True)))
  1375. finally:
  1376. os.chdir(cwd)
  1377. class UpdateHeadTests(PorcelainTestCase):
  1378. def test_set_to_branch(self):
  1379. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1380. self.repo.refs[b"refs/heads/blah"] = c1.id
  1381. porcelain.update_head(self.repo, "blah")
  1382. self.assertEqual(c1.id, self.repo.head())
  1383. self.assertEqual(b'ref: refs/heads/blah',
  1384. self.repo.refs.read_ref(b'HEAD'))
  1385. def test_set_to_branch_detached(self):
  1386. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1387. self.repo.refs[b"refs/heads/blah"] = c1.id
  1388. porcelain.update_head(self.repo, "blah", detached=True)
  1389. self.assertEqual(c1.id, self.repo.head())
  1390. self.assertEqual(c1.id, self.repo.refs.read_ref(b'HEAD'))
  1391. def test_set_to_commit_detached(self):
  1392. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1393. self.repo.refs[b"refs/heads/blah"] = c1.id
  1394. porcelain.update_head(self.repo, c1.id, detached=True)
  1395. self.assertEqual(c1.id, self.repo.head())
  1396. self.assertEqual(c1.id, self.repo.refs.read_ref(b'HEAD'))
  1397. def test_set_new_branch(self):
  1398. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1399. self.repo.refs[b"refs/heads/blah"] = c1.id
  1400. porcelain.update_head(self.repo, "blah", new_branch="bar")
  1401. self.assertEqual(c1.id, self.repo.head())
  1402. self.assertEqual(b'ref: refs/heads/bar',
  1403. self.repo.refs.read_ref(b'HEAD'))
  1404. class MailmapTests(PorcelainTestCase):
  1405. def test_no_mailmap(self):
  1406. self.assertEqual(
  1407. b'Jelmer Vernooij <jelmer@samba.org>',
  1408. porcelain.check_mailmap(
  1409. self.repo, b'Jelmer Vernooij <jelmer@samba.org>'))
  1410. def test_mailmap_lookup(self):
  1411. with open(os.path.join(self.repo.path, '.mailmap'), 'wb') as f:
  1412. f.write(b"""\
  1413. Jelmer Vernooij <jelmer@debian.org>
  1414. """)
  1415. self.assertEqual(
  1416. b'Jelmer Vernooij <jelmer@debian.org>',
  1417. porcelain.check_mailmap(
  1418. self.repo, b'Jelmer Vernooij <jelmer@samba.org>'))
  1419. class FsckTests(PorcelainTestCase):
  1420. def test_none(self):
  1421. self.assertEqual(
  1422. [],
  1423. list(porcelain.fsck(self.repo)))
  1424. def test_git_dir(self):
  1425. obj = Tree()
  1426. a = Blob()
  1427. a.data = b"foo"
  1428. obj.add(b".git", 0o100644, a.id)
  1429. self.repo.object_store.add_objects(
  1430. [(a, None), (obj, None)])
  1431. self.assertEqual(
  1432. [(obj.id, 'invalid name .git')],
  1433. [(sha, str(e)) for (sha, e) in porcelain.fsck(self.repo)])
  1434. class DescribeTests(PorcelainTestCase):
  1435. def test_no_commits(self):
  1436. self.assertRaises(KeyError, porcelain.describe, self.repo.path)
  1437. def test_single_commit(self):
  1438. fullpath = os.path.join(self.repo.path, 'foo')
  1439. with open(fullpath, 'w') as f:
  1440. f.write("BAR")
  1441. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1442. sha = porcelain.commit(
  1443. self.repo.path, message=b"Some message",
  1444. author=b"Joe <joe@example.com>",
  1445. committer=b"Bob <bob@example.com>")
  1446. self.assertEqual(
  1447. 'g{}'.format(sha[:7].decode('ascii')),
  1448. porcelain.describe(self.repo.path))
  1449. def test_tag(self):
  1450. fullpath = os.path.join(self.repo.path, 'foo')
  1451. with open(fullpath, 'w') as f:
  1452. f.write("BAR")
  1453. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1454. porcelain.commit(
  1455. self.repo.path, message=b"Some message",
  1456. author=b"Joe <joe@example.com>",
  1457. committer=b"Bob <bob@example.com>")
  1458. porcelain.tag_create(self.repo.path, b"tryme", b'foo <foo@bar.com>',
  1459. b'bar', annotated=True)
  1460. self.assertEqual(
  1461. "tryme",
  1462. porcelain.describe(self.repo.path))
  1463. def test_tag_and_commit(self):
  1464. fullpath = os.path.join(self.repo.path, 'foo')
  1465. with open(fullpath, 'w') as f:
  1466. f.write("BAR")
  1467. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1468. porcelain.commit(
  1469. self.repo.path, message=b"Some message",
  1470. author=b"Joe <joe@example.com>",
  1471. committer=b"Bob <bob@example.com>")
  1472. porcelain.tag_create(self.repo.path, b"tryme", b'foo <foo@bar.com>',
  1473. b'bar', annotated=True)
  1474. with open(fullpath, 'w') as f:
  1475. f.write("BAR2")
  1476. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1477. sha = porcelain.commit(
  1478. self.repo.path, message=b"Some message",
  1479. author=b"Joe <joe@example.com>",
  1480. committer=b"Bob <bob@example.com>")
  1481. self.assertEqual(
  1482. 'tryme-1-g{}'.format(sha[:7].decode('ascii')),
  1483. porcelain.describe(self.repo.path))
  1484. class HelperTests(PorcelainTestCase):
  1485. def test_path_to_tree_path_base(self):
  1486. self.assertEqual(
  1487. b'bar', porcelain.path_to_tree_path('/home/foo', '/home/foo/bar'))
  1488. self.assertEqual(b'bar', porcelain.path_to_tree_path('.', './bar'))
  1489. self.assertEqual(b'bar', porcelain.path_to_tree_path('.', 'bar'))
  1490. cwd = os.getcwd()
  1491. self.assertEqual(
  1492. b'bar', porcelain.path_to_tree_path('.', os.path.join(cwd, 'bar')))
  1493. self.assertEqual(b'bar', porcelain.path_to_tree_path(cwd, 'bar'))
  1494. def test_path_to_tree_path_syntax(self):
  1495. self.assertEqual(b'bar', porcelain.path_to_tree_path(b'.', './bar'))
  1496. self.assertEqual(b'bar', porcelain.path_to_tree_path('.', b'./bar'))
  1497. self.assertEqual(b'bar', porcelain.path_to_tree_path(b'.', b'./bar'))
  1498. def test_path_to_tree_path_error(self):
  1499. with self.assertRaises(ValueError):
  1500. porcelain.path_to_tree_path('/home/foo/', '/home/bar/baz')
  1501. def test_path_to_tree_path_rel(self):
  1502. cwd = os.getcwd()
  1503. os.mkdir(os.path.join(self.repo.path, 'foo'))
  1504. os.mkdir(os.path.join(self.repo.path, 'foo/bar'))
  1505. try:
  1506. os.chdir(os.path.join(self.repo.path, 'foo/bar'))
  1507. self.assertEqual(b'bar/baz', porcelain.path_to_tree_path(
  1508. '..', 'baz'))
  1509. self.assertEqual(b'bar/baz', porcelain.path_to_tree_path(
  1510. os.path.join(os.getcwd(), '..'),
  1511. os.path.join(os.getcwd(), 'baz')))
  1512. self.assertEqual(b'bar/baz', porcelain.path_to_tree_path(
  1513. '..', os.path.join(os.getcwd(), 'baz')))
  1514. self.assertEqual(b'bar/baz', porcelain.path_to_tree_path(
  1515. os.path.join(os.getcwd(), '..'), 'baz'))
  1516. finally:
  1517. os.chdir(cwd)
  1518. class GetObjectBypathTests(PorcelainTestCase):
  1519. def test_simple(self):
  1520. fullpath = os.path.join(self.repo.path, 'foo')
  1521. with open(fullpath, 'w') as f:
  1522. f.write("BAR")
  1523. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1524. porcelain.commit(
  1525. self.repo.path, message=b"Some message",
  1526. author=b"Joe <joe@example.com>",
  1527. committer=b"Bob <bob@example.com>")
  1528. self.assertEqual(
  1529. b"BAR",
  1530. porcelain.get_object_by_path(self.repo, 'foo').data)
  1531. def test_missing(self):
  1532. self.assertRaises(
  1533. KeyError,
  1534. porcelain.get_object_by_path, self.repo, 'foo')
  1535. class WriteTreeTests(PorcelainTestCase):
  1536. def test_simple(self):
  1537. fullpath = os.path.join(self.repo.path, 'foo')
  1538. with open(fullpath, 'w') as f:
  1539. f.write("BAR")
  1540. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1541. self.assertEqual(
  1542. b'd2092c8a9f311f0311083bf8d177f2ca0ab5b241',
  1543. porcelain.write_tree(self.repo))