refs.py 40 KB

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