test_porcelain.py 100 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869
  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, StringIO
  22. import os
  23. import platform
  24. import re
  25. import shutil
  26. import stat
  27. import subprocess
  28. import sys
  29. import tarfile
  30. import tempfile
  31. import time
  32. from unittest import skipIf
  33. from dulwich import porcelain
  34. from dulwich.diff_tree import tree_changes
  35. from dulwich.errors import (
  36. CommitError,
  37. )
  38. from dulwich.objects import (
  39. Blob,
  40. Tag,
  41. Tree,
  42. ZERO_SHA,
  43. )
  44. from dulwich.repo import (
  45. NoIndexPresent,
  46. Repo,
  47. )
  48. from dulwich.tests import (
  49. TestCase,
  50. )
  51. from dulwich.tests.utils import (
  52. build_commit_graph,
  53. make_commit,
  54. make_object,
  55. )
  56. def flat_walk_dir(dir_to_walk):
  57. for dirpath, _, filenames in os.walk(dir_to_walk):
  58. rel_dirpath = os.path.relpath(dirpath, dir_to_walk)
  59. if not dirpath == dir_to_walk:
  60. yield rel_dirpath
  61. for filename in filenames:
  62. if dirpath == dir_to_walk:
  63. yield filename
  64. else:
  65. yield os.path.join(rel_dirpath, filename)
  66. class PorcelainTestCase(TestCase):
  67. def setUp(self):
  68. super(PorcelainTestCase, self).setUp()
  69. self.test_dir = tempfile.mkdtemp()
  70. self.addCleanup(shutil.rmtree, self.test_dir)
  71. self.repo_path = os.path.join(self.test_dir, "repo")
  72. self.repo = Repo.init(self.repo_path, mkdir=True)
  73. self.addCleanup(self.repo.close)
  74. def assertRecentTimestamp(self, ts):
  75. # On some slow CIs it does actually take more than 5 seconds to go from
  76. # creating the tag to here.
  77. self.assertLess(time.time() - ts, 50)
  78. class PorcelainGpgTestCase(PorcelainTestCase):
  79. DEFAULT_KEY = """
  80. -----BEGIN PGP PRIVATE KEY BLOCK-----
  81. lQVYBGBjIyIBDADAwydvMPQqeEiK54FG1DHwT5sQejAaJOb+PsOhVa4fLcKsrO3F
  82. g5CxO+/9BHCXAr8xQAtp/gOhDN05fyK3MFyGlL9s+Cd8xf34S3R4rN/qbF0oZmaa
  83. FW0MuGnniq54HINs8KshadVn1Dhi/GYSJ588qNFRl/qxFTYAk+zaGsgX/QgFfy0f
  84. djWXJLypZXu9D6DlyJ0cPSzUlfBkI2Ytx6grzIquRjY0FbkjK3l+iGsQ+ebRMdcP
  85. Sqd5iTN9XuzIUVoBFAZBRjibKV3N2wxlnCbfLlzCyDp7rktzSThzjJ2pVDuLrMAx
  86. 6/L9hIhwmFwdtY4FBFGvMR0b0Ugh3kCsRWr8sgj9I7dUoLHid6ObYhJFhnD3GzRc
  87. U+xX1uy3iTCqJDsG334aQIhC5Giuxln4SUZna2MNbq65ksh38N1aM/t3+Dc/TKVB
  88. rb5KWicRPCQ4DIQkHMDCSPyj+dvRLCPzIaPvHD7IrCfHYHOWuvvPGCpwjo0As3iP
  89. IecoMeguPLVaqgcAEQEAAQAL/i5/pQaUd4G7LDydpbixPS6r9UrfPrU/y5zvBP/p
  90. DCynPDutJ1oq539pZvXQ2VwEJJy7x0UVKkjyMndJLNWly9wHC7o8jkHx/NalVP47
  91. LXR+GWbCdOOcYYbdAWcCNB3zOtzPnWhdAEagkc2G9xRQDIB0dLHLCIUpCbLP/CWM
  92. qlHnDsVMrVTWjgzcpsnyGgw8NeLYJtYGB8dsN+XgCCjo7a9LEvUBKNgdmWBbf14/
  93. iBw7PCugazFcH9QYfZwzhsi3nqRRagTXHbxFRG0LD9Ro9qCEutHYGP2PJ59Nj8+M
  94. zaVkJj/OxWxVOGvn2q16mQBCjKpbWfqXZVVl+G5DGOmiSTZqXy+3j6JCKdOMy6Qd
  95. JBHOHhFZXYmWYaaPzoc33T/C3QhMfY5sOtUDLJmV05Wi4dyBeNBEslYgUuTk/jXb
  96. 5ZAie25eDdrsoqkcnSs2ZguMF7AXhe6il2zVhUUMs/6UZgd6I7I4Is0HXT/pnxEp
  97. uiTRFu4v8E+u+5a8O3pffe5boQYA3TsIxceen20qY+kRaTOkURHMZLn/y6KLW8bZ
  98. rNJyXWS9hBAcbbSGhfOwYfzbDCM17yPQO3E2zo8lcGdRklUdIIaCxQwtu36N5dfx
  99. OLCCQc5LmYdl/EAm91iAhrr7dNntZ18MU09gdzUu+ONZwu4CP3cJT83+qYZULso8
  100. 4Fvd/X8IEfGZ7kM+ylrdqBwtlrn8yYXtom+ows2M2UuNR53B+BUOd73kVLTkTCjE
  101. JH63+nE8BqG7tDLCMws+23SAA3xxBgDfDrr0x7zCozQKVQEqBzQr9Uoo/c/ZjAfi
  102. syzNSrDz+g5gqJYtuL9XpPJVWf6V1GXVyJlSbxR9CjTkBxmlPxpvV25IsbVSsh0o
  103. aqkf2eWpbCL6Qb2E0jd1rvf8sGeTTohzYfiSVVsC2t9ngRO/CmetizwQBvRzLGMZ
  104. 4mtAPiy7ZEDc2dFrPp7zlKISYmJZUx/DJVuZWuOrVMpBP+bSgJXoMTlICxZUqUnE
  105. 2VKVStb/L+Tl8XCwIWdrZb9BaDnHqfcGAM2B4HNPxP88Yj1tEDly/vqeb3vVMhj+
  106. S1lunnLdgxp46YyuTMYAzj88eCGurRtzBsdxxlGAsioEnZGebEqAHQbieKq/DO6I
  107. MOMZHMSVBDqyyIx3assGlxSX8BSFW0lhKyT7i0XqnAgCJ9f/5oq0SbFGq+01VQb7
  108. jIx9PbcYJORxsE0JG/CXXPv27bRtQXsudkWGSYvC0NLOgk4z8+kQpQtyFh16lujq
  109. WRwMeriu0qNDjCa1/eHIKDovhAZ3GyO5/9m1tBlUZXN0IFVzZXIgPHRlc3RAdGVz
  110. dC5jb20+iQHOBBMBCAA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAFiEEjrR8
  111. MQ4fJK44PYMvfN2AClLmXiYFAmDcEZEACgkQfN2AClLmXibZzgv/ZfeTpTuqQE1W
  112. C1jT5KpQExnt0BizTX0U7BvSn8Fr6VXTyol6kYc3u71GLUuJyawCLtIzOXqOXJvz
  113. bjcZqymcMADuftKcfMy513FhbF6MhdVd6QoeBP6+7/xXOFJCi+QVYF7SQ2h7K1Qm
  114. +yXOiAMgSxhCZQGPBNJLlDUOd47nSIMANvlumFtmLY/1FD7RpG7WQWjeX1mnxNTw
  115. hUU+Yv7GuFc/JprXCIYqHbhWfvXyVtae2ZK4xuVi5eqwA2RfggOVM7drb+CgPhG0
  116. +9aEDDLOZqVi65wK7J73Puo3rFTbPQMljxw5s27rWqF+vB6hhVdJOPNomWy3naPi
  117. k5MW0mhsacASz1WYndpZz+XaQTq/wJF5HUyyeUWJ0vlOEdwx021PHcqSTyfNnkjD
  118. KncrE21t2sxWRsgGDETxIwkd2b2HNGAvveUD0ffFK/oJHGSXjAERFGc3wuiDj3mQ
  119. BvKm4wt4QF9ZMrCdhMAA6ax5kfEUqQR4ntmrJk/khp/mV7TILaI4nQVYBGBjIyIB
  120. DADghIo9wXnRxzfdDTvwnP8dHpLAIaPokgdpyLswqUCixJWiW2xcV6weUjEWwH6n
  121. eN/t1uZYVehbrotxVPla+MPvzhxp6/cmG+2lhzEBOp6zRwnL1wIB6HoKJfpREhyM
  122. c8rLR0zMso1L1bJTyydvnu07a7BWo3VWKjilb0rEZZUSD/2hidx5HxMOJSoidLWe
  123. d/PPuv6yht3NtA4UThlcfldm9G6PbqCdm1kMEKAkq0wVJvhPJ6gEFRNJimgygfUw
  124. MDFXEIhQtxjgdV5Uoz3O5452VLoRsDlgpi3E0WDGj7WXDaO5uSU0T5aJgVgHCP/f
  125. xZhHuQFk2YYIl5nCBpOZyWWI0IKmscTuEwzpkhICQDQFvcMZ5ibsl7wA2P7YTrQf
  126. FDMjjzuaK80GYPfxDFlyKUyLqFt8w/QzsZLDLX7+jxIEpbRAaMw/JsWqm5BMxxbS
  127. 3CIQiS5S3oSKDsNINelqWFfwvLhvlQra8gIxyNTlek25OdgG66BiiX+seH8A/ql+
  128. F+MAEQEAAQAL/1jrNSLjMt9pwo6qFKClVQZP2vf7+sH7v7LeHIDXr3EnYUnVYnOq
  129. B1FU5PspTp/+J9W25DB9CZLx7Gj8qeslFdiuLSOoIBB4RCToB3kAoeTH0DHqW/Gs
  130. hFTrmJkuDp9zpo/ek6SIXJx5rHAyR9KVw0fizQprH2f6PcgLbTWeM61dJuqowmg3
  131. 7eCOyIKv7VQvFqEhYokLD+JNmrvg+Htg0DXGvdjRjAwPf/NezEXpj67a6cHTp1/C
  132. hwp7pevG+3fTxaCJFesl5/TxxtnaBLE8m2uo/S6Hxgn9l0edonroe1QlTjEqGLy2
  133. 7qi2z5Rem+v6GWNDRgvAWur13v8FNdyduHlioG/NgRsU9mE2MYeFsfi3cfNpJQp/
  134. wC9PSCIXrb/45mkS8KyjZpCrIPB9RV/m0MREq01TPom7rstZc4A1pD0Ot7AtUYS3
  135. e95zLyEmeLziPJ9fV4fgPmEudDr1uItnmV0LOskKlpg5sc0hhdrwYoobfkKt2dx6
  136. DqfMlcM1ZkUbLQYA4jwfpFJG4HmYvjL2xCJxM0ycjvMbqFN+4UjgYWVlRfOrm1V4
  137. Op86FjbRbV6OOCNhznotAg7mul4xtzrrTkK8o3YLBeJseDgl4AWuzXtNa9hE0XpK
  138. 9gJoEHUuBOOsamVh2HpXESFyE5CclOV7JSh541TlZKfnqfZYCg4JSbp0UijkawCL
  139. 5bJJUiGGMD9rZUxIAKQO1DvUEzptS7Jl6S3y5sbIIhilp4KfYWbSk3PPu9CnZD5b
  140. LhEQp0elxnb/IL8PBgD+DpTeC8unkGKXUpbe9x0ISI6V1D6FmJq/FxNg7fMa3QCh
  141. fGiAyoTm80ZETynj+blRaDO3gY4lTLa3Opubof1EqK2QmwXmpyvXEZNYcQfQ2CCS
  142. GOWUCK8jEQamUPf1PWndZXJUmROI1WukhlL71V/ir6zQeVCv1wcwPwclJPnAe87u
  143. pEklnCYpvsEldwHUX9u0BWzoULIEsi+ddtHmT0KTeF/DHRy0W15jIHbjFqhqckj1
  144. /6fmr7l7kIi/kN4vWe0F/0Q8IXX+cVMgbl3aIuaGcvENLGcoAsAtPGx88SfRgmfu
  145. HK64Y7hx1m+Bo215rxJzZRjqHTBPp0BmCi+JKkaavIBrYRbsx20gveI4dzhLcUhB
  146. kiT4Q7oz0/VbGHS1CEf9KFeS/YOGj57s4yHauSVI0XdP9kBRTWmXvBkzsooB2cKH
  147. hwhUN7iiT1k717CiTNUT6Q/pcPFCyNuMoBBGQTU206JEgIjQvI3f8xMUMGmGVVQz
  148. 9/k716ycnhb2JZ/Q/AyQIeHJiQG2BBgBCAAgAhsMFiEEjrR8MQ4fJK44PYMvfN2A
  149. ClLmXiYFAmDcEa4ACgkQfN2AClLmXiZxxQv/XaMN0hPCygtrQMbCsTNb34JbvJzh
  150. hngPuUAfTbRHrR3YeATyQofNbL0DD3fvfzeFF8qESqvzCSZxS6dYsXPd4MCJTzlp
  151. zYBZ2X0sOrgDqZvqCZKN72RKgdk0KvthdzAxsIm2dfcQOxxowXMxhJEXZmsFpusx
  152. jKJxOcrfVRjXJnh9isY0NpCoqMQ+3k3wDJ3VGEHV7G+A+vFkWfbLJF5huQ96uaH9
  153. Uc+jUsREUH9G82ZBqpoioEN8Ith4VXpYnKdTMonK/+ZcyeraJZhXrvbjnEomKdzU
  154. 0pu4bt1HlLR3dcnpjN7b009MBf2xLgEfQk2nPZ4zzY+tDkxygtPllaB4dldFjBpT
  155. j7Q+t49sWMjmlJUbLlHfuJ7nUUK5+cGjBsWVObAEcyfemHWCTVFnEa2BJslGC08X
  156. rFcjRRcMEr9ct4551QFBHsv3O/Wp3/wqczYgE9itSnGT05w+4vLt4smG+dnEHjRJ
  157. brMb2upTHa+kjktjdO96/BgSnKYqmNmPB/qB
  158. =ivA/
  159. -----END PGP PRIVATE KEY BLOCK-----
  160. """
  161. DEFAULT_KEY_ID = "8EB47C310E1F24AE383D832F7CDD800A52E65E26"
  162. NON_DEFAULT_KEY = """
  163. -----BEGIN PGP PRIVATE KEY BLOCK-----
  164. lQVYBGBjI0ABDADGWBRp+t02emfzUlhrc1psqIhhecFm6Em0Kv33cfDpnfoMF1tK
  165. Yy/4eLYIR7FmpdbFPcDThFNHbXJzBi00L1mp0XQE2l50h/2bDAAgREdZ+NVo5a7/
  166. RSZjauNU1PxW6pnXMehEh1tyIQmV78jAukaakwaicrpIenMiFUN3fAKHnLuFffA6
  167. t0f3LqJvTDhUw/o2vPgw5e6UDQhA1C+KTv1KXVrhJNo88a3hZqCZ76z3drKR411Q
  168. zYgT4DUb8lfnbN+z2wfqT9oM5cegh2k86/mxAA3BYOeQrhmQo/7uhezcgbxtdGZr
  169. YlbuaNDTSBrn10ZoaxLPo2dJe2zWxgD6MpvsGU1w3tcRW508qo/+xoWp2/pDzmok
  170. +uhOh1NAj9zB05VWBz1r7oBgCOIKpkD/LD4VKq59etsZ/UnrYDwKdXWZp7uhshkU
  171. M7N35lUJcR76a852dlMdrgpmY18+BP7+o7M+5ElHTiqQbMuE1nHTg8RgVpdV+tUx
  172. dg6GWY/XHf5asm8AEQEAAQAL/A85epOp+GnymmEQfI3+5D178D//Lwu9n86vECB6
  173. xAHCqQtdjZnXpDp/1YUsL59P8nzgYRk7SoMskQDoQ/cB/XFuDOhEdMSgHaTVlnrj
  174. ktCCq6rqGnUosyolbb64vIfVaSqd/5SnCStpAsnaBoBYrAu4ZmV4xfjDQWwn0q5s
  175. u+r56mD0SkjPgbwk/b3qTVagVmf2OFzUgWwm1e/X+bA1oPag1NV8VS4hZPXswT4f
  176. qhiyqUFOgP6vUBcqehkjkIDIl/54xII7/P5tp3LIZawvIXqHKNTqYPCqaCqCj+SL
  177. vMYDIb6acjescfZoM71eAeHAANeFZzr/rwfBT+dEP6qKmPXNcvgE11X44ZCr04nT
  178. zOV/uDUifEvKT5qgtyJpSFEVr7EXubJPKoNNhoYqq9z1pYU7IedX5BloiVXKOKTY
  179. 0pk7JkLqf3g5fYtXh/wol1owemITJy5V5PgaqZvk491LkI6S+kWC7ANYUg+TDPIW
  180. afxW3E5N1CYV6XDAl0ZihbLcoQYAy0Ky/p/wayWKePyuPBLwx9O89GSONK2pQljZ
  181. yaAgxPQ5/i1vx6LIMg7k/722bXR9W3zOjWOin4eatPM3d2hkG96HFvnBqXSmXOPV
  182. 03Xqy1/B5Tj8E9naLKUHE/OBQEc363DgLLG9db5HfPlpAngeppYPdyWkhzXyzkgS
  183. PylaE5eW3zkdjEbYJ6RBTecTZEgBaMvJNPdWbn//frpP7kGvyiCg5Es+WjLInUZ6
  184. 0sdifcNTCewzLXK80v/y5mVOdJhPBgD5zs9cYdyiQJayqAuOr+He1eMHMVUbm9as
  185. qBmPrst398eBW9ZYF7eBfTSlUf6B+WnvyLKEGsUf/7IK0EWDlzoBuWzWiHjUAY1g
  186. m9eTV2MnvCCCefqCErWwfFo2nWOasAZA9sKD+ICIBY4tbtvSl4yfLBzTMwSvs9ZS
  187. K1ocPSYUnhm2miSWZ8RLZPH7roHQasNHpyq/AX7DahFf2S/bJ+46ZGZ8Pigr7hA+
  188. MjmpQ4qVdb5SaViPmZhAKO+PjuCHm+EF/2H0Y3Sl4eXgxZWoQVOUeXdWg9eMfYrj
  189. XDtUMIFppV/QxbeztZKvJdfk64vt/crvLsOp0hOky9cKwY89r4QaHfexU3qR+qDq
  190. UlMvR1rHk7dS5HZAtw0xKsFJNkuDxvBkMqv8Los8zp3nUl+U99dfZOArzNkW38wx
  191. FPa0ixkC9za2BkDrWEA8vTnxw0A2upIFegDUhwOByrSyfPPnG3tKGeqt3Izb/kDk
  192. Q9vmo+HgxBOguMIvlzbBfQZwtbd/gXzlvPqCtCJBbm90aGVyIFRlc3QgVXNlciA8
  193. dGVzdDJAdGVzdC5jb20+iQHOBBMBCAA4AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B
  194. AheAFiEEapM5P1DF5qzT1vtFuTYhLttOFMAFAmDcEeEACgkQuTYhLttOFMDe0Qv/
  195. Qx/bzXztJ3BCc+CYAVDx7Kr37S68etwwLgcWzhG+CDeMB5F/QE+upKgxy2iaqQFR
  196. mxfOMgf/TIQkUfkbaASzK1LpnesYO85pk7XYjoN1bYEHiXTkeW+bgB6aJIxrRmO2
  197. SrWasdBC/DsI3Mrya8YMt/TiHC6VpRJVxCe5vv7/kZC4CXrgTBnZocXx/YXimbke
  198. poPMVdbvhYh6N0aGeS38jRKgyN10KXmhDTAQDwseVFavBWAjVfx3DEwjtK2Z2GbA
  199. aL8JvAwRtqiPFkDMIKPL4UwxtXFws8SpMt6juroUkNyf6+BxNWYqmwXHPy8zCJAb
  200. xkxIJMlEc+s7qQsP3fILOo8Xn+dVzJ5sa5AoARoXm1GMjsdqaKAzq99Dic/dHnaQ
  201. Civev1PQsdwlYW2C2wNXNeIrxMndbDMFfNuZ6BnGHWJ/wjcp/pFs4YkyyZN8JH7L
  202. hP2FO4Jgham3AuP13kC3Ivea7V6hR8QNcDZRwFPOMIX4tXwQv1T72+7DZGaA25O7
  203. nQVXBGBjI0ABDADJMBYIcG0Yil9YxFs7aYzNbd7alUAr89VbY8eIGPHP3INFPM1w
  204. lBQCu+4j6xdEbhMpppLBZ9A5TEylP4C6qLtPa+oLtPeuSw8gHDE10XE4lbgPs376
  205. rL60XdImSOHhiduACUefYjqpcmFH9Bim1CC+koArYrSQJQx1Jri+OpnTaL/8UID0
  206. KzD/kEgMVGlHIVj9oJmb4+j9pW8I/g0wDSnIaEKFMxqu6SIVJ1GWj+MUMvZigjLC
  207. sNCZd7PnbOC5VeU3SsXj6he74Jx0AmGMPWIHi9M0DjHO5d1cCbXTnud8xxM1bOh4
  208. 7aCTnMK5cVyIr+adihgJpVVhrndSM8aklBPRgtozrGNCgF2CkYU2P1blxfloNr/8
  209. UZpM83o+s1aObBszzRNLxnpNORqoLqjfPtLEPQnagxE+4EapCq0NZ/x6yO5VTwwp
  210. NljdFAEk40uGuKyn1QA3uNMHy5DlpLl+tU7t1KEovdZ+OVYsYKZhVzw0MTpKogk9
  211. JI7AN0q62ronPskAEQEAAQAL+O8BUSt1ZCVjPSIXIsrR+ZOSkszZwgJ1CWIoh0IH
  212. YD2vmcMHGIhFYgBdgerpvhptKhaw7GcXDScEnYkyh5s4GE2hxclik1tbj/x1gYCN
  213. 8BNoyeDdPFxQG73qN12D99QYEctpOsz9xPLIDwmL0j1ehAfhwqHIAPm9Ca+i8JYM
  214. x/F+35S/jnKDXRI+NVlwbiEyXKXxxIqNlpy9i8sDBGexO5H5Sg0zSN/B1duLekGD
  215. biDw6gLc6bCgnS+0JOUpU07Z2fccMOY9ncjKGD2uIb/ePPUaek92GCQyq0eorCIV
  216. brcQsRc5sSsNtnRKQTQtxioROeDg7kf2oWySeHTswlXW/219ihrSXgteHJd+rPm7
  217. DYLEeGLRny8bRKv8rQdAtApHaJE4dAATXeY4RYo4NlXHYaztGYtU6kiM/3zCfWAe
  218. 9Nn+Wh9jMTZrjefUCagS5r6ZqAh7veNo/vgIGaCLh0a1Ypa0Yk9KFrn3LYEM3zgk
  219. 3m3bn+7qgy5cUYXoJ3DGJJEhBgDPonpW0WElqLs5ZMem1ha85SC38F0IkAaSuzuz
  220. v3eORiKWuyJGF32Q2XHa1RHQs1JtUKd8rxFer3b8Oq71zLz6JtVc9dmRudvgcJYX
  221. 0PC11F6WGjZFSSp39dajFp0A5DKUs39F3w7J1yuDM56TDIN810ywufGAHARY1pZb
  222. UJAy/dTqjFnCbNjpAakor3hVzqxcmUG+7Y2X9c2AGncT1MqAQC3M8JZcuZvkK8A9
  223. cMk8B914ryYE7VsZMdMhyTwHmykGAPgNLLa3RDETeGeGCKWI+ZPOoU0ib5JtJZ1d
  224. P3tNwfZKuZBZXKW9gqYqyBa/qhMip84SP30pr/TvulcdAFC759HK8sQZyJ6Vw24P
  225. c+5ssRxrQUEw1rvJPWhmQCmCOZHBMQl5T6eaTOpR5u3aUKTMlxPKhK9eC1dCSTnI
  226. /nyL8An3VKnLy+K/LI42YGphBVLLJmBewuTVDIJviWRdntiG8dElyEJMOywUltk3
  227. 2CEmqgsD9tPO8rXZjnMrMn3gfsiaoQYA6/6/e2utkHr7gAoWBgrBBdqVHsvqh5Ro
  228. 2DjLAOpZItO/EdCJfDAmbTYOa04535sBDP2tcH/vipPOPpbr1Y9Y/mNsKCulNxed
  229. yqAmEkKOcerLUP5UHju0AB6VBjHJFdU2mqT+UjPyBk7WeKXgFomyoYMv3KpNOFWR
  230. xi0Xji4kKHbttA6Hy3UcGPr9acyUAlDYeKmxbSUYIPhw32bbGrX9+F5YriTufRsG
  231. 3jftQVo9zqdcQSD/5pUTMn3EYbEcohYB2YWJAbYEGAEIACACGwwWIQRqkzk/UMXm
  232. rNPW+0W5NiEu204UwAUCYNwR6wAKCRC5NiEu204UwOPnC/92PgB1c3h9FBXH1maz
  233. g29fndHIHH65VLgqMiQ7HAMojwRlT5Xnj5tdkCBmszRkv5vMvdJRa3ZY8Ed/Inqr
  234. hxBFNzpjqX4oj/RYIQLKXWWfkTKYVLJFZFPCSo00jesw2gieu3Ke/Yy4gwhtNodA
  235. v+s6QNMvffTW/K3XNrWDB0E7/LXbdidzhm+MBu8ov2tuC3tp9liLICiE1jv/2xT4
  236. CNSO6yphmk1/1zEYHS/mN9qJ2csBmte2cdmGyOcuVEHk3pyINNMDOamaURBJGRwF
  237. XB5V7gTKUFU4jCp3chywKrBHJHxGGDUmPBmZtDtfWAOgL32drK7/KUyzZL/WO7Fj
  238. akOI0hRDFOcqTYWL20H7+hAiX3oHMP7eou3L5C7wJ9+JMcACklN/WMjG9a536DFJ
  239. 4UgZ6HyKPP+wy837Hbe8b25kNMBwFgiaLR0lcgzxj7NyQWjVCMOEN+M55tRCjvL6
  240. ya6JVZCRbMXfdCy8lVPgtNQ6VlHaj8Wvnn2FLbWWO2n2r3s=
  241. =9zU5
  242. -----END PGP PRIVATE KEY BLOCK-----
  243. """
  244. NON_DEFAULT_KEY_ID = "6A93393F50C5E6ACD3D6FB45B936212EDB4E14C0"
  245. def setUp(self):
  246. super(PorcelainGpgTestCase, self).setUp()
  247. self.gpg_dir = os.path.join(self.test_dir, "gpg")
  248. os.mkdir(self.gpg_dir, mode=0o700)
  249. self.addCleanup(shutil.rmtree, self.gpg_dir)
  250. self._old_gnupghome = os.environ.get("GNUPGHOME")
  251. os.environ["GNUPGHOME"] = self.gpg_dir
  252. if self._old_gnupghome is None:
  253. self.addCleanup(os.environ.__delitem__, "GNUPGHOME")
  254. else:
  255. self.addCleanup(os.environ.__setitem__, "GNUPGHOME", self._old_gnupghome)
  256. def import_default_key(self):
  257. subprocess.run(
  258. ["gpg", "--import"],
  259. stdout=subprocess.DEVNULL,
  260. stderr=subprocess.DEVNULL,
  261. input=PorcelainGpgTestCase.DEFAULT_KEY,
  262. universal_newlines=True,
  263. )
  264. def import_non_default_key(self):
  265. subprocess.run(
  266. ["gpg", "--import"],
  267. stdout=subprocess.DEVNULL,
  268. stderr=subprocess.DEVNULL,
  269. input=PorcelainGpgTestCase.NON_DEFAULT_KEY,
  270. universal_newlines=True,
  271. )
  272. class ArchiveTests(PorcelainTestCase):
  273. """Tests for the archive command."""
  274. def test_simple(self):
  275. c1, c2, c3 = build_commit_graph(
  276. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  277. )
  278. self.repo.refs[b"refs/heads/master"] = c3.id
  279. out = BytesIO()
  280. err = BytesIO()
  281. porcelain.archive(
  282. self.repo.path, b"refs/heads/master", outstream=out, errstream=err
  283. )
  284. self.assertEqual(b"", err.getvalue())
  285. tf = tarfile.TarFile(fileobj=out)
  286. self.addCleanup(tf.close)
  287. self.assertEqual([], tf.getnames())
  288. class UpdateServerInfoTests(PorcelainTestCase):
  289. def test_simple(self):
  290. c1, c2, c3 = build_commit_graph(
  291. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  292. )
  293. self.repo.refs[b"refs/heads/foo"] = c3.id
  294. porcelain.update_server_info(self.repo.path)
  295. self.assertTrue(
  296. os.path.exists(os.path.join(self.repo.controldir(), "info", "refs"))
  297. )
  298. class CommitTests(PorcelainTestCase):
  299. def test_custom_author(self):
  300. c1, c2, c3 = build_commit_graph(
  301. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  302. )
  303. self.repo.refs[b"refs/heads/foo"] = c3.id
  304. sha = porcelain.commit(
  305. self.repo.path,
  306. message=b"Some message",
  307. author=b"Joe <joe@example.com>",
  308. committer=b"Bob <bob@example.com>",
  309. )
  310. self.assertIsInstance(sha, bytes)
  311. self.assertEqual(len(sha), 40)
  312. def test_unicode(self):
  313. c1, c2, c3 = build_commit_graph(
  314. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  315. )
  316. self.repo.refs[b"refs/heads/foo"] = c3.id
  317. sha = porcelain.commit(
  318. self.repo.path,
  319. message="Some message",
  320. author="Joe <joe@example.com>",
  321. committer="Bob <bob@example.com>",
  322. )
  323. self.assertIsInstance(sha, bytes)
  324. self.assertEqual(len(sha), 40)
  325. def test_no_verify(self):
  326. if os.name != "posix":
  327. self.skipTest("shell hook tests requires POSIX shell")
  328. self.assertTrue(os.path.exists("/bin/sh"))
  329. hooks_dir = os.path.join(self.repo.controldir(), "hooks")
  330. os.makedirs(hooks_dir, exist_ok=True)
  331. self.addCleanup(shutil.rmtree, hooks_dir)
  332. c1, c2, c3 = build_commit_graph(
  333. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  334. )
  335. hook_fail = "#!/bin/sh\nexit 1"
  336. # hooks are executed in pre-commit, commit-msg order
  337. # test commit-msg failure first, then pre-commit failure, then
  338. # no_verify to skip both hooks
  339. commit_msg = os.path.join(hooks_dir, "commit-msg")
  340. with open(commit_msg, "w") as f:
  341. f.write(hook_fail)
  342. os.chmod(commit_msg, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  343. with self.assertRaises(CommitError):
  344. porcelain.commit(
  345. self.repo.path,
  346. message="Some message",
  347. author="Joe <joe@example.com>",
  348. committer="Bob <bob@example.com>",
  349. )
  350. pre_commit = os.path.join(hooks_dir, "pre-commit")
  351. with open(pre_commit, "w") as f:
  352. f.write(hook_fail)
  353. os.chmod(pre_commit, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
  354. with self.assertRaises(CommitError):
  355. porcelain.commit(
  356. self.repo.path,
  357. message="Some message",
  358. author="Joe <joe@example.com>",
  359. committer="Bob <bob@example.com>",
  360. )
  361. sha = porcelain.commit(
  362. self.repo.path,
  363. message="Some message",
  364. author="Joe <joe@example.com>",
  365. committer="Bob <bob@example.com>",
  366. no_verify=True,
  367. )
  368. self.assertIsInstance(sha, bytes)
  369. self.assertEqual(len(sha), 40)
  370. class CleanTests(PorcelainTestCase):
  371. def put_files(self, tracked, ignored, untracked, empty_dirs):
  372. """Put the described files in the wd"""
  373. all_files = tracked | ignored | untracked
  374. for file_path in all_files:
  375. abs_path = os.path.join(self.repo.path, file_path)
  376. # File may need to be written in a dir that doesn't exist yet, so
  377. # create the parent dir(s) as necessary
  378. parent_dir = os.path.dirname(abs_path)
  379. try:
  380. os.makedirs(parent_dir)
  381. except FileExistsError:
  382. pass
  383. with open(abs_path, "w") as f:
  384. f.write("")
  385. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  386. f.writelines(ignored)
  387. for dir_path in empty_dirs:
  388. os.mkdir(os.path.join(self.repo.path, "empty_dir"))
  389. files_to_add = [os.path.join(self.repo.path, t) for t in tracked]
  390. porcelain.add(repo=self.repo.path, paths=files_to_add)
  391. porcelain.commit(repo=self.repo.path, message="init commit")
  392. def assert_wd(self, expected_paths):
  393. """Assert paths of files and dirs in wd are same as expected_paths"""
  394. control_dir_rel = os.path.relpath(self.repo._controldir, self.repo.path)
  395. # normalize paths to simplify comparison across platforms
  396. found_paths = {
  397. os.path.normpath(p)
  398. for p in flat_walk_dir(self.repo.path)
  399. if not p.split(os.sep)[0] == control_dir_rel
  400. }
  401. norm_expected_paths = {os.path.normpath(p) for p in expected_paths}
  402. self.assertEqual(found_paths, norm_expected_paths)
  403. def test_from_root(self):
  404. self.put_files(
  405. tracked={"tracked_file", "tracked_dir/tracked_file", ".gitignore"},
  406. ignored={"ignored_file"},
  407. untracked={
  408. "untracked_file",
  409. "tracked_dir/untracked_dir/untracked_file",
  410. "untracked_dir/untracked_dir/untracked_file",
  411. },
  412. empty_dirs={"empty_dir"},
  413. )
  414. porcelain.clean(repo=self.repo.path, target_dir=self.repo.path)
  415. self.assert_wd(
  416. {
  417. "tracked_file",
  418. "tracked_dir/tracked_file",
  419. ".gitignore",
  420. "ignored_file",
  421. "tracked_dir",
  422. }
  423. )
  424. def test_from_subdir(self):
  425. self.put_files(
  426. tracked={"tracked_file", "tracked_dir/tracked_file", ".gitignore"},
  427. ignored={"ignored_file"},
  428. untracked={
  429. "untracked_file",
  430. "tracked_dir/untracked_dir/untracked_file",
  431. "untracked_dir/untracked_dir/untracked_file",
  432. },
  433. empty_dirs={"empty_dir"},
  434. )
  435. porcelain.clean(
  436. repo=self.repo,
  437. target_dir=os.path.join(self.repo.path, "untracked_dir"),
  438. )
  439. self.assert_wd(
  440. {
  441. "tracked_file",
  442. "tracked_dir/tracked_file",
  443. ".gitignore",
  444. "ignored_file",
  445. "untracked_file",
  446. "tracked_dir/untracked_dir/untracked_file",
  447. "empty_dir",
  448. "untracked_dir",
  449. "tracked_dir",
  450. "tracked_dir/untracked_dir",
  451. }
  452. )
  453. class CloneTests(PorcelainTestCase):
  454. def test_simple_local(self):
  455. f1_1 = make_object(Blob, data=b"f1")
  456. commit_spec = [[1], [2, 1], [3, 1, 2]]
  457. trees = {
  458. 1: [(b"f1", f1_1), (b"f2", f1_1)],
  459. 2: [(b"f1", f1_1), (b"f2", f1_1)],
  460. 3: [(b"f1", f1_1), (b"f2", f1_1)],
  461. }
  462. c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees)
  463. self.repo.refs[b"refs/heads/master"] = c3.id
  464. self.repo.refs[b"refs/tags/foo"] = c3.id
  465. target_path = tempfile.mkdtemp()
  466. errstream = BytesIO()
  467. self.addCleanup(shutil.rmtree, target_path)
  468. r = porcelain.clone(
  469. self.repo.path, target_path, checkout=False, errstream=errstream
  470. )
  471. self.addCleanup(r.close)
  472. self.assertEqual(r.path, target_path)
  473. target_repo = Repo(target_path)
  474. self.assertEqual(0, len(target_repo.open_index()))
  475. self.assertEqual(c3.id, target_repo.refs[b"refs/tags/foo"])
  476. self.assertNotIn(b"f1", os.listdir(target_path))
  477. self.assertNotIn(b"f2", os.listdir(target_path))
  478. c = r.get_config()
  479. encoded_path = self.repo.path
  480. if not isinstance(encoded_path, bytes):
  481. encoded_path = encoded_path.encode("utf-8")
  482. self.assertEqual(encoded_path, c.get((b"remote", b"origin"), b"url"))
  483. self.assertEqual(
  484. b"+refs/heads/*:refs/remotes/origin/*",
  485. c.get((b"remote", b"origin"), b"fetch"),
  486. )
  487. def test_simple_local_with_checkout(self):
  488. f1_1 = make_object(Blob, data=b"f1")
  489. commit_spec = [[1], [2, 1], [3, 1, 2]]
  490. trees = {
  491. 1: [(b"f1", f1_1), (b"f2", f1_1)],
  492. 2: [(b"f1", f1_1), (b"f2", f1_1)],
  493. 3: [(b"f1", f1_1), (b"f2", f1_1)],
  494. }
  495. c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees)
  496. self.repo.refs[b"refs/heads/master"] = c3.id
  497. target_path = tempfile.mkdtemp()
  498. errstream = BytesIO()
  499. self.addCleanup(shutil.rmtree, target_path)
  500. with porcelain.clone(
  501. self.repo.path, target_path, checkout=True, errstream=errstream
  502. ) as r:
  503. self.assertEqual(r.path, target_path)
  504. with Repo(target_path) as r:
  505. self.assertEqual(r.head(), c3.id)
  506. self.assertIn("f1", os.listdir(target_path))
  507. self.assertIn("f2", os.listdir(target_path))
  508. def test_bare_local_with_checkout(self):
  509. f1_1 = make_object(Blob, data=b"f1")
  510. commit_spec = [[1], [2, 1], [3, 1, 2]]
  511. trees = {
  512. 1: [(b"f1", f1_1), (b"f2", f1_1)],
  513. 2: [(b"f1", f1_1), (b"f2", f1_1)],
  514. 3: [(b"f1", f1_1), (b"f2", f1_1)],
  515. }
  516. c1, c2, c3 = build_commit_graph(self.repo.object_store, commit_spec, trees)
  517. self.repo.refs[b"refs/heads/master"] = c3.id
  518. target_path = tempfile.mkdtemp()
  519. errstream = BytesIO()
  520. self.addCleanup(shutil.rmtree, target_path)
  521. with porcelain.clone(
  522. self.repo.path, target_path, bare=True, errstream=errstream
  523. ) as r:
  524. self.assertEqual(r.path, target_path)
  525. with Repo(target_path) as r:
  526. r.head()
  527. self.assertRaises(NoIndexPresent, r.open_index)
  528. self.assertNotIn(b"f1", os.listdir(target_path))
  529. self.assertNotIn(b"f2", os.listdir(target_path))
  530. def test_no_checkout_with_bare(self):
  531. f1_1 = make_object(Blob, data=b"f1")
  532. commit_spec = [[1]]
  533. trees = {1: [(b"f1", f1_1), (b"f2", f1_1)]}
  534. (c1,) = build_commit_graph(self.repo.object_store, commit_spec, trees)
  535. self.repo.refs[b"refs/heads/master"] = c1.id
  536. self.repo.refs[b"HEAD"] = c1.id
  537. target_path = tempfile.mkdtemp()
  538. errstream = BytesIO()
  539. self.addCleanup(shutil.rmtree, target_path)
  540. self.assertRaises(
  541. porcelain.Error,
  542. porcelain.clone,
  543. self.repo.path,
  544. target_path,
  545. checkout=True,
  546. bare=True,
  547. errstream=errstream,
  548. )
  549. def test_no_head_no_checkout(self):
  550. f1_1 = make_object(Blob, data=b"f1")
  551. commit_spec = [[1]]
  552. trees = {1: [(b"f1", f1_1), (b"f2", f1_1)]}
  553. (c1,) = build_commit_graph(self.repo.object_store, commit_spec, trees)
  554. self.repo.refs[b"refs/heads/master"] = c1.id
  555. target_path = tempfile.mkdtemp()
  556. self.addCleanup(shutil.rmtree, target_path)
  557. errstream = BytesIO()
  558. r = porcelain.clone(
  559. self.repo.path, target_path, checkout=True, errstream=errstream
  560. )
  561. r.close()
  562. def test_no_head_no_checkout_outstream_errstream_autofallback(self):
  563. f1_1 = make_object(Blob, data=b"f1")
  564. commit_spec = [[1]]
  565. trees = {1: [(b"f1", f1_1), (b"f2", f1_1)]}
  566. (c1,) = build_commit_graph(self.repo.object_store, commit_spec, trees)
  567. self.repo.refs[b"refs/heads/master"] = c1.id
  568. target_path = tempfile.mkdtemp()
  569. self.addCleanup(shutil.rmtree, target_path)
  570. errstream = porcelain.NoneStream()
  571. r = porcelain.clone(
  572. self.repo.path, target_path, checkout=True, errstream=errstream
  573. )
  574. r.close()
  575. def test_source_broken(self):
  576. with tempfile.TemporaryDirectory() as parent:
  577. target_path = os.path.join(parent, "target")
  578. self.assertRaises(
  579. Exception, porcelain.clone, "/nonexistant/repo", target_path
  580. )
  581. self.assertFalse(os.path.exists(target_path))
  582. def test_fetch_symref(self):
  583. f1_1 = make_object(Blob, data=b"f1")
  584. trees = {1: [(b"f1", f1_1), (b"f2", f1_1)]}
  585. [c1] = build_commit_graph(self.repo.object_store, [[1]], trees)
  586. self.repo.refs.set_symbolic_ref(b"HEAD", b"refs/heads/else")
  587. self.repo.refs[b"refs/heads/else"] = c1.id
  588. target_path = tempfile.mkdtemp()
  589. errstream = BytesIO()
  590. self.addCleanup(shutil.rmtree, target_path)
  591. r = porcelain.clone(
  592. self.repo.path, target_path, checkout=False, errstream=errstream
  593. )
  594. self.addCleanup(r.close)
  595. self.assertEqual(r.path, target_path)
  596. target_repo = Repo(target_path)
  597. self.assertEqual(0, len(target_repo.open_index()))
  598. self.assertEqual(c1.id, target_repo.refs[b"refs/heads/else"])
  599. self.assertEqual(c1.id, target_repo.refs[b"HEAD"])
  600. self.assertEqual(
  601. {b"HEAD": b"refs/heads/else", b"refs/remotes/origin/HEAD": b"refs/remotes/origin/else"},
  602. target_repo.refs.get_symrefs(),
  603. )
  604. class InitTests(TestCase):
  605. def test_non_bare(self):
  606. repo_dir = tempfile.mkdtemp()
  607. self.addCleanup(shutil.rmtree, repo_dir)
  608. porcelain.init(repo_dir)
  609. def test_bare(self):
  610. repo_dir = tempfile.mkdtemp()
  611. self.addCleanup(shutil.rmtree, repo_dir)
  612. porcelain.init(repo_dir, bare=True)
  613. class AddTests(PorcelainTestCase):
  614. def test_add_default_paths(self):
  615. # create a file for initial commit
  616. fullpath = os.path.join(self.repo.path, "blah")
  617. with open(fullpath, "w") as f:
  618. f.write("\n")
  619. porcelain.add(repo=self.repo.path, paths=[fullpath])
  620. porcelain.commit(
  621. repo=self.repo.path,
  622. message=b"test",
  623. author=b"test <email>",
  624. committer=b"test <email>",
  625. )
  626. # Add a second test file and a file in a directory
  627. with open(os.path.join(self.repo.path, "foo"), "w") as f:
  628. f.write("\n")
  629. os.mkdir(os.path.join(self.repo.path, "adir"))
  630. with open(os.path.join(self.repo.path, "adir", "afile"), "w") as f:
  631. f.write("\n")
  632. cwd = os.getcwd()
  633. try:
  634. os.chdir(self.repo.path)
  635. self.assertEqual(set(["foo", "blah", "adir", ".git"]), set(os.listdir(".")))
  636. self.assertEqual(
  637. (["foo", os.path.join("adir", "afile")], set()),
  638. porcelain.add(self.repo.path),
  639. )
  640. finally:
  641. os.chdir(cwd)
  642. # Check that foo was added and nothing in .git was modified
  643. index = self.repo.open_index()
  644. self.assertEqual(sorted(index), [b"adir/afile", b"blah", b"foo"])
  645. def test_add_default_paths_subdir(self):
  646. os.mkdir(os.path.join(self.repo.path, "foo"))
  647. with open(os.path.join(self.repo.path, "blah"), "w") as f:
  648. f.write("\n")
  649. with open(os.path.join(self.repo.path, "foo", "blie"), "w") as f:
  650. f.write("\n")
  651. cwd = os.getcwd()
  652. try:
  653. os.chdir(os.path.join(self.repo.path, "foo"))
  654. porcelain.add(repo=self.repo.path)
  655. porcelain.commit(
  656. repo=self.repo.path,
  657. message=b"test",
  658. author=b"test <email>",
  659. committer=b"test <email>",
  660. )
  661. finally:
  662. os.chdir(cwd)
  663. index = self.repo.open_index()
  664. self.assertEqual(sorted(index), [b"foo/blie"])
  665. def test_add_file(self):
  666. fullpath = os.path.join(self.repo.path, "foo")
  667. with open(fullpath, "w") as f:
  668. f.write("BAR")
  669. porcelain.add(self.repo.path, paths=[fullpath])
  670. self.assertIn(b"foo", self.repo.open_index())
  671. def test_add_ignored(self):
  672. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  673. f.write("foo\nsubdir/")
  674. with open(os.path.join(self.repo.path, "foo"), "w") as f:
  675. f.write("BAR")
  676. with open(os.path.join(self.repo.path, "bar"), "w") as f:
  677. f.write("BAR")
  678. os.mkdir(os.path.join(self.repo.path, "subdir"))
  679. with open(os.path.join(self.repo.path, "subdir", "baz"), "w") as f:
  680. f.write("BAZ")
  681. (added, ignored) = porcelain.add(
  682. self.repo.path,
  683. paths=[
  684. os.path.join(self.repo.path, "foo"),
  685. os.path.join(self.repo.path, "bar"),
  686. os.path.join(self.repo.path, "subdir"),
  687. ],
  688. )
  689. self.assertIn(b"bar", self.repo.open_index())
  690. self.assertEqual(set(["bar"]), set(added))
  691. self.assertEqual(set(["foo", os.path.join("subdir", "")]), ignored)
  692. def test_add_file_absolute_path(self):
  693. # Absolute paths are (not yet) supported
  694. with open(os.path.join(self.repo.path, "foo"), "w") as f:
  695. f.write("BAR")
  696. porcelain.add(self.repo, paths=[os.path.join(self.repo.path, "foo")])
  697. self.assertIn(b"foo", self.repo.open_index())
  698. def test_add_not_in_repo(self):
  699. with open(os.path.join(self.test_dir, "foo"), "w") as f:
  700. f.write("BAR")
  701. self.assertRaises(
  702. ValueError,
  703. porcelain.add,
  704. self.repo,
  705. paths=[os.path.join(self.test_dir, "foo")],
  706. )
  707. self.assertRaises(
  708. (ValueError, FileNotFoundError),
  709. porcelain.add,
  710. self.repo,
  711. paths=["../foo"],
  712. )
  713. self.assertEqual([], list(self.repo.open_index()))
  714. def test_add_file_clrf_conversion(self):
  715. # Set the right configuration to the repo
  716. c = self.repo.get_config()
  717. c.set("core", "autocrlf", "input")
  718. c.write_to_path()
  719. # Add a file with CRLF line-ending
  720. fullpath = os.path.join(self.repo.path, "foo")
  721. with open(fullpath, "wb") as f:
  722. f.write(b"line1\r\nline2")
  723. porcelain.add(self.repo.path, paths=[fullpath])
  724. # The line-endings should have been converted to LF
  725. index = self.repo.open_index()
  726. self.assertIn(b"foo", index)
  727. entry = index[b"foo"]
  728. blob = self.repo[entry.sha]
  729. self.assertEqual(blob.data, b"line1\nline2")
  730. class RemoveTests(PorcelainTestCase):
  731. def test_remove_file(self):
  732. fullpath = os.path.join(self.repo.path, "foo")
  733. with open(fullpath, "w") as f:
  734. f.write("BAR")
  735. porcelain.add(self.repo.path, paths=[fullpath])
  736. porcelain.commit(
  737. repo=self.repo,
  738. message=b"test",
  739. author=b"test <email>",
  740. committer=b"test <email>",
  741. )
  742. self.assertTrue(os.path.exists(os.path.join(self.repo.path, "foo")))
  743. cwd = os.getcwd()
  744. try:
  745. os.chdir(self.repo.path)
  746. porcelain.remove(self.repo.path, paths=["foo"])
  747. finally:
  748. os.chdir(cwd)
  749. self.assertFalse(os.path.exists(os.path.join(self.repo.path, "foo")))
  750. def test_remove_file_staged(self):
  751. fullpath = os.path.join(self.repo.path, "foo")
  752. with open(fullpath, "w") as f:
  753. f.write("BAR")
  754. cwd = os.getcwd()
  755. try:
  756. os.chdir(self.repo.path)
  757. porcelain.add(self.repo.path, paths=[fullpath])
  758. self.assertRaises(Exception, porcelain.rm, self.repo.path, paths=["foo"])
  759. finally:
  760. os.chdir(cwd)
  761. def test_remove_file_removed_on_disk(self):
  762. fullpath = os.path.join(self.repo.path, "foo")
  763. with open(fullpath, "w") as f:
  764. f.write("BAR")
  765. porcelain.add(self.repo.path, paths=[fullpath])
  766. cwd = os.getcwd()
  767. try:
  768. os.chdir(self.repo.path)
  769. os.remove(fullpath)
  770. porcelain.remove(self.repo.path, paths=["foo"])
  771. finally:
  772. os.chdir(cwd)
  773. self.assertFalse(os.path.exists(os.path.join(self.repo.path, "foo")))
  774. class LogTests(PorcelainTestCase):
  775. def test_simple(self):
  776. c1, c2, c3 = build_commit_graph(
  777. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  778. )
  779. self.repo.refs[b"HEAD"] = c3.id
  780. outstream = StringIO()
  781. porcelain.log(self.repo.path, outstream=outstream)
  782. self.assertEqual(3, outstream.getvalue().count("-" * 50))
  783. def test_max_entries(self):
  784. c1, c2, c3 = build_commit_graph(
  785. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  786. )
  787. self.repo.refs[b"HEAD"] = c3.id
  788. outstream = StringIO()
  789. porcelain.log(self.repo.path, outstream=outstream, max_entries=1)
  790. self.assertEqual(1, outstream.getvalue().count("-" * 50))
  791. class ShowTests(PorcelainTestCase):
  792. def test_nolist(self):
  793. c1, c2, c3 = build_commit_graph(
  794. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  795. )
  796. self.repo.refs[b"HEAD"] = c3.id
  797. outstream = StringIO()
  798. porcelain.show(self.repo.path, objects=c3.id, outstream=outstream)
  799. self.assertTrue(outstream.getvalue().startswith("-" * 50))
  800. def test_simple(self):
  801. c1, c2, c3 = build_commit_graph(
  802. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  803. )
  804. self.repo.refs[b"HEAD"] = c3.id
  805. outstream = StringIO()
  806. porcelain.show(self.repo.path, objects=[c3.id], outstream=outstream)
  807. self.assertTrue(outstream.getvalue().startswith("-" * 50))
  808. def test_blob(self):
  809. b = Blob.from_string(b"The Foo\n")
  810. self.repo.object_store.add_object(b)
  811. outstream = StringIO()
  812. porcelain.show(self.repo.path, objects=[b.id], outstream=outstream)
  813. self.assertEqual(outstream.getvalue(), "The Foo\n")
  814. def test_commit_no_parent(self):
  815. a = Blob.from_string(b"The Foo\n")
  816. ta = Tree()
  817. ta.add(b"somename", 0o100644, a.id)
  818. ca = make_commit(tree=ta.id)
  819. self.repo.object_store.add_objects([(a, None), (ta, None), (ca, None)])
  820. outstream = StringIO()
  821. porcelain.show(self.repo.path, objects=[ca.id], outstream=outstream)
  822. self.assertMultiLineEqual(
  823. outstream.getvalue(),
  824. """\
  825. --------------------------------------------------
  826. commit: 344da06c1bb85901270b3e8875c988a027ec087d
  827. Author: Test Author <test@nodomain.com>
  828. Committer: Test Committer <test@nodomain.com>
  829. Date: Fri Jan 01 2010 00:00:00 +0000
  830. Test message.
  831. diff --git a/somename b/somename
  832. new file mode 100644
  833. index 0000000..ea5c7bf
  834. --- /dev/null
  835. +++ b/somename
  836. @@ -0,0 +1 @@
  837. +The Foo
  838. """,
  839. )
  840. def test_tag(self):
  841. a = Blob.from_string(b"The Foo\n")
  842. ta = Tree()
  843. ta.add(b"somename", 0o100644, a.id)
  844. ca = make_commit(tree=ta.id)
  845. self.repo.object_store.add_objects([(a, None), (ta, None), (ca, None)])
  846. porcelain.tag_create(
  847. self.repo.path,
  848. b"tryme",
  849. b"foo <foo@bar.com>",
  850. b"bar",
  851. annotated=True,
  852. objectish=ca.id,
  853. tag_time=1552854211,
  854. tag_timezone=0,
  855. )
  856. outstream = StringIO()
  857. porcelain.show(self.repo, objects=[b"refs/tags/tryme"], outstream=outstream)
  858. self.maxDiff = None
  859. self.assertMultiLineEqual(
  860. outstream.getvalue(),
  861. """\
  862. Tagger: foo <foo@bar.com>
  863. Date: Sun Mar 17 2019 20:23:31 +0000
  864. bar
  865. --------------------------------------------------
  866. commit: 344da06c1bb85901270b3e8875c988a027ec087d
  867. Author: Test Author <test@nodomain.com>
  868. Committer: Test Committer <test@nodomain.com>
  869. Date: Fri Jan 01 2010 00:00:00 +0000
  870. Test message.
  871. diff --git a/somename b/somename
  872. new file mode 100644
  873. index 0000000..ea5c7bf
  874. --- /dev/null
  875. +++ b/somename
  876. @@ -0,0 +1 @@
  877. +The Foo
  878. """,
  879. )
  880. def test_commit_with_change(self):
  881. a = Blob.from_string(b"The Foo\n")
  882. ta = Tree()
  883. ta.add(b"somename", 0o100644, a.id)
  884. ca = make_commit(tree=ta.id)
  885. b = Blob.from_string(b"The Bar\n")
  886. tb = Tree()
  887. tb.add(b"somename", 0o100644, b.id)
  888. cb = make_commit(tree=tb.id, parents=[ca.id])
  889. self.repo.object_store.add_objects(
  890. [
  891. (a, None),
  892. (b, None),
  893. (ta, None),
  894. (tb, None),
  895. (ca, None),
  896. (cb, None),
  897. ]
  898. )
  899. outstream = StringIO()
  900. porcelain.show(self.repo.path, objects=[cb.id], outstream=outstream)
  901. self.assertMultiLineEqual(
  902. outstream.getvalue(),
  903. """\
  904. --------------------------------------------------
  905. commit: 2c6b6c9cb72c130956657e1fdae58e5b103744fa
  906. Author: Test Author <test@nodomain.com>
  907. Committer: Test Committer <test@nodomain.com>
  908. Date: Fri Jan 01 2010 00:00:00 +0000
  909. Test message.
  910. diff --git a/somename b/somename
  911. index ea5c7bf..fd38bcb 100644
  912. --- a/somename
  913. +++ b/somename
  914. @@ -1 +1 @@
  915. -The Foo
  916. +The Bar
  917. """,
  918. )
  919. class SymbolicRefTests(PorcelainTestCase):
  920. def test_set_wrong_symbolic_ref(self):
  921. c1, c2, c3 = build_commit_graph(
  922. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  923. )
  924. self.repo.refs[b"HEAD"] = c3.id
  925. self.assertRaises(
  926. porcelain.Error, porcelain.symbolic_ref, self.repo.path, b"foobar"
  927. )
  928. def test_set_force_wrong_symbolic_ref(self):
  929. c1, c2, c3 = build_commit_graph(
  930. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  931. )
  932. self.repo.refs[b"HEAD"] = c3.id
  933. porcelain.symbolic_ref(self.repo.path, b"force_foobar", force=True)
  934. # test if we actually changed the file
  935. with self.repo.get_named_file("HEAD") as f:
  936. new_ref = f.read()
  937. self.assertEqual(new_ref, b"ref: refs/heads/force_foobar\n")
  938. def test_set_symbolic_ref(self):
  939. c1, c2, c3 = build_commit_graph(
  940. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  941. )
  942. self.repo.refs[b"HEAD"] = c3.id
  943. porcelain.symbolic_ref(self.repo.path, b"master")
  944. def test_set_symbolic_ref_other_than_master(self):
  945. c1, c2, c3 = build_commit_graph(
  946. self.repo.object_store,
  947. [[1], [2, 1], [3, 1, 2]],
  948. attrs=dict(refs="develop"),
  949. )
  950. self.repo.refs[b"HEAD"] = c3.id
  951. self.repo.refs[b"refs/heads/develop"] = c3.id
  952. porcelain.symbolic_ref(self.repo.path, b"develop")
  953. # test if we actually changed the file
  954. with self.repo.get_named_file("HEAD") as f:
  955. new_ref = f.read()
  956. self.assertEqual(new_ref, b"ref: refs/heads/develop\n")
  957. class DiffTreeTests(PorcelainTestCase):
  958. def test_empty(self):
  959. c1, c2, c3 = build_commit_graph(
  960. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  961. )
  962. self.repo.refs[b"HEAD"] = c3.id
  963. outstream = BytesIO()
  964. porcelain.diff_tree(self.repo.path, c2.tree, c3.tree, outstream=outstream)
  965. self.assertEqual(outstream.getvalue(), b"")
  966. class CommitTreeTests(PorcelainTestCase):
  967. def test_simple(self):
  968. c1, c2, c3 = build_commit_graph(
  969. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  970. )
  971. b = Blob()
  972. b.data = b"foo the bar"
  973. t = Tree()
  974. t.add(b"somename", 0o100644, b.id)
  975. self.repo.object_store.add_object(t)
  976. self.repo.object_store.add_object(b)
  977. sha = porcelain.commit_tree(
  978. self.repo.path,
  979. t.id,
  980. message=b"Withcommit.",
  981. author=b"Joe <joe@example.com>",
  982. committer=b"Jane <jane@example.com>",
  983. )
  984. self.assertIsInstance(sha, bytes)
  985. self.assertEqual(len(sha), 40)
  986. class RevListTests(PorcelainTestCase):
  987. def test_simple(self):
  988. c1, c2, c3 = build_commit_graph(
  989. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  990. )
  991. outstream = BytesIO()
  992. porcelain.rev_list(self.repo.path, [c3.id], outstream=outstream)
  993. self.assertEqual(
  994. c3.id + b"\n" + c2.id + b"\n" + c1.id + b"\n", outstream.getvalue()
  995. )
  996. @skipIf(platform.python_implementation() == "PyPy" or sys.platform == "win32", "gpgme not easily available or supported on Windows and PyPy")
  997. class TagCreateSignTests(PorcelainGpgTestCase):
  998. def test_default_key(self):
  999. import gpg
  1000. c1, c2, c3 = build_commit_graph(
  1001. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  1002. )
  1003. self.repo.refs[b"HEAD"] = c3.id
  1004. cfg = self.repo.get_config()
  1005. cfg.set(("user",), "signingKey", PorcelainGpgTestCase.DEFAULT_KEY_ID)
  1006. self.import_default_key()
  1007. porcelain.tag_create(
  1008. self.repo.path,
  1009. b"tryme",
  1010. b"foo <foo@bar.com>",
  1011. b"bar",
  1012. annotated=True,
  1013. sign=True,
  1014. )
  1015. tags = self.repo.refs.as_dict(b"refs/tags")
  1016. self.assertEqual(list(tags.keys()), [b"tryme"])
  1017. tag = self.repo[b"refs/tags/tryme"]
  1018. self.assertIsInstance(tag, Tag)
  1019. self.assertEqual(b"foo <foo@bar.com>", tag.tagger)
  1020. self.assertEqual(b"bar\n", tag.message)
  1021. self.assertRecentTimestamp(tag.tag_time)
  1022. tag = self.repo[b'refs/tags/tryme']
  1023. # GPG Signatures aren't deterministic, so we can't do a static assertion.
  1024. tag.verify()
  1025. tag.verify(keyids=[PorcelainGpgTestCase.DEFAULT_KEY_ID])
  1026. self.import_non_default_key()
  1027. self.assertRaises(
  1028. gpg.errors.MissingSignatures,
  1029. tag.verify,
  1030. keyids=[PorcelainGpgTestCase.NON_DEFAULT_KEY_ID],
  1031. )
  1032. tag._chunked_text = [b"bad data", tag._signature]
  1033. self.assertRaises(
  1034. gpg.errors.BadSignatures,
  1035. tag.verify,
  1036. )
  1037. def test_non_default_key(self):
  1038. c1, c2, c3 = build_commit_graph(
  1039. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  1040. )
  1041. self.repo.refs[b"HEAD"] = c3.id
  1042. cfg = self.repo.get_config()
  1043. cfg.set(("user",), "signingKey", PorcelainGpgTestCase.DEFAULT_KEY_ID)
  1044. self.import_non_default_key()
  1045. porcelain.tag_create(
  1046. self.repo.path,
  1047. b"tryme",
  1048. b"foo <foo@bar.com>",
  1049. b"bar",
  1050. annotated=True,
  1051. sign=PorcelainGpgTestCase.NON_DEFAULT_KEY_ID,
  1052. )
  1053. tags = self.repo.refs.as_dict(b"refs/tags")
  1054. self.assertEqual(list(tags.keys()), [b"tryme"])
  1055. tag = self.repo[b"refs/tags/tryme"]
  1056. self.assertIsInstance(tag, Tag)
  1057. self.assertEqual(b"foo <foo@bar.com>", tag.tagger)
  1058. self.assertEqual(b"bar\n", tag.message)
  1059. self.assertRecentTimestamp(tag.tag_time)
  1060. tag = self.repo[b'refs/tags/tryme']
  1061. # GPG Signatures aren't deterministic, so we can't do a static assertion.
  1062. tag.verify()
  1063. class TagCreateTests(PorcelainTestCase):
  1064. def test_annotated(self):
  1065. c1, c2, c3 = build_commit_graph(
  1066. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  1067. )
  1068. self.repo.refs[b"HEAD"] = c3.id
  1069. porcelain.tag_create(
  1070. self.repo.path,
  1071. b"tryme",
  1072. b"foo <foo@bar.com>",
  1073. b"bar",
  1074. annotated=True,
  1075. )
  1076. tags = self.repo.refs.as_dict(b"refs/tags")
  1077. self.assertEqual(list(tags.keys()), [b"tryme"])
  1078. tag = self.repo[b"refs/tags/tryme"]
  1079. self.assertIsInstance(tag, Tag)
  1080. self.assertEqual(b"foo <foo@bar.com>", tag.tagger)
  1081. self.assertEqual(b"bar\n", tag.message)
  1082. self.assertRecentTimestamp(tag.tag_time)
  1083. def test_unannotated(self):
  1084. c1, c2, c3 = build_commit_graph(
  1085. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  1086. )
  1087. self.repo.refs[b"HEAD"] = c3.id
  1088. porcelain.tag_create(self.repo.path, b"tryme", annotated=False)
  1089. tags = self.repo.refs.as_dict(b"refs/tags")
  1090. self.assertEqual(list(tags.keys()), [b"tryme"])
  1091. self.repo[b"refs/tags/tryme"]
  1092. self.assertEqual(list(tags.values()), [self.repo.head()])
  1093. def test_unannotated_unicode(self):
  1094. c1, c2, c3 = build_commit_graph(
  1095. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  1096. )
  1097. self.repo.refs[b"HEAD"] = c3.id
  1098. porcelain.tag_create(self.repo.path, "tryme", annotated=False)
  1099. tags = self.repo.refs.as_dict(b"refs/tags")
  1100. self.assertEqual(list(tags.keys()), [b"tryme"])
  1101. self.repo[b"refs/tags/tryme"]
  1102. self.assertEqual(list(tags.values()), [self.repo.head()])
  1103. class TagListTests(PorcelainTestCase):
  1104. def test_empty(self):
  1105. tags = porcelain.tag_list(self.repo.path)
  1106. self.assertEqual([], tags)
  1107. def test_simple(self):
  1108. self.repo.refs[b"refs/tags/foo"] = b"aa" * 20
  1109. self.repo.refs[b"refs/tags/bar/bla"] = b"bb" * 20
  1110. tags = porcelain.tag_list(self.repo.path)
  1111. self.assertEqual([b"bar/bla", b"foo"], tags)
  1112. class TagDeleteTests(PorcelainTestCase):
  1113. def test_simple(self):
  1114. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1115. self.repo[b"HEAD"] = c1.id
  1116. porcelain.tag_create(self.repo, b"foo")
  1117. self.assertIn(b"foo", porcelain.tag_list(self.repo))
  1118. porcelain.tag_delete(self.repo, b"foo")
  1119. self.assertNotIn(b"foo", porcelain.tag_list(self.repo))
  1120. class ResetTests(PorcelainTestCase):
  1121. def test_hard_head(self):
  1122. fullpath = os.path.join(self.repo.path, "foo")
  1123. with open(fullpath, "w") as f:
  1124. f.write("BAR")
  1125. porcelain.add(self.repo.path, paths=[fullpath])
  1126. porcelain.commit(
  1127. self.repo.path,
  1128. message=b"Some message",
  1129. committer=b"Jane <jane@example.com>",
  1130. author=b"John <john@example.com>",
  1131. )
  1132. with open(os.path.join(self.repo.path, "foo"), "wb") as f:
  1133. f.write(b"OOH")
  1134. porcelain.reset(self.repo, "hard", b"HEAD")
  1135. index = self.repo.open_index()
  1136. changes = list(
  1137. tree_changes(
  1138. self.repo,
  1139. index.commit(self.repo.object_store),
  1140. self.repo[b"HEAD"].tree,
  1141. )
  1142. )
  1143. self.assertEqual([], changes)
  1144. def test_hard_commit(self):
  1145. fullpath = os.path.join(self.repo.path, "foo")
  1146. with open(fullpath, "w") as f:
  1147. f.write("BAR")
  1148. porcelain.add(self.repo.path, paths=[fullpath])
  1149. sha = porcelain.commit(
  1150. self.repo.path,
  1151. message=b"Some message",
  1152. committer=b"Jane <jane@example.com>",
  1153. author=b"John <john@example.com>",
  1154. )
  1155. with open(fullpath, "wb") as f:
  1156. f.write(b"BAZ")
  1157. porcelain.add(self.repo.path, paths=[fullpath])
  1158. porcelain.commit(
  1159. self.repo.path,
  1160. message=b"Some other message",
  1161. committer=b"Jane <jane@example.com>",
  1162. author=b"John <john@example.com>",
  1163. )
  1164. porcelain.reset(self.repo, "hard", sha)
  1165. index = self.repo.open_index()
  1166. changes = list(
  1167. tree_changes(
  1168. self.repo,
  1169. index.commit(self.repo.object_store),
  1170. self.repo[sha].tree,
  1171. )
  1172. )
  1173. self.assertEqual([], changes)
  1174. class ResetFileTests(PorcelainTestCase):
  1175. def test_reset_modify_file_to_commit(self):
  1176. file = 'foo'
  1177. full_path = os.path.join(self.repo.path, file)
  1178. with open(full_path, 'w') as f:
  1179. f.write('hello')
  1180. porcelain.add(self.repo, paths=[full_path])
  1181. sha = porcelain.commit(
  1182. self.repo,
  1183. message=b"unitest",
  1184. committer=b"Jane <jane@example.com>",
  1185. author=b"John <john@example.com>",
  1186. )
  1187. with open(full_path, 'a') as f:
  1188. f.write('something new')
  1189. porcelain.reset_file(self.repo, file, target=sha)
  1190. with open(full_path, 'r') as f:
  1191. self.assertEqual('hello', f.read())
  1192. def test_reset_remove_file_to_commit(self):
  1193. file = 'foo'
  1194. full_path = os.path.join(self.repo.path, file)
  1195. with open(full_path, 'w') as f:
  1196. f.write('hello')
  1197. porcelain.add(self.repo, paths=[full_path])
  1198. sha = porcelain.commit(
  1199. self.repo,
  1200. message=b"unitest",
  1201. committer=b"Jane <jane@example.com>",
  1202. author=b"John <john@example.com>",
  1203. )
  1204. os.remove(full_path)
  1205. porcelain.reset_file(self.repo, file, target=sha)
  1206. with open(full_path, 'r') as f:
  1207. self.assertEqual('hello', f.read())
  1208. def test_resetfile_with_dir(self):
  1209. os.mkdir(os.path.join(self.repo.path, 'new_dir'))
  1210. full_path = os.path.join(self.repo.path, 'new_dir', 'foo')
  1211. with open(full_path, 'w') as f:
  1212. f.write('hello')
  1213. porcelain.add(self.repo, paths=[full_path])
  1214. sha = porcelain.commit(
  1215. self.repo,
  1216. message=b"unitest",
  1217. committer=b"Jane <jane@example.com>",
  1218. author=b"John <john@example.com>",
  1219. )
  1220. with open(full_path, 'a') as f:
  1221. f.write('something new')
  1222. porcelain.commit(
  1223. self.repo,
  1224. message=b"unitest 2",
  1225. committer=b"Jane <jane@example.com>",
  1226. author=b"John <john@example.com>",
  1227. )
  1228. porcelain.reset_file(self.repo, os.path.join('new_dir', 'foo'), target=sha)
  1229. with open(full_path, 'r') as f:
  1230. self.assertEqual('hello', f.read())
  1231. class PushTests(PorcelainTestCase):
  1232. def test_simple(self):
  1233. """
  1234. Basic test of porcelain push where self.repo is the remote. First
  1235. clone the remote, commit a file to the clone, then push the changes
  1236. back to the remote.
  1237. """
  1238. outstream = BytesIO()
  1239. errstream = BytesIO()
  1240. porcelain.commit(
  1241. repo=self.repo.path,
  1242. message=b"init",
  1243. author=b"author <email>",
  1244. committer=b"committer <email>",
  1245. )
  1246. # Setup target repo cloned from temp test repo
  1247. clone_path = tempfile.mkdtemp()
  1248. self.addCleanup(shutil.rmtree, clone_path)
  1249. target_repo = porcelain.clone(
  1250. self.repo.path, target=clone_path, errstream=errstream
  1251. )
  1252. try:
  1253. self.assertEqual(target_repo[b"HEAD"], self.repo[b"HEAD"])
  1254. finally:
  1255. target_repo.close()
  1256. # create a second file to be pushed back to origin
  1257. handle, fullpath = tempfile.mkstemp(dir=clone_path)
  1258. os.close(handle)
  1259. porcelain.add(repo=clone_path, paths=[fullpath])
  1260. porcelain.commit(
  1261. repo=clone_path,
  1262. message=b"push",
  1263. author=b"author <email>",
  1264. committer=b"committer <email>",
  1265. )
  1266. # Setup a non-checked out branch in the remote
  1267. refs_path = b"refs/heads/foo"
  1268. new_id = self.repo[b"HEAD"].id
  1269. self.assertNotEqual(new_id, ZERO_SHA)
  1270. self.repo.refs[refs_path] = new_id
  1271. # Push to the remote
  1272. porcelain.push(
  1273. clone_path,
  1274. "origin",
  1275. b"HEAD:" + refs_path,
  1276. outstream=outstream,
  1277. errstream=errstream,
  1278. )
  1279. self.assertEqual(
  1280. target_repo.refs[b"refs/remotes/origin/foo"],
  1281. target_repo.refs[b"HEAD"],
  1282. )
  1283. # Check that the target and source
  1284. with Repo(clone_path) as r_clone:
  1285. self.assertEqual(
  1286. {
  1287. b"HEAD": new_id,
  1288. b"refs/heads/foo": r_clone[b"HEAD"].id,
  1289. b"refs/heads/master": new_id,
  1290. },
  1291. self.repo.get_refs(),
  1292. )
  1293. self.assertEqual(r_clone[b"HEAD"].id, self.repo[refs_path].id)
  1294. # Get the change in the target repo corresponding to the add
  1295. # this will be in the foo branch.
  1296. change = list(
  1297. tree_changes(
  1298. self.repo,
  1299. self.repo[b"HEAD"].tree,
  1300. self.repo[b"refs/heads/foo"].tree,
  1301. )
  1302. )[0]
  1303. self.assertEqual(
  1304. os.path.basename(fullpath), change.new.path.decode("ascii")
  1305. )
  1306. def test_local_missing(self):
  1307. """Pushing a new branch."""
  1308. outstream = BytesIO()
  1309. errstream = BytesIO()
  1310. # Setup target repo cloned from temp test repo
  1311. clone_path = tempfile.mkdtemp()
  1312. self.addCleanup(shutil.rmtree, clone_path)
  1313. target_repo = porcelain.init(clone_path)
  1314. target_repo.close()
  1315. self.assertRaises(
  1316. porcelain.Error,
  1317. porcelain.push,
  1318. self.repo,
  1319. clone_path,
  1320. b"HEAD:refs/heads/master",
  1321. outstream=outstream,
  1322. errstream=errstream,
  1323. )
  1324. def test_new(self):
  1325. """Pushing a new branch."""
  1326. outstream = BytesIO()
  1327. errstream = BytesIO()
  1328. # Setup target repo cloned from temp test repo
  1329. clone_path = tempfile.mkdtemp()
  1330. self.addCleanup(shutil.rmtree, clone_path)
  1331. target_repo = porcelain.init(clone_path)
  1332. target_repo.close()
  1333. # create a second file to be pushed back to origin
  1334. handle, fullpath = tempfile.mkstemp(dir=clone_path)
  1335. os.close(handle)
  1336. porcelain.add(repo=clone_path, paths=[fullpath])
  1337. new_id = porcelain.commit(
  1338. repo=self.repo,
  1339. message=b"push",
  1340. author=b"author <email>",
  1341. committer=b"committer <email>",
  1342. )
  1343. # Push to the remote
  1344. porcelain.push(
  1345. self.repo,
  1346. clone_path,
  1347. b"HEAD:refs/heads/master",
  1348. outstream=outstream,
  1349. errstream=errstream,
  1350. )
  1351. with Repo(clone_path) as r_clone:
  1352. self.assertEqual(
  1353. {
  1354. b"HEAD": new_id,
  1355. b"refs/heads/master": new_id,
  1356. },
  1357. r_clone.get_refs(),
  1358. )
  1359. def test_delete(self):
  1360. """Basic test of porcelain push, removing a branch."""
  1361. outstream = BytesIO()
  1362. errstream = BytesIO()
  1363. porcelain.commit(
  1364. repo=self.repo.path,
  1365. message=b"init",
  1366. author=b"author <email>",
  1367. committer=b"committer <email>",
  1368. )
  1369. # Setup target repo cloned from temp test repo
  1370. clone_path = tempfile.mkdtemp()
  1371. self.addCleanup(shutil.rmtree, clone_path)
  1372. target_repo = porcelain.clone(
  1373. self.repo.path, target=clone_path, errstream=errstream
  1374. )
  1375. target_repo.close()
  1376. # Setup a non-checked out branch in the remote
  1377. refs_path = b"refs/heads/foo"
  1378. new_id = self.repo[b"HEAD"].id
  1379. self.assertNotEqual(new_id, ZERO_SHA)
  1380. self.repo.refs[refs_path] = new_id
  1381. # Push to the remote
  1382. porcelain.push(
  1383. clone_path,
  1384. self.repo.path,
  1385. b":" + refs_path,
  1386. outstream=outstream,
  1387. errstream=errstream,
  1388. )
  1389. self.assertEqual(
  1390. {
  1391. b"HEAD": new_id,
  1392. b"refs/heads/master": new_id,
  1393. },
  1394. self.repo.get_refs(),
  1395. )
  1396. def test_diverged(self):
  1397. outstream = BytesIO()
  1398. errstream = BytesIO()
  1399. porcelain.commit(
  1400. repo=self.repo.path,
  1401. message=b"init",
  1402. author=b"author <email>",
  1403. committer=b"committer <email>",
  1404. )
  1405. # Setup target repo cloned from temp test repo
  1406. clone_path = tempfile.mkdtemp()
  1407. self.addCleanup(shutil.rmtree, clone_path)
  1408. target_repo = porcelain.clone(
  1409. self.repo.path, target=clone_path, errstream=errstream
  1410. )
  1411. target_repo.close()
  1412. remote_id = porcelain.commit(
  1413. repo=self.repo.path,
  1414. message=b"remote change",
  1415. author=b"author <email>",
  1416. committer=b"committer <email>",
  1417. )
  1418. local_id = porcelain.commit(
  1419. repo=clone_path,
  1420. message=b"local change",
  1421. author=b"author <email>",
  1422. committer=b"committer <email>",
  1423. )
  1424. outstream = BytesIO()
  1425. errstream = BytesIO()
  1426. # Push to the remote
  1427. self.assertRaises(
  1428. porcelain.DivergedBranches,
  1429. porcelain.push,
  1430. clone_path,
  1431. self.repo.path,
  1432. b"refs/heads/master",
  1433. outstream=outstream,
  1434. errstream=errstream,
  1435. )
  1436. self.assertEqual(
  1437. {
  1438. b"HEAD": remote_id,
  1439. b"refs/heads/master": remote_id,
  1440. },
  1441. self.repo.get_refs(),
  1442. )
  1443. self.assertEqual(b"", outstream.getvalue())
  1444. self.assertEqual(b"", errstream.getvalue())
  1445. outstream = BytesIO()
  1446. errstream = BytesIO()
  1447. # Push to the remote with --force
  1448. porcelain.push(
  1449. clone_path,
  1450. self.repo.path,
  1451. b"refs/heads/master",
  1452. outstream=outstream,
  1453. errstream=errstream,
  1454. force=True,
  1455. )
  1456. self.assertEqual(
  1457. {
  1458. b"HEAD": local_id,
  1459. b"refs/heads/master": local_id,
  1460. },
  1461. self.repo.get_refs(),
  1462. )
  1463. self.assertEqual(b"", outstream.getvalue())
  1464. self.assertTrue(re.match(b"Push to .* successful.\n", errstream.getvalue()))
  1465. class PullTests(PorcelainTestCase):
  1466. def setUp(self):
  1467. super(PullTests, self).setUp()
  1468. # create a file for initial commit
  1469. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1470. os.close(handle)
  1471. porcelain.add(repo=self.repo.path, paths=fullpath)
  1472. porcelain.commit(
  1473. repo=self.repo.path,
  1474. message=b"test",
  1475. author=b"test <email>",
  1476. committer=b"test <email>",
  1477. )
  1478. # Setup target repo
  1479. self.target_path = tempfile.mkdtemp()
  1480. self.addCleanup(shutil.rmtree, self.target_path)
  1481. target_repo = porcelain.clone(
  1482. self.repo.path, target=self.target_path, errstream=BytesIO()
  1483. )
  1484. target_repo.close()
  1485. # create a second file to be pushed
  1486. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  1487. os.close(handle)
  1488. porcelain.add(repo=self.repo.path, paths=fullpath)
  1489. porcelain.commit(
  1490. repo=self.repo.path,
  1491. message=b"test2",
  1492. author=b"test2 <email>",
  1493. committer=b"test2 <email>",
  1494. )
  1495. self.assertIn(b"refs/heads/master", self.repo.refs)
  1496. self.assertIn(b"refs/heads/master", target_repo.refs)
  1497. def test_simple(self):
  1498. outstream = BytesIO()
  1499. errstream = BytesIO()
  1500. # Pull changes into the cloned repo
  1501. porcelain.pull(
  1502. self.target_path,
  1503. self.repo.path,
  1504. b"refs/heads/master",
  1505. outstream=outstream,
  1506. errstream=errstream,
  1507. )
  1508. # Check the target repo for pushed changes
  1509. with Repo(self.target_path) as r:
  1510. self.assertEqual(r[b"HEAD"].id, self.repo[b"HEAD"].id)
  1511. def test_diverged(self):
  1512. outstream = BytesIO()
  1513. errstream = BytesIO()
  1514. c3a = porcelain.commit(
  1515. repo=self.target_path,
  1516. message=b"test3a",
  1517. author=b"test2 <email>",
  1518. committer=b"test2 <email>",
  1519. )
  1520. porcelain.commit(
  1521. repo=self.repo.path,
  1522. message=b"test3b",
  1523. author=b"test2 <email>",
  1524. committer=b"test2 <email>",
  1525. )
  1526. # Pull changes into the cloned repo
  1527. self.assertRaises(
  1528. porcelain.DivergedBranches,
  1529. porcelain.pull,
  1530. self.target_path,
  1531. self.repo.path,
  1532. b"refs/heads/master",
  1533. outstream=outstream,
  1534. errstream=errstream,
  1535. )
  1536. # Check the target repo for pushed changes
  1537. with Repo(self.target_path) as r:
  1538. self.assertEqual(r[b"refs/heads/master"].id, c3a)
  1539. self.assertRaises(
  1540. NotImplementedError,
  1541. porcelain.pull,
  1542. self.target_path,
  1543. self.repo.path,
  1544. b"refs/heads/master",
  1545. outstream=outstream,
  1546. errstream=errstream,
  1547. fast_forward=False,
  1548. )
  1549. # Check the target repo for pushed changes
  1550. with Repo(self.target_path) as r:
  1551. self.assertEqual(r[b"refs/heads/master"].id, c3a)
  1552. def test_no_refspec(self):
  1553. outstream = BytesIO()
  1554. errstream = BytesIO()
  1555. # Pull changes into the cloned repo
  1556. porcelain.pull(
  1557. self.target_path,
  1558. self.repo.path,
  1559. outstream=outstream,
  1560. errstream=errstream,
  1561. )
  1562. # Check the target repo for pushed changes
  1563. with Repo(self.target_path) as r:
  1564. self.assertEqual(r[b"HEAD"].id, self.repo[b"HEAD"].id)
  1565. def test_no_remote_location(self):
  1566. outstream = BytesIO()
  1567. errstream = BytesIO()
  1568. # Pull changes into the cloned repo
  1569. porcelain.pull(
  1570. self.target_path,
  1571. refspecs=b"refs/heads/master",
  1572. outstream=outstream,
  1573. errstream=errstream,
  1574. )
  1575. # Check the target repo for pushed changes
  1576. with Repo(self.target_path) as r:
  1577. self.assertEqual(r[b"HEAD"].id, self.repo[b"HEAD"].id)
  1578. class StatusTests(PorcelainTestCase):
  1579. def test_empty(self):
  1580. results = porcelain.status(self.repo)
  1581. self.assertEqual({"add": [], "delete": [], "modify": []}, results.staged)
  1582. self.assertEqual([], results.unstaged)
  1583. def test_status_base(self):
  1584. """Integration test for `status` functionality."""
  1585. # Commit a dummy file then modify it
  1586. fullpath = os.path.join(self.repo.path, "foo")
  1587. with open(fullpath, "w") as f:
  1588. f.write("origstuff")
  1589. porcelain.add(repo=self.repo.path, paths=[fullpath])
  1590. porcelain.commit(
  1591. repo=self.repo.path,
  1592. message=b"test status",
  1593. author=b"author <email>",
  1594. committer=b"committer <email>",
  1595. )
  1596. # modify access and modify time of path
  1597. os.utime(fullpath, (0, 0))
  1598. with open(fullpath, "wb") as f:
  1599. f.write(b"stuff")
  1600. # Make a dummy file and stage it
  1601. filename_add = "bar"
  1602. fullpath = os.path.join(self.repo.path, filename_add)
  1603. with open(fullpath, "w") as f:
  1604. f.write("stuff")
  1605. porcelain.add(repo=self.repo.path, paths=fullpath)
  1606. results = porcelain.status(self.repo)
  1607. self.assertEqual(results.staged["add"][0], filename_add.encode("ascii"))
  1608. self.assertEqual(results.unstaged, [b"foo"])
  1609. def test_status_all(self):
  1610. del_path = os.path.join(self.repo.path, "foo")
  1611. mod_path = os.path.join(self.repo.path, "bar")
  1612. add_path = os.path.join(self.repo.path, "baz")
  1613. us_path = os.path.join(self.repo.path, "blye")
  1614. ut_path = os.path.join(self.repo.path, "blyat")
  1615. with open(del_path, "w") as f:
  1616. f.write("origstuff")
  1617. with open(mod_path, "w") as f:
  1618. f.write("origstuff")
  1619. with open(us_path, "w") as f:
  1620. f.write("origstuff")
  1621. porcelain.add(repo=self.repo.path, paths=[del_path, mod_path, us_path])
  1622. porcelain.commit(
  1623. repo=self.repo.path,
  1624. message=b"test status",
  1625. author=b"author <email>",
  1626. committer=b"committer <email>",
  1627. )
  1628. porcelain.remove(self.repo.path, [del_path])
  1629. with open(add_path, "w") as f:
  1630. f.write("origstuff")
  1631. with open(mod_path, "w") as f:
  1632. f.write("more_origstuff")
  1633. with open(us_path, "w") as f:
  1634. f.write("more_origstuff")
  1635. porcelain.add(repo=self.repo.path, paths=[add_path, mod_path])
  1636. with open(us_path, "w") as f:
  1637. f.write("\norigstuff")
  1638. with open(ut_path, "w") as f:
  1639. f.write("origstuff")
  1640. results = porcelain.status(self.repo.path)
  1641. self.assertDictEqual(
  1642. {"add": [b"baz"], "delete": [b"foo"], "modify": [b"bar"]},
  1643. results.staged,
  1644. )
  1645. self.assertListEqual(results.unstaged, [b"blye"])
  1646. results_no_untracked = porcelain.status(self.repo.path, untracked_files="no")
  1647. self.assertListEqual(results_no_untracked.untracked, [])
  1648. def test_status_wrong_untracked_files_value(self):
  1649. with self.assertRaises(ValueError):
  1650. porcelain.status(self.repo.path, untracked_files="antani")
  1651. def test_status_crlf_mismatch(self):
  1652. # First make a commit as if the file has been added on a Linux system
  1653. # or with core.autocrlf=True
  1654. file_path = os.path.join(self.repo.path, "crlf")
  1655. with open(file_path, "wb") as f:
  1656. f.write(b"line1\nline2")
  1657. porcelain.add(repo=self.repo.path, paths=[file_path])
  1658. porcelain.commit(
  1659. repo=self.repo.path,
  1660. message=b"test status",
  1661. author=b"author <email>",
  1662. committer=b"committer <email>",
  1663. )
  1664. # Then update the file as if it was created by CGit on a Windows
  1665. # system with core.autocrlf=true
  1666. with open(file_path, "wb") as f:
  1667. f.write(b"line1\r\nline2")
  1668. results = porcelain.status(self.repo)
  1669. self.assertDictEqual({"add": [], "delete": [], "modify": []}, results.staged)
  1670. self.assertListEqual(results.unstaged, [b"crlf"])
  1671. self.assertListEqual(results.untracked, [])
  1672. def test_status_autocrlf_true(self):
  1673. # First make a commit as if the file has been added on a Linux system
  1674. # or with core.autocrlf=True
  1675. file_path = os.path.join(self.repo.path, "crlf")
  1676. with open(file_path, "wb") as f:
  1677. f.write(b"line1\nline2")
  1678. porcelain.add(repo=self.repo.path, paths=[file_path])
  1679. porcelain.commit(
  1680. repo=self.repo.path,
  1681. message=b"test status",
  1682. author=b"author <email>",
  1683. committer=b"committer <email>",
  1684. )
  1685. # Then update the file as if it was created by CGit on a Windows
  1686. # system with core.autocrlf=true
  1687. with open(file_path, "wb") as f:
  1688. f.write(b"line1\r\nline2")
  1689. # TODO: It should be set automatically by looking at the configuration
  1690. c = self.repo.get_config()
  1691. c.set("core", "autocrlf", True)
  1692. c.write_to_path()
  1693. results = porcelain.status(self.repo)
  1694. self.assertDictEqual({"add": [], "delete": [], "modify": []}, results.staged)
  1695. self.assertListEqual(results.unstaged, [])
  1696. self.assertListEqual(results.untracked, [])
  1697. def test_status_autocrlf_input(self):
  1698. # Commit existing file with CRLF
  1699. file_path = os.path.join(self.repo.path, "crlf-exists")
  1700. with open(file_path, "wb") as f:
  1701. f.write(b"line1\r\nline2")
  1702. porcelain.add(repo=self.repo.path, paths=[file_path])
  1703. porcelain.commit(
  1704. repo=self.repo.path,
  1705. message=b"test status",
  1706. author=b"author <email>",
  1707. committer=b"committer <email>",
  1708. )
  1709. c = self.repo.get_config()
  1710. c.set("core", "autocrlf", "input")
  1711. c.write_to_path()
  1712. # Add new (untracked) file
  1713. file_path = os.path.join(self.repo.path, "crlf-new")
  1714. with open(file_path, "wb") as f:
  1715. f.write(b"line1\r\nline2")
  1716. porcelain.add(repo=self.repo.path, paths=[file_path])
  1717. results = porcelain.status(self.repo)
  1718. self.assertDictEqual({"add": [b"crlf-new"], "delete": [], "modify": []}, results.staged)
  1719. self.assertListEqual(results.unstaged, [])
  1720. self.assertListEqual(results.untracked, [])
  1721. def test_get_tree_changes_add(self):
  1722. """Unit test for get_tree_changes add."""
  1723. # Make a dummy file, stage
  1724. filename = "bar"
  1725. fullpath = os.path.join(self.repo.path, filename)
  1726. with open(fullpath, "w") as f:
  1727. f.write("stuff")
  1728. porcelain.add(repo=self.repo.path, paths=fullpath)
  1729. porcelain.commit(
  1730. repo=self.repo.path,
  1731. message=b"test status",
  1732. author=b"author <email>",
  1733. committer=b"committer <email>",
  1734. )
  1735. filename = "foo"
  1736. fullpath = os.path.join(self.repo.path, filename)
  1737. with open(fullpath, "w") as f:
  1738. f.write("stuff")
  1739. porcelain.add(repo=self.repo.path, paths=fullpath)
  1740. changes = porcelain.get_tree_changes(self.repo.path)
  1741. self.assertEqual(changes["add"][0], filename.encode("ascii"))
  1742. self.assertEqual(len(changes["add"]), 1)
  1743. self.assertEqual(len(changes["modify"]), 0)
  1744. self.assertEqual(len(changes["delete"]), 0)
  1745. def test_get_tree_changes_modify(self):
  1746. """Unit test for get_tree_changes modify."""
  1747. # Make a dummy file, stage, commit, modify
  1748. filename = "foo"
  1749. fullpath = os.path.join(self.repo.path, filename)
  1750. with open(fullpath, "w") as f:
  1751. f.write("stuff")
  1752. porcelain.add(repo=self.repo.path, paths=fullpath)
  1753. porcelain.commit(
  1754. repo=self.repo.path,
  1755. message=b"test status",
  1756. author=b"author <email>",
  1757. committer=b"committer <email>",
  1758. )
  1759. with open(fullpath, "w") as f:
  1760. f.write("otherstuff")
  1761. porcelain.add(repo=self.repo.path, paths=fullpath)
  1762. changes = porcelain.get_tree_changes(self.repo.path)
  1763. self.assertEqual(changes["modify"][0], filename.encode("ascii"))
  1764. self.assertEqual(len(changes["add"]), 0)
  1765. self.assertEqual(len(changes["modify"]), 1)
  1766. self.assertEqual(len(changes["delete"]), 0)
  1767. def test_get_tree_changes_delete(self):
  1768. """Unit test for get_tree_changes delete."""
  1769. # Make a dummy file, stage, commit, remove
  1770. filename = "foo"
  1771. fullpath = os.path.join(self.repo.path, filename)
  1772. with open(fullpath, "w") as f:
  1773. f.write("stuff")
  1774. porcelain.add(repo=self.repo.path, paths=fullpath)
  1775. porcelain.commit(
  1776. repo=self.repo.path,
  1777. message=b"test status",
  1778. author=b"author <email>",
  1779. committer=b"committer <email>",
  1780. )
  1781. cwd = os.getcwd()
  1782. try:
  1783. os.chdir(self.repo.path)
  1784. porcelain.remove(repo=self.repo.path, paths=[filename])
  1785. finally:
  1786. os.chdir(cwd)
  1787. changes = porcelain.get_tree_changes(self.repo.path)
  1788. self.assertEqual(changes["delete"][0], filename.encode("ascii"))
  1789. self.assertEqual(len(changes["add"]), 0)
  1790. self.assertEqual(len(changes["modify"]), 0)
  1791. self.assertEqual(len(changes["delete"]), 1)
  1792. def test_get_untracked_paths(self):
  1793. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  1794. f.write("ignored\n")
  1795. with open(os.path.join(self.repo.path, "ignored"), "w") as f:
  1796. f.write("blah\n")
  1797. with open(os.path.join(self.repo.path, "notignored"), "w") as f:
  1798. f.write("blah\n")
  1799. os.symlink(
  1800. os.path.join(self.repo.path, os.pardir, "external_target"),
  1801. os.path.join(self.repo.path, "link"),
  1802. )
  1803. self.assertEqual(
  1804. set(["ignored", "notignored", ".gitignore", "link"]),
  1805. set(
  1806. porcelain.get_untracked_paths(
  1807. self.repo.path, self.repo.path, self.repo.open_index()
  1808. )
  1809. ),
  1810. )
  1811. self.assertEqual(
  1812. set([".gitignore", "notignored", "link"]),
  1813. set(porcelain.status(self.repo).untracked),
  1814. )
  1815. self.assertEqual(
  1816. set([".gitignore", "notignored", "ignored", "link"]),
  1817. set(porcelain.status(self.repo, ignored=True).untracked),
  1818. )
  1819. def test_get_untracked_paths_subrepo(self):
  1820. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  1821. f.write("nested/\n")
  1822. with open(os.path.join(self.repo.path, "notignored"), "w") as f:
  1823. f.write("blah\n")
  1824. subrepo = Repo.init(os.path.join(self.repo.path, "nested"), mkdir=True)
  1825. with open(os.path.join(subrepo.path, "ignored"), "w") as f:
  1826. f.write("bleep\n")
  1827. with open(os.path.join(subrepo.path, "with"), "w") as f:
  1828. f.write("bloop\n")
  1829. with open(os.path.join(subrepo.path, "manager"), "w") as f:
  1830. f.write("blop\n")
  1831. self.assertEqual(
  1832. set([".gitignore", "notignored", os.path.join("nested", "")]),
  1833. set(
  1834. porcelain.get_untracked_paths(
  1835. self.repo.path, self.repo.path, self.repo.open_index()
  1836. )
  1837. ),
  1838. )
  1839. self.assertEqual(
  1840. set([".gitignore", "notignored"]),
  1841. set(
  1842. porcelain.get_untracked_paths(
  1843. self.repo.path,
  1844. self.repo.path,
  1845. self.repo.open_index(),
  1846. exclude_ignored=True,
  1847. )
  1848. ),
  1849. )
  1850. self.assertEqual(
  1851. set(["ignored", "with", "manager"]),
  1852. set(
  1853. porcelain.get_untracked_paths(
  1854. subrepo.path, subrepo.path, subrepo.open_index()
  1855. )
  1856. ),
  1857. )
  1858. self.assertEqual(
  1859. set(),
  1860. set(
  1861. porcelain.get_untracked_paths(
  1862. subrepo.path,
  1863. self.repo.path,
  1864. self.repo.open_index(),
  1865. )
  1866. ),
  1867. )
  1868. self.assertEqual(
  1869. set([os.path.join('nested', 'ignored'),
  1870. os.path.join('nested', 'with'),
  1871. os.path.join('nested', 'manager')]),
  1872. set(
  1873. porcelain.get_untracked_paths(
  1874. self.repo.path,
  1875. subrepo.path,
  1876. self.repo.open_index(),
  1877. )
  1878. ),
  1879. )
  1880. def test_get_untracked_paths_subdir(self):
  1881. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  1882. f.write("subdir/\nignored")
  1883. with open(os.path.join(self.repo.path, "notignored"), "w") as f:
  1884. f.write("blah\n")
  1885. os.mkdir(os.path.join(self.repo.path, "subdir"))
  1886. with open(os.path.join(self.repo.path, "ignored"), "w") as f:
  1887. f.write("foo")
  1888. with open(os.path.join(self.repo.path, "subdir", "ignored"), "w") as f:
  1889. f.write("foo")
  1890. self.assertEqual(
  1891. set(
  1892. [
  1893. ".gitignore",
  1894. "notignored",
  1895. "ignored",
  1896. os.path.join("subdir", ""),
  1897. ]
  1898. ),
  1899. set(
  1900. porcelain.get_untracked_paths(
  1901. self.repo.path,
  1902. self.repo.path,
  1903. self.repo.open_index(),
  1904. )
  1905. )
  1906. )
  1907. self.assertEqual(
  1908. set([".gitignore", "notignored"]),
  1909. set(
  1910. porcelain.get_untracked_paths(
  1911. self.repo.path,
  1912. self.repo.path,
  1913. self.repo.open_index(),
  1914. exclude_ignored=True,
  1915. )
  1916. )
  1917. )
  1918. def test_get_untracked_paths_invalid_untracked_files(self):
  1919. with self.assertRaises(ValueError):
  1920. list(
  1921. porcelain.get_untracked_paths(
  1922. self.repo.path,
  1923. self.repo.path,
  1924. self.repo.open_index(),
  1925. untracked_files="invalid_value",
  1926. )
  1927. )
  1928. def test_get_untracked_paths_normal(self):
  1929. with self.assertRaises(NotImplementedError):
  1930. _, _, _ = porcelain.status(
  1931. repo=self.repo.path, untracked_files="normal"
  1932. )
  1933. # TODO(jelmer): Add test for dulwich.porcelain.daemon
  1934. class UploadPackTests(PorcelainTestCase):
  1935. """Tests for upload_pack."""
  1936. def test_upload_pack(self):
  1937. outf = BytesIO()
  1938. exitcode = porcelain.upload_pack(self.repo.path, BytesIO(b"0000"), outf)
  1939. outlines = outf.getvalue().splitlines()
  1940. self.assertEqual([b"0000"], outlines)
  1941. self.assertEqual(0, exitcode)
  1942. class ReceivePackTests(PorcelainTestCase):
  1943. """Tests for receive_pack."""
  1944. def test_receive_pack(self):
  1945. filename = "foo"
  1946. fullpath = os.path.join(self.repo.path, filename)
  1947. with open(fullpath, "w") as f:
  1948. f.write("stuff")
  1949. porcelain.add(repo=self.repo.path, paths=fullpath)
  1950. self.repo.do_commit(
  1951. message=b"test status",
  1952. author=b"author <email>",
  1953. committer=b"committer <email>",
  1954. author_timestamp=1402354300,
  1955. commit_timestamp=1402354300,
  1956. author_timezone=0,
  1957. commit_timezone=0,
  1958. )
  1959. outf = BytesIO()
  1960. exitcode = porcelain.receive_pack(self.repo.path, BytesIO(b"0000"), outf)
  1961. outlines = outf.getvalue().splitlines()
  1962. self.assertEqual(
  1963. [
  1964. b"0091319b56ce3aee2d489f759736a79cc552c9bb86d9 HEAD\x00 report-status "
  1965. b"delete-refs quiet ofs-delta side-band-64k "
  1966. b"no-done symref=HEAD:refs/heads/master",
  1967. b"003f319b56ce3aee2d489f759736a79cc552c9bb86d9 refs/heads/master",
  1968. b"0000",
  1969. ],
  1970. outlines,
  1971. )
  1972. self.assertEqual(0, exitcode)
  1973. class BranchListTests(PorcelainTestCase):
  1974. def test_standard(self):
  1975. self.assertEqual(set([]), set(porcelain.branch_list(self.repo)))
  1976. def test_new_branch(self):
  1977. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1978. self.repo[b"HEAD"] = c1.id
  1979. porcelain.branch_create(self.repo, b"foo")
  1980. self.assertEqual(
  1981. set([b"master", b"foo"]), set(porcelain.branch_list(self.repo))
  1982. )
  1983. class BranchCreateTests(PorcelainTestCase):
  1984. def test_branch_exists(self):
  1985. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1986. self.repo[b"HEAD"] = c1.id
  1987. porcelain.branch_create(self.repo, b"foo")
  1988. self.assertRaises(porcelain.Error, porcelain.branch_create, self.repo, b"foo")
  1989. porcelain.branch_create(self.repo, b"foo", force=True)
  1990. def test_new_branch(self):
  1991. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  1992. self.repo[b"HEAD"] = c1.id
  1993. porcelain.branch_create(self.repo, b"foo")
  1994. self.assertEqual(
  1995. set([b"master", b"foo"]), set(porcelain.branch_list(self.repo))
  1996. )
  1997. class BranchDeleteTests(PorcelainTestCase):
  1998. def test_simple(self):
  1999. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  2000. self.repo[b"HEAD"] = c1.id
  2001. porcelain.branch_create(self.repo, b"foo")
  2002. self.assertIn(b"foo", porcelain.branch_list(self.repo))
  2003. porcelain.branch_delete(self.repo, b"foo")
  2004. self.assertNotIn(b"foo", porcelain.branch_list(self.repo))
  2005. def test_simple_unicode(self):
  2006. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  2007. self.repo[b"HEAD"] = c1.id
  2008. porcelain.branch_create(self.repo, "foo")
  2009. self.assertIn(b"foo", porcelain.branch_list(self.repo))
  2010. porcelain.branch_delete(self.repo, "foo")
  2011. self.assertNotIn(b"foo", porcelain.branch_list(self.repo))
  2012. class FetchTests(PorcelainTestCase):
  2013. def test_simple(self):
  2014. outstream = BytesIO()
  2015. errstream = BytesIO()
  2016. # create a file for initial commit
  2017. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  2018. os.close(handle)
  2019. porcelain.add(repo=self.repo.path, paths=fullpath)
  2020. porcelain.commit(
  2021. repo=self.repo.path,
  2022. message=b"test",
  2023. author=b"test <email>",
  2024. committer=b"test <email>",
  2025. )
  2026. # Setup target repo
  2027. target_path = tempfile.mkdtemp()
  2028. self.addCleanup(shutil.rmtree, target_path)
  2029. target_repo = porcelain.clone(
  2030. self.repo.path, target=target_path, errstream=errstream
  2031. )
  2032. # create a second file to be pushed
  2033. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  2034. os.close(handle)
  2035. porcelain.add(repo=self.repo.path, paths=fullpath)
  2036. porcelain.commit(
  2037. repo=self.repo.path,
  2038. message=b"test2",
  2039. author=b"test2 <email>",
  2040. committer=b"test2 <email>",
  2041. )
  2042. self.assertNotIn(self.repo[b"HEAD"].id, target_repo)
  2043. target_repo.close()
  2044. # Fetch changes into the cloned repo
  2045. porcelain.fetch(target_path, "origin", outstream=outstream, errstream=errstream)
  2046. # Assert that fetch updated the local image of the remote
  2047. self.assert_correct_remote_refs(target_repo.get_refs(), self.repo.get_refs())
  2048. # Check the target repo for pushed changes
  2049. with Repo(target_path) as r:
  2050. self.assertIn(self.repo[b"HEAD"].id, r)
  2051. def test_with_remote_name(self):
  2052. remote_name = "origin"
  2053. outstream = BytesIO()
  2054. errstream = BytesIO()
  2055. # create a file for initial commit
  2056. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  2057. os.close(handle)
  2058. porcelain.add(repo=self.repo.path, paths=fullpath)
  2059. porcelain.commit(
  2060. repo=self.repo.path,
  2061. message=b"test",
  2062. author=b"test <email>",
  2063. committer=b"test <email>",
  2064. )
  2065. # Setup target repo
  2066. target_path = tempfile.mkdtemp()
  2067. self.addCleanup(shutil.rmtree, target_path)
  2068. target_repo = porcelain.clone(
  2069. self.repo.path, target=target_path, errstream=errstream
  2070. )
  2071. # Capture current refs
  2072. target_refs = target_repo.get_refs()
  2073. # create a second file to be pushed
  2074. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  2075. os.close(handle)
  2076. porcelain.add(repo=self.repo.path, paths=fullpath)
  2077. porcelain.commit(
  2078. repo=self.repo.path,
  2079. message=b"test2",
  2080. author=b"test2 <email>",
  2081. committer=b"test2 <email>",
  2082. )
  2083. self.assertNotIn(self.repo[b"HEAD"].id, target_repo)
  2084. target_config = target_repo.get_config()
  2085. target_config.set(
  2086. (b"remote", remote_name.encode()), b"url", self.repo.path.encode()
  2087. )
  2088. target_repo.close()
  2089. # Fetch changes into the cloned repo
  2090. porcelain.fetch(
  2091. target_path, remote_name, outstream=outstream, errstream=errstream
  2092. )
  2093. # Assert that fetch updated the local image of the remote
  2094. self.assert_correct_remote_refs(target_repo.get_refs(), self.repo.get_refs())
  2095. # Check the target repo for pushed changes, as well as updates
  2096. # for the refs
  2097. with Repo(target_path) as r:
  2098. self.assertIn(self.repo[b"HEAD"].id, r)
  2099. self.assertNotEqual(self.repo.get_refs(), target_refs)
  2100. def assert_correct_remote_refs(
  2101. self, local_refs, remote_refs, remote_name=b"origin"
  2102. ):
  2103. """Assert that known remote refs corresponds to actual remote refs."""
  2104. local_ref_prefix = b"refs/heads"
  2105. remote_ref_prefix = b"refs/remotes/" + remote_name
  2106. locally_known_remote_refs = {
  2107. k[len(remote_ref_prefix) + 1 :]: v
  2108. for k, v in local_refs.items()
  2109. if k.startswith(remote_ref_prefix)
  2110. }
  2111. normalized_remote_refs = {
  2112. k[len(local_ref_prefix) + 1 :]: v
  2113. for k, v in remote_refs.items()
  2114. if k.startswith(local_ref_prefix)
  2115. }
  2116. if b"HEAD" in locally_known_remote_refs and b"HEAD" in remote_refs:
  2117. normalized_remote_refs[b"HEAD"] = remote_refs[b"HEAD"]
  2118. self.assertEqual(locally_known_remote_refs, normalized_remote_refs)
  2119. class RepackTests(PorcelainTestCase):
  2120. def test_empty(self):
  2121. porcelain.repack(self.repo)
  2122. def test_simple(self):
  2123. handle, fullpath = tempfile.mkstemp(dir=self.repo.path)
  2124. os.close(handle)
  2125. porcelain.add(repo=self.repo.path, paths=fullpath)
  2126. porcelain.repack(self.repo)
  2127. class LsTreeTests(PorcelainTestCase):
  2128. def test_empty(self):
  2129. porcelain.commit(
  2130. repo=self.repo.path,
  2131. message=b"test status",
  2132. author=b"author <email>",
  2133. committer=b"committer <email>",
  2134. )
  2135. f = StringIO()
  2136. porcelain.ls_tree(self.repo, b"HEAD", outstream=f)
  2137. self.assertEqual(f.getvalue(), "")
  2138. def test_simple(self):
  2139. # Commit a dummy file then modify it
  2140. fullpath = os.path.join(self.repo.path, "foo")
  2141. with open(fullpath, "w") as f:
  2142. f.write("origstuff")
  2143. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2144. porcelain.commit(
  2145. repo=self.repo.path,
  2146. message=b"test status",
  2147. author=b"author <email>",
  2148. committer=b"committer <email>",
  2149. )
  2150. f = StringIO()
  2151. porcelain.ls_tree(self.repo, b"HEAD", outstream=f)
  2152. self.assertEqual(
  2153. f.getvalue(),
  2154. "100644 blob 8b82634d7eae019850bb883f06abf428c58bc9aa\tfoo\n",
  2155. )
  2156. def test_recursive(self):
  2157. # Create a directory then write a dummy file in it
  2158. dirpath = os.path.join(self.repo.path, "adir")
  2159. filepath = os.path.join(dirpath, "afile")
  2160. os.mkdir(dirpath)
  2161. with open(filepath, "w") as f:
  2162. f.write("origstuff")
  2163. porcelain.add(repo=self.repo.path, paths=[filepath])
  2164. porcelain.commit(
  2165. repo=self.repo.path,
  2166. message=b"test status",
  2167. author=b"author <email>",
  2168. committer=b"committer <email>",
  2169. )
  2170. f = StringIO()
  2171. porcelain.ls_tree(self.repo, b"HEAD", outstream=f)
  2172. self.assertEqual(
  2173. f.getvalue(),
  2174. "40000 tree b145cc69a5e17693e24d8a7be0016ed8075de66d\tadir\n",
  2175. )
  2176. f = StringIO()
  2177. porcelain.ls_tree(self.repo, b"HEAD", outstream=f, recursive=True)
  2178. self.assertEqual(
  2179. f.getvalue(),
  2180. "40000 tree b145cc69a5e17693e24d8a7be0016ed8075de66d\tadir\n"
  2181. "100644 blob 8b82634d7eae019850bb883f06abf428c58bc9aa\tadir"
  2182. "/afile\n",
  2183. )
  2184. class LsRemoteTests(PorcelainTestCase):
  2185. def test_empty(self):
  2186. self.assertEqual({}, porcelain.ls_remote(self.repo.path))
  2187. def test_some(self):
  2188. cid = porcelain.commit(
  2189. repo=self.repo.path,
  2190. message=b"test status",
  2191. author=b"author <email>",
  2192. committer=b"committer <email>",
  2193. )
  2194. self.assertEqual(
  2195. {b"refs/heads/master": cid, b"HEAD": cid},
  2196. porcelain.ls_remote(self.repo.path),
  2197. )
  2198. class LsFilesTests(PorcelainTestCase):
  2199. def test_empty(self):
  2200. self.assertEqual([], list(porcelain.ls_files(self.repo)))
  2201. def test_simple(self):
  2202. # Commit a dummy file then modify it
  2203. fullpath = os.path.join(self.repo.path, "foo")
  2204. with open(fullpath, "w") as f:
  2205. f.write("origstuff")
  2206. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2207. self.assertEqual([b"foo"], list(porcelain.ls_files(self.repo)))
  2208. class RemoteAddTests(PorcelainTestCase):
  2209. def test_new(self):
  2210. porcelain.remote_add(self.repo, "jelmer", "git://jelmer.uk/code/dulwich")
  2211. c = self.repo.get_config()
  2212. self.assertEqual(
  2213. c.get((b"remote", b"jelmer"), b"url"),
  2214. b"git://jelmer.uk/code/dulwich",
  2215. )
  2216. def test_exists(self):
  2217. porcelain.remote_add(self.repo, "jelmer", "git://jelmer.uk/code/dulwich")
  2218. self.assertRaises(
  2219. porcelain.RemoteExists,
  2220. porcelain.remote_add,
  2221. self.repo,
  2222. "jelmer",
  2223. "git://jelmer.uk/code/dulwich",
  2224. )
  2225. class RemoteRemoveTests(PorcelainTestCase):
  2226. def test_remove(self):
  2227. porcelain.remote_add(self.repo, "jelmer", "git://jelmer.uk/code/dulwich")
  2228. c = self.repo.get_config()
  2229. self.assertEqual(
  2230. c.get((b"remote", b"jelmer"), b"url"),
  2231. b"git://jelmer.uk/code/dulwich",
  2232. )
  2233. porcelain.remote_remove(self.repo, "jelmer")
  2234. self.assertRaises(KeyError, porcelain.remote_remove, self.repo, "jelmer")
  2235. c = self.repo.get_config()
  2236. self.assertRaises(KeyError, c.get, (b"remote", b"jelmer"), b"url")
  2237. class CheckIgnoreTests(PorcelainTestCase):
  2238. def test_check_ignored(self):
  2239. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  2240. f.write("foo")
  2241. foo_path = os.path.join(self.repo.path, "foo")
  2242. with open(foo_path, "w") as f:
  2243. f.write("BAR")
  2244. bar_path = os.path.join(self.repo.path, "bar")
  2245. with open(bar_path, "w") as f:
  2246. f.write("BAR")
  2247. self.assertEqual(["foo"], list(porcelain.check_ignore(self.repo, [foo_path])))
  2248. self.assertEqual([], list(porcelain.check_ignore(self.repo, [bar_path])))
  2249. def test_check_added_abs(self):
  2250. path = os.path.join(self.repo.path, "foo")
  2251. with open(path, "w") as f:
  2252. f.write("BAR")
  2253. self.repo.stage(["foo"])
  2254. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  2255. f.write("foo\n")
  2256. self.assertEqual([], list(porcelain.check_ignore(self.repo, [path])))
  2257. self.assertEqual(
  2258. ["foo"],
  2259. list(porcelain.check_ignore(self.repo, [path], no_index=True)),
  2260. )
  2261. def test_check_added_rel(self):
  2262. with open(os.path.join(self.repo.path, "foo"), "w") as f:
  2263. f.write("BAR")
  2264. self.repo.stage(["foo"])
  2265. with open(os.path.join(self.repo.path, ".gitignore"), "w") as f:
  2266. f.write("foo\n")
  2267. cwd = os.getcwd()
  2268. os.mkdir(os.path.join(self.repo.path, "bar"))
  2269. os.chdir(os.path.join(self.repo.path, "bar"))
  2270. try:
  2271. self.assertEqual(list(porcelain.check_ignore(self.repo, ["../foo"])), [])
  2272. self.assertEqual(
  2273. ["../foo"],
  2274. list(porcelain.check_ignore(self.repo, ["../foo"], no_index=True)),
  2275. )
  2276. finally:
  2277. os.chdir(cwd)
  2278. class UpdateHeadTests(PorcelainTestCase):
  2279. def test_set_to_branch(self):
  2280. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  2281. self.repo.refs[b"refs/heads/blah"] = c1.id
  2282. porcelain.update_head(self.repo, "blah")
  2283. self.assertEqual(c1.id, self.repo.head())
  2284. self.assertEqual(b"ref: refs/heads/blah", self.repo.refs.read_ref(b"HEAD"))
  2285. def test_set_to_branch_detached(self):
  2286. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  2287. self.repo.refs[b"refs/heads/blah"] = c1.id
  2288. porcelain.update_head(self.repo, "blah", detached=True)
  2289. self.assertEqual(c1.id, self.repo.head())
  2290. self.assertEqual(c1.id, self.repo.refs.read_ref(b"HEAD"))
  2291. def test_set_to_commit_detached(self):
  2292. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  2293. self.repo.refs[b"refs/heads/blah"] = c1.id
  2294. porcelain.update_head(self.repo, c1.id, detached=True)
  2295. self.assertEqual(c1.id, self.repo.head())
  2296. self.assertEqual(c1.id, self.repo.refs.read_ref(b"HEAD"))
  2297. def test_set_new_branch(self):
  2298. [c1] = build_commit_graph(self.repo.object_store, [[1]])
  2299. self.repo.refs[b"refs/heads/blah"] = c1.id
  2300. porcelain.update_head(self.repo, "blah", new_branch="bar")
  2301. self.assertEqual(c1.id, self.repo.head())
  2302. self.assertEqual(b"ref: refs/heads/bar", self.repo.refs.read_ref(b"HEAD"))
  2303. class MailmapTests(PorcelainTestCase):
  2304. def test_no_mailmap(self):
  2305. self.assertEqual(
  2306. b"Jelmer Vernooij <jelmer@samba.org>",
  2307. porcelain.check_mailmap(self.repo, b"Jelmer Vernooij <jelmer@samba.org>"),
  2308. )
  2309. def test_mailmap_lookup(self):
  2310. with open(os.path.join(self.repo.path, ".mailmap"), "wb") as f:
  2311. f.write(
  2312. b"""\
  2313. Jelmer Vernooij <jelmer@debian.org>
  2314. """
  2315. )
  2316. self.assertEqual(
  2317. b"Jelmer Vernooij <jelmer@debian.org>",
  2318. porcelain.check_mailmap(self.repo, b"Jelmer Vernooij <jelmer@samba.org>"),
  2319. )
  2320. class FsckTests(PorcelainTestCase):
  2321. def test_none(self):
  2322. self.assertEqual([], list(porcelain.fsck(self.repo)))
  2323. def test_git_dir(self):
  2324. obj = Tree()
  2325. a = Blob()
  2326. a.data = b"foo"
  2327. obj.add(b".git", 0o100644, a.id)
  2328. self.repo.object_store.add_objects([(a, None), (obj, None)])
  2329. self.assertEqual(
  2330. [(obj.id, "invalid name .git")],
  2331. [(sha, str(e)) for (sha, e) in porcelain.fsck(self.repo)],
  2332. )
  2333. class DescribeTests(PorcelainTestCase):
  2334. def test_no_commits(self):
  2335. self.assertRaises(KeyError, porcelain.describe, self.repo.path)
  2336. def test_single_commit(self):
  2337. fullpath = os.path.join(self.repo.path, "foo")
  2338. with open(fullpath, "w") as f:
  2339. f.write("BAR")
  2340. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2341. sha = porcelain.commit(
  2342. self.repo.path,
  2343. message=b"Some message",
  2344. author=b"Joe <joe@example.com>",
  2345. committer=b"Bob <bob@example.com>",
  2346. )
  2347. self.assertEqual(
  2348. "g{}".format(sha[:7].decode("ascii")),
  2349. porcelain.describe(self.repo.path),
  2350. )
  2351. def test_tag(self):
  2352. fullpath = os.path.join(self.repo.path, "foo")
  2353. with open(fullpath, "w") as f:
  2354. f.write("BAR")
  2355. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2356. porcelain.commit(
  2357. self.repo.path,
  2358. message=b"Some message",
  2359. author=b"Joe <joe@example.com>",
  2360. committer=b"Bob <bob@example.com>",
  2361. )
  2362. porcelain.tag_create(
  2363. self.repo.path,
  2364. b"tryme",
  2365. b"foo <foo@bar.com>",
  2366. b"bar",
  2367. annotated=True,
  2368. )
  2369. self.assertEqual("tryme", porcelain.describe(self.repo.path))
  2370. def test_tag_and_commit(self):
  2371. fullpath = os.path.join(self.repo.path, "foo")
  2372. with open(fullpath, "w") as f:
  2373. f.write("BAR")
  2374. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2375. porcelain.commit(
  2376. self.repo.path,
  2377. message=b"Some message",
  2378. author=b"Joe <joe@example.com>",
  2379. committer=b"Bob <bob@example.com>",
  2380. )
  2381. porcelain.tag_create(
  2382. self.repo.path,
  2383. b"tryme",
  2384. b"foo <foo@bar.com>",
  2385. b"bar",
  2386. annotated=True,
  2387. )
  2388. with open(fullpath, "w") as f:
  2389. f.write("BAR2")
  2390. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2391. sha = porcelain.commit(
  2392. self.repo.path,
  2393. message=b"Some message",
  2394. author=b"Joe <joe@example.com>",
  2395. committer=b"Bob <bob@example.com>",
  2396. )
  2397. self.assertEqual(
  2398. "tryme-1-g{}".format(sha[:7].decode("ascii")),
  2399. porcelain.describe(self.repo.path),
  2400. )
  2401. class PathToTreeTests(PorcelainTestCase):
  2402. def setUp(self):
  2403. super(PathToTreeTests, self).setUp()
  2404. self.fp = os.path.join(self.test_dir, "bar")
  2405. with open(self.fp, "w") as f:
  2406. f.write("something")
  2407. oldcwd = os.getcwd()
  2408. self.addCleanup(os.chdir, oldcwd)
  2409. os.chdir(self.test_dir)
  2410. def test_path_to_tree_path_base(self):
  2411. self.assertEqual(b"bar", porcelain.path_to_tree_path(self.test_dir, self.fp))
  2412. self.assertEqual(b"bar", porcelain.path_to_tree_path(".", "./bar"))
  2413. self.assertEqual(b"bar", porcelain.path_to_tree_path(".", "bar"))
  2414. cwd = os.getcwd()
  2415. self.assertEqual(
  2416. b"bar", porcelain.path_to_tree_path(".", os.path.join(cwd, "bar"))
  2417. )
  2418. self.assertEqual(b"bar", porcelain.path_to_tree_path(cwd, "bar"))
  2419. def test_path_to_tree_path_syntax(self):
  2420. self.assertEqual(b"bar", porcelain.path_to_tree_path(".", "./bar"))
  2421. def test_path_to_tree_path_error(self):
  2422. with self.assertRaises(ValueError):
  2423. with tempfile.TemporaryDirectory() as od:
  2424. porcelain.path_to_tree_path(od, self.fp)
  2425. def test_path_to_tree_path_rel(self):
  2426. cwd = os.getcwd()
  2427. os.mkdir(os.path.join(self.repo.path, "foo"))
  2428. os.mkdir(os.path.join(self.repo.path, "foo/bar"))
  2429. try:
  2430. os.chdir(os.path.join(self.repo.path, "foo/bar"))
  2431. with open("baz", "w") as f:
  2432. f.write("contents")
  2433. self.assertEqual(b"bar/baz", porcelain.path_to_tree_path("..", "baz"))
  2434. self.assertEqual(
  2435. b"bar/baz",
  2436. porcelain.path_to_tree_path(
  2437. os.path.join(os.getcwd(), ".."),
  2438. os.path.join(os.getcwd(), "baz"),
  2439. ),
  2440. )
  2441. self.assertEqual(
  2442. b"bar/baz",
  2443. porcelain.path_to_tree_path("..", os.path.join(os.getcwd(), "baz")),
  2444. )
  2445. self.assertEqual(
  2446. b"bar/baz",
  2447. porcelain.path_to_tree_path(os.path.join(os.getcwd(), ".."), "baz"),
  2448. )
  2449. finally:
  2450. os.chdir(cwd)
  2451. class GetObjectByPathTests(PorcelainTestCase):
  2452. def test_simple(self):
  2453. fullpath = os.path.join(self.repo.path, "foo")
  2454. with open(fullpath, "w") as f:
  2455. f.write("BAR")
  2456. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2457. porcelain.commit(
  2458. self.repo.path,
  2459. message=b"Some message",
  2460. author=b"Joe <joe@example.com>",
  2461. committer=b"Bob <bob@example.com>",
  2462. )
  2463. self.assertEqual(b"BAR", porcelain.get_object_by_path(self.repo, "foo").data)
  2464. self.assertEqual(b"BAR", porcelain.get_object_by_path(self.repo, b"foo").data)
  2465. def test_encoding(self):
  2466. fullpath = os.path.join(self.repo.path, "foo")
  2467. with open(fullpath, "w") as f:
  2468. f.write("BAR")
  2469. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2470. porcelain.commit(
  2471. self.repo.path,
  2472. message=b"Some message",
  2473. author=b"Joe <joe@example.com>",
  2474. committer=b"Bob <bob@example.com>",
  2475. encoding=b"utf-8",
  2476. )
  2477. self.assertEqual(b"BAR", porcelain.get_object_by_path(self.repo, "foo").data)
  2478. self.assertEqual(b"BAR", porcelain.get_object_by_path(self.repo, b"foo").data)
  2479. def test_missing(self):
  2480. self.assertRaises(KeyError, porcelain.get_object_by_path, self.repo, "foo")
  2481. class WriteTreeTests(PorcelainTestCase):
  2482. def test_simple(self):
  2483. fullpath = os.path.join(self.repo.path, "foo")
  2484. with open(fullpath, "w") as f:
  2485. f.write("BAR")
  2486. porcelain.add(repo=self.repo.path, paths=[fullpath])
  2487. self.assertEqual(
  2488. b"d2092c8a9f311f0311083bf8d177f2ca0ab5b241",
  2489. porcelain.write_tree(self.repo),
  2490. )
  2491. class ActiveBranchTests(PorcelainTestCase):
  2492. def test_simple(self):
  2493. self.assertEqual(b"master", porcelain.active_branch(self.repo))
  2494. class FindUniqueAbbrevTests(PorcelainTestCase):
  2495. def test_simple(self):
  2496. c1, c2, c3 = build_commit_graph(
  2497. self.repo.object_store, [[1], [2, 1], [3, 1, 2]]
  2498. )
  2499. self.repo.refs[b"HEAD"] = c3.id
  2500. self.assertEqual(
  2501. c1.id.decode('ascii')[:7],
  2502. porcelain.find_unique_abbrev(self.repo.object_store, c1.id))