refs.py 39 KB

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