refs.py 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277
  1. # refs.py -- For dealing with git refs
  2. # Copyright (C) 2008-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. """Ref handling.
  21. """
  22. from contextlib import suppress
  23. import os
  24. from typing import Dict, Optional
  25. from dulwich.errors import (
  26. PackedRefsException,
  27. RefFormatError,
  28. )
  29. from dulwich.objects import (
  30. git_line,
  31. valid_hexsha,
  32. ZERO_SHA,
  33. Tag,
  34. ObjectID,
  35. )
  36. from dulwich.file import (
  37. GitFile,
  38. ensure_dir_exists,
  39. )
  40. Ref = bytes
  41. HEADREF = b"HEAD"
  42. SYMREF = b"ref: "
  43. LOCAL_BRANCH_PREFIX = b"refs/heads/"
  44. LOCAL_TAG_PREFIX = b"refs/tags/"
  45. BAD_REF_CHARS = set(b"\177 ~^:?*[")
  46. ANNOTATED_TAG_SUFFIX = b"^{}"
  47. class SymrefLoop(Exception):
  48. """There is a loop between one or more symrefs."""
  49. def __init__(self, ref, depth):
  50. self.ref = ref
  51. self.depth = depth
  52. def parse_symref_value(contents):
  53. """Parse a symref value.
  54. Args:
  55. contents: Contents to parse
  56. Returns: Destination
  57. """
  58. if contents.startswith(SYMREF):
  59. return contents[len(SYMREF) :].rstrip(b"\r\n")
  60. raise ValueError(contents)
  61. def check_ref_format(refname: Ref):
  62. """Check if a refname is correctly formatted.
  63. Implements all the same rules as git-check-ref-format[1].
  64. [1]
  65. http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
  66. Args:
  67. refname: The refname to check
  68. Returns: True if refname is valid, False otherwise
  69. """
  70. # These could be combined into one big expression, but are listed
  71. # separately to parallel [1].
  72. if b"/." in refname or refname.startswith(b"."):
  73. return False
  74. if b"/" not in refname:
  75. return False
  76. if b".." in refname:
  77. return False
  78. for i, c in enumerate(refname):
  79. if ord(refname[i : i + 1]) < 0o40 or c in BAD_REF_CHARS:
  80. return False
  81. if refname[-1] in b"/.":
  82. return False
  83. if refname.endswith(b".lock"):
  84. return False
  85. if b"@{" in refname:
  86. return False
  87. if b"\\" in refname:
  88. return False
  89. return True
  90. class RefsContainer:
  91. """A container for refs."""
  92. def __init__(self, logger=None):
  93. self._logger = logger
  94. def _log(
  95. self,
  96. ref,
  97. old_sha,
  98. new_sha,
  99. committer=None,
  100. timestamp=None,
  101. timezone=None,
  102. message=None,
  103. ):
  104. if self._logger is None:
  105. return
  106. if message is None:
  107. return
  108. self._logger(ref, old_sha, new_sha, committer, timestamp, timezone, message)
  109. def set_symbolic_ref(
  110. self,
  111. name,
  112. other,
  113. committer=None,
  114. timestamp=None,
  115. timezone=None,
  116. message=None,
  117. ):
  118. """Make a ref point at another ref.
  119. Args:
  120. name: Name of the ref to set
  121. other: Name of the ref to point at
  122. message: Optional message
  123. """
  124. raise NotImplementedError(self.set_symbolic_ref)
  125. def get_packed_refs(self):
  126. """Get contents of the packed-refs file.
  127. Returns: Dictionary mapping ref names to SHA1s
  128. Note: Will return an empty dictionary when no packed-refs file is
  129. present.
  130. """
  131. raise NotImplementedError(self.get_packed_refs)
  132. def add_packed_refs(self, new_refs: Dict[Ref, Optional[ObjectID]]):
  133. """Add the given refs as packed refs.
  134. Args:
  135. new_refs: A mapping of ref names to targets; if a target is None that
  136. means remove the ref
  137. """
  138. raise NotImplementedError(self.add_packed_refs)
  139. def get_peeled(self, name):
  140. """Return the cached peeled value of a ref, if available.
  141. Args:
  142. name: Name of the ref to peel
  143. Returns: The peeled value of the ref. If the ref is known not point to
  144. a tag, this will be the SHA the ref refers to. If the ref may point
  145. to a tag, but no cached information is available, None is returned.
  146. """
  147. return None
  148. def import_refs(
  149. self,
  150. base: Ref,
  151. other: Dict[Ref, ObjectID],
  152. committer: Optional[bytes] = None,
  153. timestamp: Optional[bytes] = None,
  154. timezone: Optional[bytes] = None,
  155. message: Optional[bytes] = None,
  156. prune: bool = False,
  157. ):
  158. if prune:
  159. to_delete = set(self.subkeys(base))
  160. else:
  161. to_delete = set()
  162. for name, value in other.items():
  163. if value is None:
  164. to_delete.add(name)
  165. else:
  166. self.set_if_equals(
  167. b"/".join((base, name)), None, value, message=message
  168. )
  169. if to_delete:
  170. try:
  171. to_delete.remove(name)
  172. except KeyError:
  173. pass
  174. for ref in to_delete:
  175. self.remove_if_equals(b"/".join((base, ref)), None, message=message)
  176. def allkeys(self):
  177. """All refs present in this container."""
  178. raise NotImplementedError(self.allkeys)
  179. def __iter__(self):
  180. return iter(self.allkeys())
  181. def keys(self, base=None):
  182. """Refs present in this container.
  183. Args:
  184. base: An optional base to return refs under.
  185. Returns: An unsorted set of valid refs in this container, including
  186. packed refs.
  187. """
  188. if base is not None:
  189. return self.subkeys(base)
  190. else:
  191. return self.allkeys()
  192. def subkeys(self, base):
  193. """Refs present in this container under a base.
  194. Args:
  195. base: The base to return refs under.
  196. Returns: A set of valid refs in this container under the base; the base
  197. prefix is stripped from the ref names returned.
  198. """
  199. keys = set()
  200. base_len = len(base) + 1
  201. for refname in self.allkeys():
  202. if refname.startswith(base):
  203. keys.add(refname[base_len:])
  204. return keys
  205. def as_dict(self, base=None):
  206. """Return the contents of this container as a dictionary."""
  207. ret = {}
  208. keys = self.keys(base)
  209. if base is None:
  210. base = b""
  211. else:
  212. base = base.rstrip(b"/")
  213. for key in keys:
  214. try:
  215. ret[key] = self[(base + b"/" + key).strip(b"/")]
  216. except (SymrefLoop, KeyError):
  217. continue # Unable to resolve
  218. return ret
  219. def _check_refname(self, name):
  220. """Ensure a refname is valid and lives in refs or is HEAD.
  221. HEAD is not a valid refname according to git-check-ref-format, but this
  222. class needs to be able to touch HEAD. Also, check_ref_format expects
  223. refnames without the leading 'refs/', but this class requires that
  224. so it cannot touch anything outside the refs dir (or HEAD).
  225. Args:
  226. name: The name of the reference.
  227. Raises:
  228. KeyError: if a refname is not HEAD or is otherwise not valid.
  229. """
  230. if name in (HEADREF, b"refs/stash"):
  231. return
  232. if not name.startswith(b"refs/") or not check_ref_format(name[5:]):
  233. raise RefFormatError(name)
  234. def read_ref(self, refname):
  235. """Read a reference without following any references.
  236. Args:
  237. refname: The name of the reference
  238. Returns: The contents of the ref file, or None if it does
  239. not exist.
  240. """
  241. contents = self.read_loose_ref(refname)
  242. if not contents:
  243. contents = self.get_packed_refs().get(refname, None)
  244. return contents
  245. def read_loose_ref(self, name):
  246. """Read a loose reference and return its contents.
  247. Args:
  248. name: the refname to read
  249. Returns: The contents of the ref file, or None if it does
  250. not exist.
  251. """
  252. raise NotImplementedError(self.read_loose_ref)
  253. def follow(self, name):
  254. """Follow a reference name.
  255. Returns: a tuple of (refnames, sha), wheres refnames are the names of
  256. references in the chain
  257. """
  258. contents = SYMREF + name
  259. depth = 0
  260. refnames = []
  261. while contents.startswith(SYMREF):
  262. refname = contents[len(SYMREF) :]
  263. refnames.append(refname)
  264. contents = self.read_ref(refname)
  265. if not contents:
  266. break
  267. depth += 1
  268. if depth > 5:
  269. raise SymrefLoop(name, depth)
  270. return refnames, contents
  271. def __contains__(self, refname):
  272. if self.read_ref(refname):
  273. return True
  274. return False
  275. def __getitem__(self, name):
  276. """Get the SHA1 for a reference name.
  277. This method follows all symbolic references.
  278. """
  279. _, sha = self.follow(name)
  280. if sha is None:
  281. raise KeyError(name)
  282. return sha
  283. def set_if_equals(
  284. self,
  285. name,
  286. old_ref,
  287. new_ref,
  288. committer=None,
  289. timestamp=None,
  290. timezone=None,
  291. message=None,
  292. ):
  293. """Set a refname to new_ref only if it currently equals old_ref.
  294. This method follows all symbolic references if applicable for the
  295. subclass, and can be used to perform an atomic compare-and-swap
  296. operation.
  297. Args:
  298. name: The refname to set.
  299. old_ref: The old sha the refname must refer to, or None to set
  300. unconditionally.
  301. new_ref: The new sha the refname will refer to.
  302. message: Message for reflog
  303. Returns: True if the set was successful, False otherwise.
  304. """
  305. raise NotImplementedError(self.set_if_equals)
  306. def add_if_new(self, name, ref, committer=None, timestamp=None,
  307. timezone=None, message=None):
  308. """Add a new reference only if it does not already exist.
  309. Args:
  310. name: Ref name
  311. ref: Ref value
  312. """
  313. raise NotImplementedError(self.add_if_new)
  314. def __setitem__(self, name, ref):
  315. """Set a reference name to point to the given SHA1.
  316. This method follows all symbolic references if applicable for the
  317. subclass.
  318. Note: This method unconditionally overwrites the contents of a
  319. reference. To update atomically only if the reference has not
  320. changed, use set_if_equals().
  321. Args:
  322. name: The refname to set.
  323. ref: The new sha the refname will refer to.
  324. """
  325. self.set_if_equals(name, None, ref)
  326. def remove_if_equals(
  327. self,
  328. name,
  329. old_ref,
  330. committer=None,
  331. timestamp=None,
  332. timezone=None,
  333. message=None,
  334. ):
  335. """Remove a refname only if it currently equals old_ref.
  336. This method does not follow symbolic references, even if applicable for
  337. the subclass. It can be used to perform an atomic compare-and-delete
  338. operation.
  339. Args:
  340. name: The refname to delete.
  341. old_ref: The old sha the refname must refer to, or None to
  342. delete unconditionally.
  343. message: Message for reflog
  344. Returns: True if the delete was successful, False otherwise.
  345. """
  346. raise NotImplementedError(self.remove_if_equals)
  347. def __delitem__(self, name):
  348. """Remove a refname.
  349. This method does not follow symbolic references, even if applicable for
  350. the subclass.
  351. Note: This method unconditionally deletes the contents of a reference.
  352. To delete atomically only if the reference has not changed, use
  353. remove_if_equals().
  354. Args:
  355. name: The refname to delete.
  356. """
  357. self.remove_if_equals(name, None)
  358. def get_symrefs(self):
  359. """Get a dict with all symrefs in this container.
  360. Returns: Dictionary mapping source ref to target ref
  361. """
  362. ret = {}
  363. for src in self.allkeys():
  364. try:
  365. dst = parse_symref_value(self.read_ref(src))
  366. except ValueError:
  367. pass
  368. else:
  369. ret[src] = dst
  370. return ret
  371. class DictRefsContainer(RefsContainer):
  372. """RefsContainer backed by a simple dict.
  373. This container does not support symbolic or packed references and is not
  374. threadsafe.
  375. """
  376. def __init__(self, refs, logger=None):
  377. super().__init__(logger=logger)
  378. self._refs = refs
  379. self._peeled = {}
  380. self._watchers = set()
  381. def allkeys(self):
  382. return self._refs.keys()
  383. def read_loose_ref(self, name):
  384. return self._refs.get(name, None)
  385. def get_packed_refs(self):
  386. return {}
  387. def _notify(self, ref, newsha):
  388. for watcher in self._watchers:
  389. watcher._notify((ref, newsha))
  390. def set_symbolic_ref(
  391. self,
  392. name: Ref,
  393. other: Ref,
  394. committer=None,
  395. timestamp=None,
  396. timezone=None,
  397. message=None,
  398. ):
  399. old = self.follow(name)[-1]
  400. new = SYMREF + other
  401. self._refs[name] = new
  402. self._notify(name, new)
  403. self._log(
  404. name,
  405. old,
  406. new,
  407. committer=committer,
  408. timestamp=timestamp,
  409. timezone=timezone,
  410. message=message,
  411. )
  412. def set_if_equals(
  413. self,
  414. name,
  415. old_ref,
  416. new_ref,
  417. committer=None,
  418. timestamp=None,
  419. timezone=None,
  420. message=None,
  421. ):
  422. if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
  423. return False
  424. realnames, _ = self.follow(name)
  425. for realname in realnames:
  426. self._check_refname(realname)
  427. old = self._refs.get(realname)
  428. self._refs[realname] = new_ref
  429. self._notify(realname, new_ref)
  430. self._log(
  431. realname,
  432. old,
  433. new_ref,
  434. committer=committer,
  435. timestamp=timestamp,
  436. timezone=timezone,
  437. message=message,
  438. )
  439. return True
  440. def add_if_new(
  441. self,
  442. name: Ref,
  443. ref: ObjectID,
  444. committer=None,
  445. timestamp=None,
  446. timezone=None,
  447. message: Optional[bytes] = None,
  448. ):
  449. if name in self._refs:
  450. return False
  451. self._refs[name] = ref
  452. self._notify(name, ref)
  453. self._log(
  454. name,
  455. None,
  456. ref,
  457. committer=committer,
  458. timestamp=timestamp,
  459. timezone=timezone,
  460. message=message,
  461. )
  462. return True
  463. def remove_if_equals(
  464. self,
  465. name,
  466. old_ref,
  467. committer=None,
  468. timestamp=None,
  469. timezone=None,
  470. message=None,
  471. ):
  472. if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
  473. return False
  474. try:
  475. old = self._refs.pop(name)
  476. except KeyError:
  477. pass
  478. else:
  479. self._notify(name, None)
  480. self._log(
  481. name,
  482. old,
  483. None,
  484. committer=committer,
  485. timestamp=timestamp,
  486. timezone=timezone,
  487. message=message,
  488. )
  489. return True
  490. def get_peeled(self, name):
  491. return self._peeled.get(name)
  492. def _update(self, refs):
  493. """Update multiple refs; intended only for testing."""
  494. # TODO(dborowitz): replace this with a public function that uses
  495. # set_if_equal.
  496. for ref, sha in refs.items():
  497. self.set_if_equals(ref, None, sha)
  498. def _update_peeled(self, peeled):
  499. """Update cached peeled refs; intended only for testing."""
  500. self._peeled.update(peeled)
  501. class InfoRefsContainer(RefsContainer):
  502. """Refs container that reads refs from a info/refs file."""
  503. def __init__(self, f):
  504. self._refs = {}
  505. self._peeled = {}
  506. for line in f.readlines():
  507. sha, name = line.rstrip(b"\n").split(b"\t")
  508. if name.endswith(ANNOTATED_TAG_SUFFIX):
  509. name = name[:-3]
  510. if not check_ref_format(name):
  511. raise ValueError("invalid ref name %r" % name)
  512. self._peeled[name] = sha
  513. else:
  514. if not check_ref_format(name):
  515. raise ValueError("invalid ref name %r" % name)
  516. self._refs[name] = sha
  517. def allkeys(self):
  518. return self._refs.keys()
  519. def read_loose_ref(self, name):
  520. return self._refs.get(name, None)
  521. def get_packed_refs(self):
  522. return {}
  523. def get_peeled(self, name):
  524. try:
  525. return self._peeled[name]
  526. except KeyError:
  527. return self._refs[name]
  528. class DiskRefsContainer(RefsContainer):
  529. """Refs container that reads refs from disk."""
  530. def __init__(self, path, worktree_path=None, logger=None):
  531. super().__init__(logger=logger)
  532. if getattr(path, "encode", None) is not None:
  533. path = os.fsencode(path)
  534. self.path = path
  535. if worktree_path is None:
  536. worktree_path = path
  537. if getattr(worktree_path, "encode", None) is not None:
  538. worktree_path = os.fsencode(worktree_path)
  539. self.worktree_path = worktree_path
  540. self._packed_refs = None
  541. self._peeled_refs = None
  542. def __repr__(self):
  543. return "{}({!r})".format(self.__class__.__name__, self.path)
  544. def subkeys(self, base):
  545. subkeys = set()
  546. path = self.refpath(base)
  547. for root, unused_dirs, files in os.walk(path):
  548. dir = root[len(path) :]
  549. if os.path.sep != "/":
  550. dir = dir.replace(os.fsencode(os.path.sep), b"/")
  551. dir = dir.strip(b"/")
  552. for filename in files:
  553. refname = b"/".join(([dir] if dir else []) + [filename])
  554. # check_ref_format requires at least one /, so we prepend the
  555. # base before calling it.
  556. if check_ref_format(base + b"/" + refname):
  557. subkeys.add(refname)
  558. for key in self.get_packed_refs():
  559. if key.startswith(base):
  560. subkeys.add(key[len(base) :].strip(b"/"))
  561. return subkeys
  562. def allkeys(self):
  563. allkeys = set()
  564. if os.path.exists(self.refpath(HEADREF)):
  565. allkeys.add(HEADREF)
  566. path = self.refpath(b"")
  567. refspath = self.refpath(b"refs")
  568. for root, unused_dirs, files in os.walk(refspath):
  569. dir = root[len(path) :]
  570. if os.path.sep != "/":
  571. dir = dir.replace(os.fsencode(os.path.sep), b"/")
  572. for filename in files:
  573. refname = b"/".join([dir, filename])
  574. if check_ref_format(refname):
  575. allkeys.add(refname)
  576. allkeys.update(self.get_packed_refs())
  577. return allkeys
  578. def refpath(self, name):
  579. """Return the disk path of a ref."""
  580. if os.path.sep != "/":
  581. name = name.replace(b"/", os.fsencode(os.path.sep))
  582. # TODO: as the 'HEAD' reference is working tree specific, it
  583. # should actually not be a part of RefsContainer
  584. if name == HEADREF:
  585. return os.path.join(self.worktree_path, name)
  586. else:
  587. return os.path.join(self.path, name)
  588. def get_packed_refs(self):
  589. """Get contents of the packed-refs file.
  590. Returns: Dictionary mapping ref names to SHA1s
  591. Note: Will return an empty dictionary when no packed-refs file is
  592. present.
  593. """
  594. # TODO: invalidate the cache on repacking
  595. if self._packed_refs is None:
  596. # set both to empty because we want _peeled_refs to be
  597. # None if and only if _packed_refs is also None.
  598. self._packed_refs = {}
  599. self._peeled_refs = {}
  600. path = os.path.join(self.path, b"packed-refs")
  601. try:
  602. f = GitFile(path, "rb")
  603. except FileNotFoundError:
  604. return {}
  605. with f:
  606. first_line = next(iter(f)).rstrip()
  607. if first_line.startswith(b"# pack-refs") and b" peeled" in first_line:
  608. for sha, name, peeled in read_packed_refs_with_peeled(f):
  609. self._packed_refs[name] = sha
  610. if peeled:
  611. self._peeled_refs[name] = peeled
  612. else:
  613. f.seek(0)
  614. for sha, name in read_packed_refs(f):
  615. self._packed_refs[name] = sha
  616. return self._packed_refs
  617. def add_packed_refs(self, new_refs: Dict[Ref, Optional[ObjectID]]):
  618. """Add the given refs as packed refs.
  619. Args:
  620. new_refs: A mapping of ref names to targets; if a target is None that
  621. means remove the ref
  622. """
  623. if not new_refs:
  624. return
  625. path = os.path.join(self.path, b"packed-refs")
  626. with GitFile(path, "wb") as f:
  627. # reread cached refs from disk, while holding the lock
  628. packed_refs = self.get_packed_refs().copy()
  629. for ref, target in new_refs.items():
  630. # sanity check
  631. if ref == HEADREF:
  632. raise ValueError("cannot pack HEAD")
  633. # remove any loose refs pointing to this one -- please
  634. # note that this bypasses remove_if_equals as we don't
  635. # want to affect packed refs in here
  636. try:
  637. os.remove(self.refpath(ref))
  638. except OSError:
  639. pass
  640. if target is not None:
  641. packed_refs[ref] = target
  642. else:
  643. packed_refs.pop(ref, None)
  644. write_packed_refs(f, packed_refs, self._peeled_refs)
  645. self._packed_refs = packed_refs
  646. def get_peeled(self, name):
  647. """Return the cached peeled value of a ref, if available.
  648. Args:
  649. name: Name of the ref to peel
  650. Returns: The peeled value of the ref. If the ref is known not point to
  651. a tag, this will be the SHA the ref refers to. If the ref may point
  652. to a tag, but no cached information is available, None is returned.
  653. """
  654. self.get_packed_refs()
  655. if self._peeled_refs is None or name not in self._packed_refs:
  656. # No cache: no peeled refs were read, or this ref is loose
  657. return None
  658. if name in self._peeled_refs:
  659. return self._peeled_refs[name]
  660. else:
  661. # Known not peelable
  662. return self[name]
  663. def read_loose_ref(self, name):
  664. """Read a reference file and return its contents.
  665. If the reference file a symbolic reference, only read the first line of
  666. the file. Otherwise, only read the first 40 bytes.
  667. Args:
  668. name: the refname to read, relative to refpath
  669. Returns: The contents of the ref file, or None if the file does not
  670. exist.
  671. Raises:
  672. IOError: if any other error occurs
  673. """
  674. filename = self.refpath(name)
  675. try:
  676. with GitFile(filename, "rb") as f:
  677. header = f.read(len(SYMREF))
  678. if header == SYMREF:
  679. # Read only the first line
  680. return header + next(iter(f)).rstrip(b"\r\n")
  681. else:
  682. # Read only the first 40 bytes
  683. return header + f.read(40 - len(SYMREF))
  684. except (OSError, UnicodeError):
  685. # don't assume anything specific about the error; in
  686. # particular, invalid or forbidden paths can raise weird
  687. # errors depending on the specific operating system
  688. return None
  689. def _remove_packed_ref(self, name):
  690. if self._packed_refs is None:
  691. return
  692. filename = os.path.join(self.path, b"packed-refs")
  693. # reread cached refs from disk, while holding the lock
  694. f = GitFile(filename, "wb")
  695. try:
  696. self._packed_refs = None
  697. self.get_packed_refs()
  698. if name not in self._packed_refs:
  699. return
  700. del self._packed_refs[name]
  701. with suppress(KeyError):
  702. del self._peeled_refs[name]
  703. write_packed_refs(f, self._packed_refs, self._peeled_refs)
  704. f.close()
  705. finally:
  706. f.abort()
  707. def set_symbolic_ref(
  708. self,
  709. name,
  710. other,
  711. committer=None,
  712. timestamp=None,
  713. timezone=None,
  714. message=None,
  715. ):
  716. """Make a ref point at another ref.
  717. Args:
  718. name: Name of the ref to set
  719. other: Name of the ref to point at
  720. message: Optional message to describe the change
  721. """
  722. self._check_refname(name)
  723. self._check_refname(other)
  724. filename = self.refpath(name)
  725. f = GitFile(filename, "wb")
  726. try:
  727. f.write(SYMREF + other + b"\n")
  728. sha = self.follow(name)[-1]
  729. self._log(
  730. name,
  731. sha,
  732. sha,
  733. committer=committer,
  734. timestamp=timestamp,
  735. timezone=timezone,
  736. message=message,
  737. )
  738. except BaseException:
  739. f.abort()
  740. raise
  741. else:
  742. f.close()
  743. def set_if_equals(
  744. self,
  745. name,
  746. old_ref,
  747. new_ref,
  748. committer=None,
  749. timestamp=None,
  750. timezone=None,
  751. message=None,
  752. ):
  753. """Set a refname to new_ref only if it currently equals old_ref.
  754. This method follows all symbolic references, and can be used to perform
  755. an atomic compare-and-swap operation.
  756. Args:
  757. name: The refname to set.
  758. old_ref: The old sha the refname must refer to, or None to set
  759. unconditionally.
  760. new_ref: The new sha the refname will refer to.
  761. message: Set message for reflog
  762. Returns: True if the set was successful, False otherwise.
  763. """
  764. self._check_refname(name)
  765. try:
  766. realnames, _ = self.follow(name)
  767. realname = realnames[-1]
  768. except (KeyError, IndexError, SymrefLoop):
  769. realname = name
  770. filename = self.refpath(realname)
  771. # make sure none of the ancestor folders is in packed refs
  772. probe_ref = os.path.dirname(realname)
  773. packed_refs = self.get_packed_refs()
  774. while probe_ref:
  775. if packed_refs.get(probe_ref, None) is not None:
  776. raise NotADirectoryError(filename)
  777. probe_ref = os.path.dirname(probe_ref)
  778. ensure_dir_exists(os.path.dirname(filename))
  779. with GitFile(filename, "wb") as f:
  780. if old_ref is not None:
  781. try:
  782. # read again while holding the lock
  783. orig_ref = self.read_loose_ref(realname)
  784. if orig_ref is None:
  785. orig_ref = self.get_packed_refs().get(realname, ZERO_SHA)
  786. if orig_ref != old_ref:
  787. f.abort()
  788. return False
  789. except OSError:
  790. f.abort()
  791. raise
  792. try:
  793. f.write(new_ref + b"\n")
  794. except OSError:
  795. f.abort()
  796. raise
  797. self._log(
  798. realname,
  799. old_ref,
  800. new_ref,
  801. committer=committer,
  802. timestamp=timestamp,
  803. timezone=timezone,
  804. message=message,
  805. )
  806. return True
  807. def add_if_new(
  808. self,
  809. name: bytes,
  810. ref: bytes,
  811. committer=None,
  812. timestamp=None,
  813. timezone=None,
  814. message: Optional[bytes] = None,
  815. ):
  816. """Add a new reference only if it does not already exist.
  817. This method follows symrefs, and only ensures that the last ref in the
  818. chain does not exist.
  819. Args:
  820. name: The refname to set.
  821. ref: The new sha the refname will refer to.
  822. message: Optional message for reflog
  823. Returns: True if the add was successful, False otherwise.
  824. """
  825. try:
  826. realnames, contents = self.follow(name)
  827. if contents is not None:
  828. return False
  829. realname = realnames[-1]
  830. except (KeyError, IndexError):
  831. realname = name
  832. self._check_refname(realname)
  833. filename = self.refpath(realname)
  834. ensure_dir_exists(os.path.dirname(filename))
  835. with GitFile(filename, "wb") as f:
  836. if os.path.exists(filename) or name in self.get_packed_refs():
  837. f.abort()
  838. return False
  839. try:
  840. f.write(ref + b"\n")
  841. except OSError:
  842. f.abort()
  843. raise
  844. else:
  845. self._log(
  846. name,
  847. None,
  848. ref,
  849. committer=committer,
  850. timestamp=timestamp,
  851. timezone=timezone,
  852. message=message,
  853. )
  854. return True
  855. def remove_if_equals(
  856. self,
  857. name,
  858. old_ref,
  859. committer=None,
  860. timestamp=None,
  861. timezone=None,
  862. message=None,
  863. ):
  864. """Remove a refname only if it currently equals old_ref.
  865. This method does not follow symbolic references. It can be used to
  866. perform an atomic compare-and-delete operation.
  867. Args:
  868. name: The refname to delete.
  869. old_ref: The old sha the refname must refer to, or None to
  870. delete unconditionally.
  871. message: Optional message
  872. Returns: True if the delete was successful, False otherwise.
  873. """
  874. self._check_refname(name)
  875. filename = self.refpath(name)
  876. ensure_dir_exists(os.path.dirname(filename))
  877. f = GitFile(filename, "wb")
  878. try:
  879. if old_ref is not None:
  880. orig_ref = self.read_loose_ref(name)
  881. if orig_ref is None:
  882. orig_ref = self.get_packed_refs().get(name, ZERO_SHA)
  883. if orig_ref != old_ref:
  884. return False
  885. # remove the reference file itself
  886. try:
  887. found = os.path.lexists(filename)
  888. except OSError:
  889. # may only be packed, or otherwise unstorable
  890. found = False
  891. if found:
  892. os.remove(filename)
  893. self._remove_packed_ref(name)
  894. self._log(
  895. name,
  896. old_ref,
  897. None,
  898. committer=committer,
  899. timestamp=timestamp,
  900. timezone=timezone,
  901. message=message,
  902. )
  903. finally:
  904. # never write, we just wanted the lock
  905. f.abort()
  906. # outside of the lock, clean-up any parent directory that might now
  907. # be empty. this ensures that re-creating a reference of the same
  908. # name of what was previously a directory works as expected
  909. parent = name
  910. while True:
  911. try:
  912. parent, _ = parent.rsplit(b"/", 1)
  913. except ValueError:
  914. break
  915. if parent == b'refs':
  916. break
  917. parent_filename = self.refpath(parent)
  918. try:
  919. os.rmdir(parent_filename)
  920. except OSError:
  921. # this can be caused by the parent directory being
  922. # removed by another process, being not empty, etc.
  923. # in any case, this is non fatal because we already
  924. # removed the reference, just ignore it
  925. break
  926. return True
  927. def _split_ref_line(line):
  928. """Split a single ref line into a tuple of SHA1 and name."""
  929. fields = line.rstrip(b"\n\r").split(b" ")
  930. if len(fields) != 2:
  931. raise PackedRefsException("invalid ref line %r" % line)
  932. sha, name = fields
  933. if not valid_hexsha(sha):
  934. raise PackedRefsException("Invalid hex sha %r" % sha)
  935. if not check_ref_format(name):
  936. raise PackedRefsException("invalid ref name %r" % name)
  937. return (sha, name)
  938. def read_packed_refs(f):
  939. """Read a packed refs file.
  940. Args:
  941. f: file-like object to read from
  942. Returns: Iterator over tuples with SHA1s and ref names.
  943. """
  944. for line in f:
  945. if line.startswith(b"#"):
  946. # Comment
  947. continue
  948. if line.startswith(b"^"):
  949. raise PackedRefsException("found peeled ref in packed-refs without peeled")
  950. yield _split_ref_line(line)
  951. def read_packed_refs_with_peeled(f):
  952. """Read a packed refs file including peeled refs.
  953. Assumes the "# pack-refs with: peeled" line was already read. Yields tuples
  954. with ref names, SHA1s, and peeled SHA1s (or None).
  955. Args:
  956. f: file-like object to read from, seek'ed to the second line
  957. """
  958. last = None
  959. for line in f:
  960. if line[0] == b"#":
  961. continue
  962. line = line.rstrip(b"\r\n")
  963. if line.startswith(b"^"):
  964. if not last:
  965. raise PackedRefsException("unexpected peeled ref line")
  966. if not valid_hexsha(line[1:]):
  967. raise PackedRefsException("Invalid hex sha %r" % line[1:])
  968. sha, name = _split_ref_line(last)
  969. last = None
  970. yield (sha, name, line[1:])
  971. else:
  972. if last:
  973. sha, name = _split_ref_line(last)
  974. yield (sha, name, None)
  975. last = line
  976. if last:
  977. sha, name = _split_ref_line(last)
  978. yield (sha, name, None)
  979. def write_packed_refs(f, packed_refs, peeled_refs=None):
  980. """Write a packed refs file.
  981. Args:
  982. f: empty file-like object to write to
  983. packed_refs: dict of refname to sha of packed refs to write
  984. peeled_refs: dict of refname to peeled value of sha
  985. """
  986. if peeled_refs is None:
  987. peeled_refs = {}
  988. else:
  989. f.write(b"# pack-refs with: peeled\n")
  990. for refname in sorted(packed_refs.keys()):
  991. f.write(git_line(packed_refs[refname], refname))
  992. if refname in peeled_refs:
  993. f.write(b"^" + peeled_refs[refname] + b"\n")
  994. def read_info_refs(f):
  995. ret = {}
  996. for line in f.readlines():
  997. (sha, name) = line.rstrip(b"\r\n").split(b"\t", 1)
  998. ret[name] = sha
  999. return ret
  1000. def write_info_refs(refs, store):
  1001. """Generate info refs."""
  1002. for name, sha in sorted(refs.items()):
  1003. # get_refs() includes HEAD as a special case, but we don't want to
  1004. # advertise it
  1005. if name == HEADREF:
  1006. continue
  1007. try:
  1008. o = store[sha]
  1009. except KeyError:
  1010. continue
  1011. peeled = store.peel_sha(sha)
  1012. yield o.id + b"\t" + name + b"\n"
  1013. if o.id != peeled.id:
  1014. yield peeled.id + b"\t" + name + ANNOTATED_TAG_SUFFIX + b"\n"
  1015. def is_local_branch(x):
  1016. return x.startswith(LOCAL_BRANCH_PREFIX)
  1017. def strip_peeled_refs(refs):
  1018. """Remove all peeled refs"""
  1019. return {
  1020. ref: sha
  1021. for (ref, sha) in refs.items()
  1022. if not ref.endswith(ANNOTATED_TAG_SUFFIX)
  1023. }
  1024. def _set_origin_head(refs, origin, origin_head):
  1025. # set refs/remotes/origin/HEAD
  1026. origin_base = b"refs/remotes/" + origin + b"/"
  1027. if origin_head and origin_head.startswith(LOCAL_BRANCH_PREFIX):
  1028. origin_ref = origin_base + HEADREF
  1029. target_ref = origin_base + origin_head[len(LOCAL_BRANCH_PREFIX) :]
  1030. if target_ref in refs:
  1031. refs.set_symbolic_ref(origin_ref, target_ref)
  1032. def _set_default_branch(
  1033. refs: RefsContainer, origin: bytes, origin_head: bytes, branch: bytes,
  1034. ref_message: Optional[bytes]) -> bytes:
  1035. """Set the default branch.
  1036. """
  1037. origin_base = b"refs/remotes/" + origin + b"/"
  1038. if branch:
  1039. origin_ref = origin_base + branch
  1040. if origin_ref in refs:
  1041. local_ref = LOCAL_BRANCH_PREFIX + branch
  1042. refs.add_if_new(
  1043. local_ref, refs[origin_ref], ref_message
  1044. )
  1045. head_ref = local_ref
  1046. elif LOCAL_TAG_PREFIX + branch in refs:
  1047. head_ref = LOCAL_TAG_PREFIX + branch
  1048. else:
  1049. raise ValueError(
  1050. "%r is not a valid branch or tag" % os.fsencode(branch)
  1051. )
  1052. elif origin_head:
  1053. head_ref = origin_head
  1054. if origin_head.startswith(LOCAL_BRANCH_PREFIX):
  1055. origin_ref = origin_base + origin_head[len(LOCAL_BRANCH_PREFIX) :]
  1056. else:
  1057. origin_ref = origin_head
  1058. try:
  1059. refs.add_if_new(
  1060. head_ref, refs[origin_ref], ref_message
  1061. )
  1062. except KeyError:
  1063. pass
  1064. else:
  1065. raise ValueError('neither origin_head nor branch are provided')
  1066. return head_ref
  1067. def _set_head(refs, head_ref, ref_message):
  1068. if head_ref.startswith(LOCAL_TAG_PREFIX):
  1069. # detach HEAD at specified tag
  1070. head = refs[head_ref]
  1071. if isinstance(head, Tag):
  1072. _cls, obj = head.object
  1073. head = obj.get_object(obj).id
  1074. del refs[HEADREF]
  1075. refs.set_if_equals(
  1076. HEADREF, None, head, message=ref_message
  1077. )
  1078. else:
  1079. # set HEAD to specific branch
  1080. try:
  1081. head = refs[head_ref]
  1082. refs.set_symbolic_ref(HEADREF, head_ref)
  1083. refs.set_if_equals(HEADREF, None, head, message=ref_message)
  1084. except KeyError:
  1085. head = None
  1086. return head
  1087. def _import_remote_refs(
  1088. refs_container: RefsContainer,
  1089. remote_name: str,
  1090. refs: Dict[str, str],
  1091. message: Optional[bytes] = None,
  1092. prune: bool = False,
  1093. prune_tags: bool = False,
  1094. ):
  1095. stripped_refs = strip_peeled_refs(refs)
  1096. branches = {
  1097. n[len(LOCAL_BRANCH_PREFIX) :]: v
  1098. for (n, v) in stripped_refs.items()
  1099. if n.startswith(LOCAL_BRANCH_PREFIX)
  1100. }
  1101. refs_container.import_refs(
  1102. b"refs/remotes/" + remote_name.encode(),
  1103. branches,
  1104. message=message,
  1105. prune=prune,
  1106. )
  1107. tags = {
  1108. n[len(LOCAL_TAG_PREFIX) :]: v
  1109. for (n, v) in stripped_refs.items()
  1110. if n.startswith(LOCAL_TAG_PREFIX) and not n.endswith(ANNOTATED_TAG_SUFFIX)
  1111. }
  1112. refs_container.import_refs(LOCAL_TAG_PREFIX, tags, message=message, prune=prune_tags)