test_porcelain.py 105 KB

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