2
0

refs.py 39 KB

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