refs.py 37 KB

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