refs.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. # refs.py -- For dealing with git refs
  2. # Copyright (C) 2008-2013 Jelmer Vernooij <jelmer@samba.org>
  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 errno
  23. import os
  24. import sys
  25. from dulwich.errors import (
  26. PackedRefsException,
  27. RefFormatError,
  28. )
  29. from dulwich.objects import (
  30. git_line,
  31. valid_hexsha,
  32. ZERO_SHA,
  33. )
  34. from dulwich.file import (
  35. GitFile,
  36. ensure_dir_exists,
  37. )
  38. SYMREF = b'ref: '
  39. LOCAL_BRANCH_PREFIX = b'refs/heads/'
  40. BAD_REF_CHARS = set(b'\177 ~^:?*[')
  41. def check_ref_format(refname):
  42. """Check if a refname is correctly formatted.
  43. Implements all the same rules as git-check-ref-format[1].
  44. [1] http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
  45. :param refname: The refname to check
  46. :return: True if refname is valid, False otherwise
  47. """
  48. # These could be combined into one big expression, but are listed separately
  49. # to parallel [1].
  50. if b'/.' in refname or refname.startswith(b'.'):
  51. return False
  52. if b'/' not in refname:
  53. return False
  54. if b'..' in refname:
  55. return False
  56. for i, c in enumerate(refname):
  57. if ord(refname[i:i+1]) < 0o40 or c in BAD_REF_CHARS:
  58. return False
  59. if refname[-1] in b'/.':
  60. return False
  61. if refname.endswith(b'.lock'):
  62. return False
  63. if b'@{' in refname:
  64. return False
  65. if b'\\' in refname:
  66. return False
  67. return True
  68. class RefsContainer(object):
  69. """A container for refs."""
  70. def set_symbolic_ref(self, name, other):
  71. """Make a ref point at another ref.
  72. :param name: Name of the ref to set
  73. :param other: Name of the ref to point at
  74. """
  75. raise NotImplementedError(self.set_symbolic_ref)
  76. def get_packed_refs(self):
  77. """Get contents of the packed-refs file.
  78. :return: Dictionary mapping ref names to SHA1s
  79. :note: Will return an empty dictionary when no packed-refs file is
  80. present.
  81. """
  82. raise NotImplementedError(self.get_packed_refs)
  83. def get_peeled(self, name):
  84. """Return the cached peeled value of a ref, if available.
  85. :param name: Name of the ref to peel
  86. :return: The peeled value of the ref. If the ref is known not point to a
  87. tag, this will be the SHA the ref refers to. If the ref may point to
  88. a tag, but no cached information is available, None is returned.
  89. """
  90. return None
  91. def import_refs(self, base, other):
  92. for name, value in other.items():
  93. self[b'/'.join((base, name))] = value
  94. def allkeys(self):
  95. """All refs present in this container."""
  96. raise NotImplementedError(self.allkeys)
  97. def keys(self, base=None):
  98. """Refs present in this container.
  99. :param base: An optional base to return refs under.
  100. :return: An unsorted set of valid refs in this container, including
  101. packed refs.
  102. """
  103. if base is not None:
  104. return self.subkeys(base)
  105. else:
  106. return self.allkeys()
  107. def subkeys(self, base):
  108. """Refs present in this container under a base.
  109. :param base: The base to return refs under.
  110. :return: A set of valid refs in this container under the base; the base
  111. prefix is stripped from the ref names returned.
  112. """
  113. keys = set()
  114. base_len = len(base) + 1
  115. for refname in self.allkeys():
  116. if refname.startswith(base):
  117. keys.add(refname[base_len:])
  118. return keys
  119. def as_dict(self, base=None):
  120. """Return the contents of this container as a dictionary.
  121. """
  122. ret = {}
  123. keys = self.keys(base)
  124. if base is None:
  125. base = b''
  126. else:
  127. base = base.rstrip(b'/')
  128. for key in keys:
  129. try:
  130. ret[key] = self[(base + b'/' + key).strip(b'/')]
  131. except KeyError:
  132. continue # Unable to resolve
  133. return ret
  134. def _check_refname(self, name):
  135. """Ensure a refname is valid and lives in refs or is HEAD.
  136. HEAD is not a valid refname according to git-check-ref-format, but this
  137. class needs to be able to touch HEAD. Also, check_ref_format expects
  138. refnames without the leading 'refs/', but this class requires that
  139. so it cannot touch anything outside the refs dir (or HEAD).
  140. :param name: The name of the reference.
  141. :raises KeyError: if a refname is not HEAD or is otherwise not valid.
  142. """
  143. if name in (b'HEAD', b'refs/stash'):
  144. return
  145. if not name.startswith(b'refs/') or not check_ref_format(name[5:]):
  146. raise RefFormatError(name)
  147. def read_ref(self, refname):
  148. """Read a reference without following any references.
  149. :param refname: The name of the reference
  150. :return: The contents of the ref file, or None if it does
  151. not exist.
  152. """
  153. contents = self.read_loose_ref(refname)
  154. if not contents:
  155. contents = self.get_packed_refs().get(refname, None)
  156. return contents
  157. def read_loose_ref(self, name):
  158. """Read a loose reference and return its contents.
  159. :param name: the refname to read
  160. :return: The contents of the ref file, or None if it does
  161. not exist.
  162. """
  163. raise NotImplementedError(self.read_loose_ref)
  164. def follow(self, name):
  165. """Follow a reference name.
  166. :return: a tuple of (refnames, sha), wheres refnames are the names of
  167. references in the chain
  168. """
  169. contents = SYMREF + name
  170. depth = 0
  171. refnames = []
  172. while contents.startswith(SYMREF):
  173. refname = contents[len(SYMREF):]
  174. refnames.append(refname)
  175. contents = self.read_ref(refname)
  176. if not contents:
  177. break
  178. depth += 1
  179. if depth > 5:
  180. raise KeyError(name)
  181. return refnames, contents
  182. def _follow(self, name):
  183. import warnings
  184. warnings.warn(
  185. "RefsContainer._follow is deprecated. Use RefsContainer.follow instead.",
  186. DeprecationWarning)
  187. refnames, contents = self.follow(name)
  188. if not refnames:
  189. return (None, contents)
  190. return (refnames[-1], contents)
  191. def __contains__(self, refname):
  192. if self.read_ref(refname):
  193. return True
  194. return False
  195. def __getitem__(self, name):
  196. """Get the SHA1 for a reference name.
  197. This method follows all symbolic references.
  198. """
  199. _, sha = self.follow(name)
  200. if sha is None:
  201. raise KeyError(name)
  202. return sha
  203. def set_if_equals(self, name, old_ref, new_ref):
  204. """Set a refname to new_ref only if it currently equals old_ref.
  205. This method follows all symbolic references if applicable for the
  206. subclass, and can be used to perform an atomic compare-and-swap
  207. operation.
  208. :param name: The refname to set.
  209. :param old_ref: The old sha the refname must refer to, or None to set
  210. unconditionally.
  211. :param new_ref: The new sha the refname will refer to.
  212. :return: True if the set was successful, False otherwise.
  213. """
  214. raise NotImplementedError(self.set_if_equals)
  215. def add_if_new(self, name, ref):
  216. """Add a new reference only if it does not already exist."""
  217. raise NotImplementedError(self.add_if_new)
  218. def __setitem__(self, name, ref):
  219. """Set a reference name to point to the given SHA1.
  220. This method follows all symbolic references if applicable for the
  221. subclass.
  222. :note: This method unconditionally overwrites the contents of a
  223. reference. To update atomically only if the reference has not
  224. changed, use set_if_equals().
  225. :param name: The refname to set.
  226. :param ref: The new sha the refname will refer to.
  227. """
  228. self.set_if_equals(name, None, ref)
  229. def remove_if_equals(self, name, old_ref):
  230. """Remove a refname only if it currently equals old_ref.
  231. This method does not follow symbolic references, even if applicable for
  232. the subclass. It can be used to perform an atomic compare-and-delete
  233. operation.
  234. :param name: The refname to delete.
  235. :param old_ref: The old sha the refname must refer to, or None to delete
  236. unconditionally.
  237. :return: True if the delete was successful, False otherwise.
  238. """
  239. raise NotImplementedError(self.remove_if_equals)
  240. def __delitem__(self, name):
  241. """Remove a refname.
  242. This method does not follow symbolic references, even if applicable for
  243. the subclass.
  244. :note: This method unconditionally deletes the contents of a reference.
  245. To delete atomically only if the reference has not changed, use
  246. remove_if_equals().
  247. :param name: The refname to delete.
  248. """
  249. self.remove_if_equals(name, None)
  250. class DictRefsContainer(RefsContainer):
  251. """RefsContainer backed by a simple dict.
  252. This container does not support symbolic or packed references and is not
  253. threadsafe.
  254. """
  255. def __init__(self, refs):
  256. self._refs = refs
  257. self._peeled = {}
  258. def allkeys(self):
  259. return self._refs.keys()
  260. def read_loose_ref(self, name):
  261. return self._refs.get(name, None)
  262. def get_packed_refs(self):
  263. return {}
  264. def set_symbolic_ref(self, name, other):
  265. self._refs[name] = SYMREF + other
  266. def set_if_equals(self, name, old_ref, new_ref):
  267. if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
  268. return False
  269. realnames, _ = self.follow(name)
  270. for realname in realnames:
  271. self._check_refname(realname)
  272. self._refs[realname] = new_ref
  273. return True
  274. def add_if_new(self, name, ref):
  275. if name in self._refs:
  276. return False
  277. self._refs[name] = ref
  278. return True
  279. def remove_if_equals(self, name, old_ref):
  280. if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
  281. return False
  282. try:
  283. del self._refs[name]
  284. except KeyError:
  285. pass
  286. return True
  287. def get_peeled(self, name):
  288. return self._peeled.get(name)
  289. def _update(self, refs):
  290. """Update multiple refs; intended only for testing."""
  291. # TODO(dborowitz): replace this with a public function that uses
  292. # set_if_equal.
  293. self._refs.update(refs)
  294. def _update_peeled(self, peeled):
  295. """Update cached peeled refs; intended only for testing."""
  296. self._peeled.update(peeled)
  297. class InfoRefsContainer(RefsContainer):
  298. """Refs container that reads refs from a info/refs file."""
  299. def __init__(self, f):
  300. self._refs = {}
  301. self._peeled = {}
  302. for l in f.readlines():
  303. sha, name = l.rstrip(b'\n').split(b'\t')
  304. if name.endswith(b'^{}'):
  305. name = name[:-3]
  306. if not check_ref_format(name):
  307. raise ValueError("invalid ref name %r" % name)
  308. self._peeled[name] = sha
  309. else:
  310. if not check_ref_format(name):
  311. raise ValueError("invalid ref name %r" % name)
  312. self._refs[name] = sha
  313. def allkeys(self):
  314. return self._refs.keys()
  315. def read_loose_ref(self, name):
  316. return self._refs.get(name, None)
  317. def get_packed_refs(self):
  318. return {}
  319. def get_peeled(self, name):
  320. try:
  321. return self._peeled[name]
  322. except KeyError:
  323. return self._refs[name]
  324. class DiskRefsContainer(RefsContainer):
  325. """Refs container that reads refs from disk."""
  326. def __init__(self, path):
  327. self.path = path
  328. self._packed_refs = None
  329. self._peeled_refs = None
  330. def __repr__(self):
  331. return "%s(%r)" % (self.__class__.__name__, self.path)
  332. def subkeys(self, base):
  333. subkeys = set()
  334. path = self.refpath(base)
  335. for root, dirs, files in os.walk(path):
  336. dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/")
  337. for filename in files:
  338. refname = (("%s/%s" % (dir, filename))
  339. .strip("/").encode(sys.getfilesystemencoding()))
  340. # check_ref_format requires at least one /, so we prepend the
  341. # base before calling it.
  342. if check_ref_format(base + b'/' + refname):
  343. subkeys.add(refname)
  344. for key in self.get_packed_refs():
  345. if key.startswith(base):
  346. subkeys.add(key[len(base):].strip(b'/'))
  347. return subkeys
  348. def allkeys(self):
  349. allkeys = set()
  350. if os.path.exists(self.refpath(b'HEAD')):
  351. allkeys.add(b'HEAD')
  352. path = self.refpath(b'')
  353. for root, dirs, files in os.walk(self.refpath(b'refs')):
  354. dir = root[len(path):].strip(os.path.sep).replace(os.path.sep, "/")
  355. for filename in files:
  356. refname = ("%s/%s" % (dir, filename)).encode(sys.getfilesystemencoding())
  357. if check_ref_format(refname):
  358. allkeys.add(refname)
  359. allkeys.update(self.get_packed_refs())
  360. return allkeys
  361. def refpath(self, name):
  362. """Return the disk path of a ref.
  363. """
  364. if getattr(self.path, "encode", None) and getattr(name, "decode", None):
  365. name = name.decode(sys.getfilesystemencoding())
  366. if os.path.sep != "/":
  367. name = name.replace("/", os.path.sep)
  368. return os.path.join(self.path, name)
  369. def get_packed_refs(self):
  370. """Get contents of the packed-refs file.
  371. :return: Dictionary mapping ref names to SHA1s
  372. :note: Will return an empty dictionary when no packed-refs file is
  373. present.
  374. """
  375. # TODO: invalidate the cache on repacking
  376. if self._packed_refs is None:
  377. # set both to empty because we want _peeled_refs to be
  378. # None if and only if _packed_refs is also None.
  379. self._packed_refs = {}
  380. self._peeled_refs = {}
  381. path = os.path.join(self.path, 'packed-refs')
  382. try:
  383. f = GitFile(path, 'rb')
  384. except IOError as e:
  385. if e.errno == errno.ENOENT:
  386. return {}
  387. raise
  388. with f:
  389. first_line = next(iter(f)).rstrip()
  390. if (first_line.startswith(b'# pack-refs') and b' peeled' in
  391. first_line):
  392. for sha, name, peeled in read_packed_refs_with_peeled(f):
  393. self._packed_refs[name] = sha
  394. if peeled:
  395. self._peeled_refs[name] = peeled
  396. else:
  397. f.seek(0)
  398. for sha, name in read_packed_refs(f):
  399. self._packed_refs[name] = sha
  400. return self._packed_refs
  401. def get_peeled(self, name):
  402. """Return the cached peeled value of a ref, if available.
  403. :param name: Name of the ref to peel
  404. :return: The peeled value of the ref. If the ref is known not point to a
  405. tag, this will be the SHA the ref refers to. If the ref may point to
  406. a tag, but no cached information is available, None is returned.
  407. """
  408. self.get_packed_refs()
  409. if self._peeled_refs is None or name not in self._packed_refs:
  410. # No cache: no peeled refs were read, or this ref is loose
  411. return None
  412. if name in self._peeled_refs:
  413. return self._peeled_refs[name]
  414. else:
  415. # Known not peelable
  416. return self[name]
  417. def read_loose_ref(self, name):
  418. """Read a reference file and return its contents.
  419. If the reference file a symbolic reference, only read the first line of
  420. the file. Otherwise, only read the first 40 bytes.
  421. :param name: the refname to read, relative to refpath
  422. :return: The contents of the ref file, or None if the file does not
  423. exist.
  424. :raises IOError: if any other error occurs
  425. """
  426. filename = self.refpath(name)
  427. try:
  428. with GitFile(filename, 'rb') as f:
  429. header = f.read(len(SYMREF))
  430. if header == SYMREF:
  431. # Read only the first line
  432. return header + next(iter(f)).rstrip(b'\r\n')
  433. else:
  434. # Read only the first 40 bytes
  435. return header + f.read(40 - len(SYMREF))
  436. except IOError as e:
  437. if e.errno == errno.ENOENT:
  438. return None
  439. raise
  440. def _remove_packed_ref(self, name):
  441. if self._packed_refs is None:
  442. return
  443. filename = os.path.join(self.path, 'packed-refs')
  444. # reread cached refs from disk, while holding the lock
  445. f = GitFile(filename, 'wb')
  446. try:
  447. self._packed_refs = None
  448. self.get_packed_refs()
  449. if name not in self._packed_refs:
  450. return
  451. del self._packed_refs[name]
  452. if name in self._peeled_refs:
  453. del self._peeled_refs[name]
  454. write_packed_refs(f, self._packed_refs, self._peeled_refs)
  455. f.close()
  456. finally:
  457. f.abort()
  458. def set_symbolic_ref(self, name, other):
  459. """Make a ref point at another ref.
  460. :param name: Name of the ref to set
  461. :param other: Name of the ref to point at
  462. """
  463. self._check_refname(name)
  464. self._check_refname(other)
  465. filename = self.refpath(name)
  466. try:
  467. f = GitFile(filename, 'wb')
  468. try:
  469. f.write(SYMREF + other + b'\n')
  470. except (IOError, OSError):
  471. f.abort()
  472. raise
  473. finally:
  474. f.close()
  475. def set_if_equals(self, name, old_ref, new_ref):
  476. """Set a refname to new_ref only if it currently equals old_ref.
  477. This method follows all symbolic references, and can be used to perform
  478. an atomic compare-and-swap operation.
  479. :param name: The refname to set.
  480. :param old_ref: The old sha the refname must refer to, or None to set
  481. unconditionally.
  482. :param new_ref: The new sha the refname will refer to.
  483. :return: True if the set was successful, False otherwise.
  484. """
  485. self._check_refname(name)
  486. try:
  487. realnames, _ = self.follow(name)
  488. realname = realnames[-1]
  489. except (KeyError, IndexError):
  490. realname = name
  491. filename = self.refpath(realname)
  492. ensure_dir_exists(os.path.dirname(filename))
  493. with GitFile(filename, 'wb') as f:
  494. if old_ref is not None:
  495. try:
  496. # read again while holding the lock
  497. orig_ref = self.read_loose_ref(realname)
  498. if orig_ref is None:
  499. orig_ref = self.get_packed_refs().get(realname, ZERO_SHA)
  500. if orig_ref != old_ref:
  501. f.abort()
  502. return False
  503. except (OSError, IOError):
  504. f.abort()
  505. raise
  506. try:
  507. f.write(new_ref + b'\n')
  508. except (OSError, IOError):
  509. f.abort()
  510. raise
  511. return True
  512. def add_if_new(self, name, ref):
  513. """Add a new reference only if it does not already exist.
  514. This method follows symrefs, and only ensures that the last ref in the
  515. chain does not exist.
  516. :param name: The refname to set.
  517. :param ref: The new sha the refname will refer to.
  518. :return: True if the add was successful, False otherwise.
  519. """
  520. try:
  521. realnames, contents = self.follow(name)
  522. if contents is not None:
  523. return False
  524. realname = realnames[-1]
  525. except (KeyError, IndexError):
  526. realname = name
  527. self._check_refname(realname)
  528. filename = self.refpath(realname)
  529. ensure_dir_exists(os.path.dirname(filename))
  530. with GitFile(filename, 'wb') as f:
  531. if os.path.exists(filename) or name in self.get_packed_refs():
  532. f.abort()
  533. return False
  534. try:
  535. f.write(ref + b'\n')
  536. except (OSError, IOError):
  537. f.abort()
  538. raise
  539. return True
  540. def remove_if_equals(self, name, old_ref):
  541. """Remove a refname only if it currently equals old_ref.
  542. This method does not follow symbolic references. It can be used to
  543. perform an atomic compare-and-delete operation.
  544. :param name: The refname to delete.
  545. :param old_ref: The old sha the refname must refer to, or None to delete
  546. unconditionally.
  547. :return: True if the delete was successful, False otherwise.
  548. """
  549. self._check_refname(name)
  550. filename = self.refpath(name)
  551. ensure_dir_exists(os.path.dirname(filename))
  552. f = GitFile(filename, 'wb')
  553. try:
  554. if old_ref is not None:
  555. orig_ref = self.read_loose_ref(name)
  556. if orig_ref is None:
  557. orig_ref = self.get_packed_refs().get(name, ZERO_SHA)
  558. if orig_ref != old_ref:
  559. return False
  560. # may only be packed
  561. try:
  562. os.remove(filename)
  563. except OSError as e:
  564. if e.errno != errno.ENOENT:
  565. raise
  566. self._remove_packed_ref(name)
  567. finally:
  568. # never write, we just wanted the lock
  569. f.abort()
  570. return True
  571. def _split_ref_line(line):
  572. """Split a single ref line into a tuple of SHA1 and name."""
  573. fields = line.rstrip(b'\n').split(b' ')
  574. if len(fields) != 2:
  575. raise PackedRefsException("invalid ref line %r" % line)
  576. sha, name = fields
  577. if not valid_hexsha(sha):
  578. raise PackedRefsException("Invalid hex sha %r" % sha)
  579. if not check_ref_format(name):
  580. raise PackedRefsException("invalid ref name %r" % name)
  581. return (sha, name)
  582. def read_packed_refs(f):
  583. """Read a packed refs file.
  584. :param f: file-like object to read from
  585. :return: Iterator over tuples with SHA1s and ref names.
  586. """
  587. for l in f:
  588. if l.startswith(b'#'):
  589. # Comment
  590. continue
  591. if l.startswith(b'^'):
  592. raise PackedRefsException(
  593. "found peeled ref in packed-refs without peeled")
  594. yield _split_ref_line(l)
  595. def read_packed_refs_with_peeled(f):
  596. """Read a packed refs file including peeled refs.
  597. Assumes the "# pack-refs with: peeled" line was already read. Yields tuples
  598. with ref names, SHA1s, and peeled SHA1s (or None).
  599. :param f: file-like object to read from, seek'ed to the second line
  600. """
  601. last = None
  602. for l in f:
  603. if l[0] == b'#':
  604. continue
  605. l = l.rstrip(b'\r\n')
  606. if l.startswith(b'^'):
  607. if not last:
  608. raise PackedRefsException("unexpected peeled ref line")
  609. if not valid_hexsha(l[1:]):
  610. raise PackedRefsException("Invalid hex sha %r" % l[1:])
  611. sha, name = _split_ref_line(last)
  612. last = None
  613. yield (sha, name, l[1:])
  614. else:
  615. if last:
  616. sha, name = _split_ref_line(last)
  617. yield (sha, name, None)
  618. last = l
  619. if last:
  620. sha, name = _split_ref_line(last)
  621. yield (sha, name, None)
  622. def write_packed_refs(f, packed_refs, peeled_refs=None):
  623. """Write a packed refs file.
  624. :param f: empty file-like object to write to
  625. :param packed_refs: dict of refname to sha of packed refs to write
  626. :param peeled_refs: dict of refname to peeled value of sha
  627. """
  628. if peeled_refs is None:
  629. peeled_refs = {}
  630. else:
  631. f.write(b'# pack-refs with: peeled\n')
  632. for refname in sorted(packed_refs.keys()):
  633. f.write(git_line(packed_refs[refname], refname))
  634. if refname in peeled_refs:
  635. f.write(b'^' + peeled_refs[refname] + b'\n')
  636. def read_info_refs(f):
  637. ret = {}
  638. for l in f.readlines():
  639. (sha, name) = l.rstrip(b"\r\n").split(b"\t", 1)
  640. ret[name] = sha
  641. return ret
  642. def write_info_refs(refs, store):
  643. """Generate info refs."""
  644. for name, sha in sorted(refs.items()):
  645. # get_refs() includes HEAD as a special case, but we don't want to
  646. # advertise it
  647. if name == b'HEAD':
  648. continue
  649. try:
  650. o = store[sha]
  651. except KeyError:
  652. continue
  653. peeled = store.peel_sha(sha)
  654. yield o.id + b'\t' + name + b'\n'
  655. if o.id != peeled.id:
  656. yield peeled.id + b'\t' + name + b'^{}\n'
  657. is_local_branch = lambda x: x.startswith(b'refs/heads/')