refs.py 25 KB

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