refs.py 37 KB

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