refs.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  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 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. LOCAL_TAG_PREFIX = b'refs/tags/'
  41. BAD_REF_CHARS = set(b'\177 ~^:?*[')
  42. ANNOTATED_TAG_SUFFIX = b'^{}'
  43. def parse_symref_value(contents):
  44. """Parse a symref value.
  45. Args:
  46. contents: Contents to parse
  47. Returns: Destination
  48. """
  49. if contents.startswith(SYMREF):
  50. return contents[len(SYMREF):].rstrip(b'\r\n')
  51. raise ValueError(contents)
  52. def check_ref_format(refname):
  53. """Check if a refname is correctly formatted.
  54. Implements all the same rules as git-check-ref-format[1].
  55. [1]
  56. http://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
  57. Args:
  58. refname: The refname to check
  59. Returns: True if refname is valid, False otherwise
  60. """
  61. # These could be combined into one big expression, but are listed
  62. # separately to parallel [1].
  63. if b'/.' in refname or refname.startswith(b'.'):
  64. return False
  65. if b'/' not in refname:
  66. return False
  67. if b'..' in refname:
  68. return False
  69. for i, c in enumerate(refname):
  70. if ord(refname[i:i+1]) < 0o40 or c in BAD_REF_CHARS:
  71. return False
  72. if refname[-1] in b'/.':
  73. return False
  74. if refname.endswith(b'.lock'):
  75. return False
  76. if b'@{' in refname:
  77. return False
  78. if b'\\' in refname:
  79. return False
  80. return True
  81. class RefsContainer(object):
  82. """A container for refs."""
  83. def __init__(self, logger=None):
  84. self._logger = logger
  85. def _log(self, ref, old_sha, new_sha, committer=None, timestamp=None,
  86. timezone=None, message=None):
  87. if self._logger is None:
  88. return
  89. if message is None:
  90. return
  91. self._logger(ref, old_sha, new_sha, committer, timestamp,
  92. timezone, message)
  93. def set_symbolic_ref(self, name, other, committer=None, timestamp=None,
  94. timezone=None, message=None):
  95. """Make a ref point at another ref.
  96. Args:
  97. name: Name of the ref to set
  98. other: Name of the ref to point at
  99. message: Optional message
  100. """
  101. raise NotImplementedError(self.set_symbolic_ref)
  102. def get_packed_refs(self):
  103. """Get contents of the packed-refs file.
  104. Returns: Dictionary mapping ref names to SHA1s
  105. Note: Will return an empty dictionary when no packed-refs file is
  106. present.
  107. """
  108. raise NotImplementedError(self.get_packed_refs)
  109. def get_peeled(self, name):
  110. """Return the cached peeled value of a ref, if available.
  111. Args:
  112. name: Name of the ref to peel
  113. Returns: The peeled value of the ref. If the ref is known not point to
  114. a tag, this will be the SHA the ref refers to. If the ref may point
  115. to a tag, but no cached information is available, None is returned.
  116. """
  117. return None
  118. def import_refs(self, base, other, committer=None, timestamp=None,
  119. timezone=None, message=None, prune=False):
  120. if prune:
  121. to_delete = set(self.subkeys(base))
  122. else:
  123. to_delete = set()
  124. for name, value in other.items():
  125. self.set_if_equals(b'/'.join((base, name)), None, value,
  126. message=message)
  127. if to_delete:
  128. try:
  129. to_delete.remove(name)
  130. except KeyError:
  131. pass
  132. for ref in to_delete:
  133. self.remove_if_equals(b'/'.join((base, ref)), None)
  134. def allkeys(self):
  135. """All refs present in this container."""
  136. raise NotImplementedError(self.allkeys)
  137. def __iter__(self):
  138. return iter(self.allkeys())
  139. def keys(self, base=None):
  140. """Refs present in this container.
  141. Args:
  142. base: An optional base to return refs under.
  143. Returns: An unsorted set of valid refs in this container, including
  144. packed refs.
  145. """
  146. if base is not None:
  147. return self.subkeys(base)
  148. else:
  149. return self.allkeys()
  150. def subkeys(self, base):
  151. """Refs present in this container under a base.
  152. Args:
  153. base: The base to return refs under.
  154. Returns: A set of valid refs in this container under the base; the base
  155. prefix is stripped from the ref names returned.
  156. """
  157. keys = set()
  158. base_len = len(base) + 1
  159. for refname in self.allkeys():
  160. if refname.startswith(base):
  161. keys.add(refname[base_len:])
  162. return keys
  163. def as_dict(self, base=None):
  164. """Return the contents of this container as a dictionary.
  165. """
  166. ret = {}
  167. keys = self.keys(base)
  168. if base is None:
  169. base = b''
  170. else:
  171. base = base.rstrip(b'/')
  172. for key in keys:
  173. try:
  174. ret[key] = self[(base + b'/' + key).strip(b'/')]
  175. except KeyError:
  176. continue # Unable to resolve
  177. return ret
  178. def _check_refname(self, name):
  179. """Ensure a refname is valid and lives in refs or is HEAD.
  180. HEAD is not a valid refname according to git-check-ref-format, but this
  181. class needs to be able to touch HEAD. Also, check_ref_format expects
  182. refnames without the leading 'refs/', but this class requires that
  183. so it cannot touch anything outside the refs dir (or HEAD).
  184. Args:
  185. name: The name of the reference.
  186. Raises:
  187. KeyError: if a refname is not HEAD or is otherwise not valid.
  188. """
  189. if name in (b'HEAD', b'refs/stash'):
  190. return
  191. if not name.startswith(b'refs/') or not check_ref_format(name[5:]):
  192. raise RefFormatError(name)
  193. def read_ref(self, refname):
  194. """Read a reference without following any references.
  195. Args:
  196. refname: The name of the reference
  197. Returns: The contents of the ref file, or None if it does
  198. not exist.
  199. """
  200. contents = self.read_loose_ref(refname)
  201. if not contents:
  202. contents = self.get_packed_refs().get(refname, None)
  203. return contents
  204. def read_loose_ref(self, name):
  205. """Read a loose reference and return its contents.
  206. Args:
  207. name: the refname to read
  208. Returns: The contents of the ref file, or None if it does
  209. not exist.
  210. """
  211. raise NotImplementedError(self.read_loose_ref)
  212. def follow(self, name):
  213. """Follow a reference name.
  214. Returns: a tuple of (refnames, sha), wheres refnames are the names of
  215. references in the chain
  216. """
  217. contents = SYMREF + name
  218. depth = 0
  219. refnames = []
  220. while contents.startswith(SYMREF):
  221. refname = contents[len(SYMREF):]
  222. refnames.append(refname)
  223. contents = self.read_ref(refname)
  224. if not contents:
  225. break
  226. depth += 1
  227. if depth > 5:
  228. raise KeyError(name)
  229. return refnames, contents
  230. def _follow(self, name):
  231. import warnings
  232. warnings.warn(
  233. "RefsContainer._follow is deprecated. Use RefsContainer.follow "
  234. "instead.", DeprecationWarning)
  235. refnames, contents = self.follow(name)
  236. if not refnames:
  237. return (None, contents)
  238. return (refnames[-1], contents)
  239. def __contains__(self, refname):
  240. if self.read_ref(refname):
  241. return True
  242. return False
  243. def __getitem__(self, name):
  244. """Get the SHA1 for a reference name.
  245. This method follows all symbolic references.
  246. """
  247. _, sha = self.follow(name)
  248. if sha is None:
  249. raise KeyError(name)
  250. return sha
  251. def set_if_equals(self, name, old_ref, new_ref, committer=None,
  252. timestamp=None, timezone=None, message=None):
  253. """Set a refname to new_ref only if it currently equals old_ref.
  254. This method follows all symbolic references if applicable for the
  255. subclass, and can be used to perform an atomic compare-and-swap
  256. operation.
  257. Args:
  258. name: The refname to set.
  259. old_ref: The old sha the refname must refer to, or None to set
  260. unconditionally.
  261. new_ref: The new sha the refname will refer to.
  262. message: Message for reflog
  263. Returns: True if the set was successful, False otherwise.
  264. """
  265. raise NotImplementedError(self.set_if_equals)
  266. def add_if_new(self, name, ref):
  267. """Add a new reference only if it does not already exist.
  268. Args:
  269. name: Ref name
  270. ref: Ref value
  271. message: Message for reflog
  272. """
  273. raise NotImplementedError(self.add_if_new)
  274. def __setitem__(self, name, ref):
  275. """Set a reference name to point to the given SHA1.
  276. This method follows all symbolic references if applicable for the
  277. subclass.
  278. Note: This method unconditionally overwrites the contents of a
  279. reference. To update atomically only if the reference has not
  280. changed, use set_if_equals().
  281. Args:
  282. name: The refname to set.
  283. ref: The new sha the refname will refer to.
  284. """
  285. self.set_if_equals(name, None, ref)
  286. def remove_if_equals(self, name, old_ref, committer=None,
  287. timestamp=None, timezone=None, message=None):
  288. """Remove a refname only if it currently equals old_ref.
  289. This method does not follow symbolic references, even if applicable for
  290. the subclass. It can be used to perform an atomic compare-and-delete
  291. operation.
  292. Args:
  293. name: The refname to delete.
  294. old_ref: The old sha the refname must refer to, or None to
  295. delete unconditionally.
  296. message: Message for reflog
  297. Returns: True if the delete was successful, False otherwise.
  298. """
  299. raise NotImplementedError(self.remove_if_equals)
  300. def __delitem__(self, name):
  301. """Remove a refname.
  302. This method does not follow symbolic references, even if applicable for
  303. the subclass.
  304. Note: This method unconditionally deletes the contents of a reference.
  305. To delete atomically only if the reference has not changed, use
  306. remove_if_equals().
  307. Args:
  308. name: The refname to delete.
  309. """
  310. self.remove_if_equals(name, None)
  311. def get_symrefs(self):
  312. """Get a dict with all symrefs in this container.
  313. Returns: Dictionary mapping source ref to target ref
  314. """
  315. ret = {}
  316. for src in self.allkeys():
  317. try:
  318. dst = parse_symref_value(self.read_ref(src))
  319. except ValueError:
  320. pass
  321. else:
  322. ret[src] = dst
  323. return ret
  324. class DictRefsContainer(RefsContainer):
  325. """RefsContainer backed by a simple dict.
  326. This container does not support symbolic or packed references and is not
  327. threadsafe.
  328. """
  329. def __init__(self, refs, logger=None):
  330. super(DictRefsContainer, self).__init__(logger=logger)
  331. self._refs = refs
  332. self._peeled = {}
  333. def allkeys(self):
  334. return self._refs.keys()
  335. def read_loose_ref(self, name):
  336. return self._refs.get(name, None)
  337. def get_packed_refs(self):
  338. return {}
  339. def set_symbolic_ref(self, name, other, committer=None,
  340. timestamp=None, timezone=None, message=None):
  341. old = self.follow(name)[-1]
  342. self._refs[name] = SYMREF + other
  343. self._log(name, old, old, committer=committer, timestamp=timestamp,
  344. timezone=timezone, message=message)
  345. def set_if_equals(self, name, old_ref, new_ref, committer=None,
  346. timestamp=None, timezone=None, message=None):
  347. if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
  348. return False
  349. realnames, _ = self.follow(name)
  350. for realname in realnames:
  351. self._check_refname(realname)
  352. old = self._refs.get(realname)
  353. self._refs[realname] = new_ref
  354. self._log(realname, old, new_ref, committer=committer,
  355. timestamp=timestamp, timezone=timezone, message=message)
  356. return True
  357. def add_if_new(self, name, ref, committer=None, timestamp=None,
  358. timezone=None, message=None):
  359. if name in self._refs:
  360. return False
  361. self._refs[name] = ref
  362. self._log(name, None, ref, committer=committer, timestamp=timestamp,
  363. timezone=timezone, message=message)
  364. return True
  365. def remove_if_equals(self, name, old_ref, committer=None, timestamp=None,
  366. timezone=None, message=None):
  367. if old_ref is not None and self._refs.get(name, ZERO_SHA) != old_ref:
  368. return False
  369. try:
  370. old = self._refs.pop(name)
  371. except KeyError:
  372. pass
  373. else:
  374. self._log(name, old, None, committer=committer,
  375. timestamp=timestamp, timezone=timezone, message=message)
  376. return True
  377. def get_peeled(self, name):
  378. return self._peeled.get(name)
  379. def _update(self, refs):
  380. """Update multiple refs; intended only for testing."""
  381. # TODO(dborowitz): replace this with a public function that uses
  382. # set_if_equal.
  383. self._refs.update(refs)
  384. def _update_peeled(self, peeled):
  385. """Update cached peeled refs; intended only for testing."""
  386. self._peeled.update(peeled)
  387. class InfoRefsContainer(RefsContainer):
  388. """Refs container that reads refs from a info/refs file."""
  389. def __init__(self, f):
  390. self._refs = {}
  391. self._peeled = {}
  392. for line in f.readlines():
  393. sha, name = line.rstrip(b'\n').split(b'\t')
  394. if name.endswith(ANNOTATED_TAG_SUFFIX):
  395. name = name[:-3]
  396. if not check_ref_format(name):
  397. raise ValueError("invalid ref name %r" % name)
  398. self._peeled[name] = sha
  399. else:
  400. if not check_ref_format(name):
  401. raise ValueError("invalid ref name %r" % name)
  402. self._refs[name] = sha
  403. def allkeys(self):
  404. return self._refs.keys()
  405. def read_loose_ref(self, name):
  406. return self._refs.get(name, None)
  407. def get_packed_refs(self):
  408. return {}
  409. def get_peeled(self, name):
  410. try:
  411. return self._peeled[name]
  412. except KeyError:
  413. return self._refs[name]
  414. class DiskRefsContainer(RefsContainer):
  415. """Refs container that reads refs from disk."""
  416. def __init__(self, path, worktree_path=None, logger=None):
  417. super(DiskRefsContainer, self).__init__(logger=logger)
  418. if getattr(path, 'encode', None) is not None:
  419. path = path.encode(sys.getfilesystemencoding())
  420. self.path = path
  421. if worktree_path is None:
  422. worktree_path = path
  423. if getattr(worktree_path, 'encode', None) is not None:
  424. worktree_path = worktree_path.encode(sys.getfilesystemencoding())
  425. self.worktree_path = worktree_path
  426. self._packed_refs = None
  427. self._peeled_refs = None
  428. def __repr__(self):
  429. return "%s(%r)" % (self.__class__.__name__, self.path)
  430. def subkeys(self, base):
  431. subkeys = set()
  432. path = self.refpath(base)
  433. for root, unused_dirs, files in os.walk(path):
  434. dir = root[len(path):]
  435. if os.path.sep != '/':
  436. dir = dir.replace(os.path.sep.encode(
  437. sys.getfilesystemencoding()), b"/")
  438. dir = dir.strip(b'/')
  439. for filename in files:
  440. refname = b"/".join(([dir] if dir else []) + [filename])
  441. # check_ref_format requires at least one /, so we prepend the
  442. # base before calling it.
  443. if check_ref_format(base + b'/' + refname):
  444. subkeys.add(refname)
  445. for key in self.get_packed_refs():
  446. if key.startswith(base):
  447. subkeys.add(key[len(base):].strip(b'/'))
  448. return subkeys
  449. def allkeys(self):
  450. allkeys = set()
  451. if os.path.exists(self.refpath(b'HEAD')):
  452. allkeys.add(b'HEAD')
  453. path = self.refpath(b'')
  454. refspath = self.refpath(b'refs')
  455. for root, unused_dirs, files in os.walk(refspath):
  456. dir = root[len(path):]
  457. if os.path.sep != '/':
  458. dir = dir.replace(
  459. os.path.sep.encode(sys.getfilesystemencoding()), b"/")
  460. for filename in files:
  461. refname = b"/".join([dir, filename])
  462. if check_ref_format(refname):
  463. allkeys.add(refname)
  464. allkeys.update(self.get_packed_refs())
  465. return allkeys
  466. def refpath(self, name):
  467. """Return the disk path of a ref.
  468. """
  469. if os.path.sep != "/":
  470. name = name.replace(
  471. b"/",
  472. os.path.sep.encode(sys.getfilesystemencoding()))
  473. # TODO: as the 'HEAD' reference is working tree specific, it
  474. # should actually not be a part of RefsContainer
  475. if name == b'HEAD':
  476. return os.path.join(self.worktree_path, name)
  477. else:
  478. return os.path.join(self.path, name)
  479. def get_packed_refs(self):
  480. """Get contents of the packed-refs file.
  481. Returns: Dictionary mapping ref names to SHA1s
  482. Note: Will return an empty dictionary when no packed-refs file is
  483. present.
  484. """
  485. # TODO: invalidate the cache on repacking
  486. if self._packed_refs is None:
  487. # set both to empty because we want _peeled_refs to be
  488. # None if and only if _packed_refs is also None.
  489. self._packed_refs = {}
  490. self._peeled_refs = {}
  491. path = os.path.join(self.path, b'packed-refs')
  492. try:
  493. f = GitFile(path, 'rb')
  494. except IOError as e:
  495. if e.errno == errno.ENOENT:
  496. return {}
  497. raise
  498. with f:
  499. first_line = next(iter(f)).rstrip()
  500. if (first_line.startswith(b'# pack-refs') and b' peeled' in
  501. first_line):
  502. for sha, name, peeled in read_packed_refs_with_peeled(f):
  503. self._packed_refs[name] = sha
  504. if peeled:
  505. self._peeled_refs[name] = peeled
  506. else:
  507. f.seek(0)
  508. for sha, name in read_packed_refs(f):
  509. self._packed_refs[name] = sha
  510. return self._packed_refs
  511. def get_peeled(self, name):
  512. """Return the cached peeled value of a ref, if available.
  513. Args:
  514. name: Name of the ref to peel
  515. Returns: The peeled value of the ref. If the ref is known not point to
  516. a tag, this will be the SHA the ref refers to. If the ref may point
  517. to a tag, but no cached information is available, None is returned.
  518. """
  519. self.get_packed_refs()
  520. if self._peeled_refs is None or name not in self._packed_refs:
  521. # No cache: no peeled refs were read, or this ref is loose
  522. return None
  523. if name in self._peeled_refs:
  524. return self._peeled_refs[name]
  525. else:
  526. # Known not peelable
  527. return self[name]
  528. def read_loose_ref(self, name):
  529. """Read a reference file and return its contents.
  530. If the reference file a symbolic reference, only read the first line of
  531. the file. Otherwise, only read the first 40 bytes.
  532. Args:
  533. name: the refname to read, relative to refpath
  534. Returns: The contents of the ref file, or None if the file does not
  535. exist.
  536. Raises:
  537. IOError: if any other error occurs
  538. """
  539. filename = self.refpath(name)
  540. try:
  541. with GitFile(filename, 'rb') as f:
  542. header = f.read(len(SYMREF))
  543. if header == SYMREF:
  544. # Read only the first line
  545. return header + next(iter(f)).rstrip(b'\r\n')
  546. else:
  547. # Read only the first 40 bytes
  548. return header + f.read(40 - len(SYMREF))
  549. except IOError as e:
  550. if e.errno in (errno.ENOENT, errno.EISDIR, errno.ENOTDIR):
  551. return None
  552. raise
  553. def _remove_packed_ref(self, name):
  554. if self._packed_refs is None:
  555. return
  556. filename = os.path.join(self.path, b'packed-refs')
  557. # reread cached refs from disk, while holding the lock
  558. f = GitFile(filename, 'wb')
  559. try:
  560. self._packed_refs = None
  561. self.get_packed_refs()
  562. if name not in self._packed_refs:
  563. return
  564. del self._packed_refs[name]
  565. if name in self._peeled_refs:
  566. del self._peeled_refs[name]
  567. write_packed_refs(f, self._packed_refs, self._peeled_refs)
  568. f.close()
  569. finally:
  570. f.abort()
  571. def set_symbolic_ref(self, name, other, committer=None, timestamp=None,
  572. timezone=None, message=None):
  573. """Make a ref point at another ref.
  574. Args:
  575. name: Name of the ref to set
  576. other: Name of the ref to point at
  577. message: Optional message to describe the change
  578. """
  579. self._check_refname(name)
  580. self._check_refname(other)
  581. filename = self.refpath(name)
  582. f = GitFile(filename, 'wb')
  583. try:
  584. f.write(SYMREF + other + b'\n')
  585. sha = self.follow(name)[-1]
  586. self._log(name, sha, sha, committer=committer,
  587. timestamp=timestamp, timezone=timezone,
  588. message=message)
  589. except BaseException:
  590. f.abort()
  591. raise
  592. else:
  593. f.close()
  594. def set_if_equals(self, name, old_ref, new_ref, committer=None,
  595. timestamp=None, timezone=None, message=None):
  596. """Set a refname to new_ref only if it currently equals old_ref.
  597. This method follows all symbolic references, and can be used to perform
  598. an atomic compare-and-swap operation.
  599. Args:
  600. name: The refname to set.
  601. old_ref: The old sha the refname must refer to, or None to set
  602. unconditionally.
  603. new_ref: The new sha the refname will refer to.
  604. message: Set message for reflog
  605. Returns: True if the set was successful, False otherwise.
  606. """
  607. self._check_refname(name)
  608. try:
  609. realnames, _ = self.follow(name)
  610. realname = realnames[-1]
  611. except (KeyError, IndexError):
  612. realname = name
  613. filename = self.refpath(realname)
  614. # make sure none of the ancestor folders is in packed refs
  615. probe_ref = os.path.dirname(realname)
  616. packed_refs = self.get_packed_refs()
  617. while probe_ref:
  618. if packed_refs.get(probe_ref, None) is not None:
  619. raise OSError(errno.ENOTDIR,
  620. 'Not a directory: {}'.format(filename))
  621. probe_ref = os.path.dirname(probe_ref)
  622. ensure_dir_exists(os.path.dirname(filename))
  623. with GitFile(filename, 'wb') as f:
  624. if old_ref is not None:
  625. try:
  626. # read again while holding the lock
  627. orig_ref = self.read_loose_ref(realname)
  628. if orig_ref is None:
  629. orig_ref = self.get_packed_refs().get(
  630. realname, ZERO_SHA)
  631. if orig_ref != old_ref:
  632. f.abort()
  633. return False
  634. except (OSError, IOError):
  635. f.abort()
  636. raise
  637. try:
  638. f.write(new_ref + b'\n')
  639. except (OSError, IOError):
  640. f.abort()
  641. raise
  642. self._log(realname, old_ref, new_ref, committer=committer,
  643. timestamp=timestamp, timezone=timezone, message=message)
  644. return True
  645. def add_if_new(self, name, ref, committer=None, timestamp=None,
  646. timezone=None, message=None):
  647. """Add a new reference only if it does not already exist.
  648. This method follows symrefs, and only ensures that the last ref in the
  649. chain does not exist.
  650. Args:
  651. name: The refname to set.
  652. ref: The new sha the refname will refer to.
  653. message: Optional message for reflog
  654. Returns: True if the add was successful, False otherwise.
  655. """
  656. try:
  657. realnames, contents = self.follow(name)
  658. if contents is not None:
  659. return False
  660. realname = realnames[-1]
  661. except (KeyError, IndexError):
  662. realname = name
  663. self._check_refname(realname)
  664. filename = self.refpath(realname)
  665. ensure_dir_exists(os.path.dirname(filename))
  666. with GitFile(filename, 'wb') as f:
  667. if os.path.exists(filename) or name in self.get_packed_refs():
  668. f.abort()
  669. return False
  670. try:
  671. f.write(ref + b'\n')
  672. except (OSError, IOError):
  673. f.abort()
  674. raise
  675. else:
  676. self._log(name, None, ref, committer=committer,
  677. timestamp=timestamp, timezone=timezone,
  678. message=message)
  679. return True
  680. def remove_if_equals(self, name, old_ref, committer=None, timestamp=None,
  681. timezone=None, message=None):
  682. """Remove a refname only if it currently equals old_ref.
  683. This method does not follow symbolic references. It can be used to
  684. perform an atomic compare-and-delete operation.
  685. Args:
  686. name: The refname to delete.
  687. old_ref: The old sha the refname must refer to, or None to
  688. delete unconditionally.
  689. message: Optional message
  690. Returns: True if the delete was successful, False otherwise.
  691. """
  692. self._check_refname(name)
  693. filename = self.refpath(name)
  694. ensure_dir_exists(os.path.dirname(filename))
  695. f = GitFile(filename, 'wb')
  696. try:
  697. if old_ref is not None:
  698. orig_ref = self.read_loose_ref(name)
  699. if orig_ref is None:
  700. orig_ref = self.get_packed_refs().get(name, ZERO_SHA)
  701. if orig_ref != old_ref:
  702. return False
  703. # remove the reference file itself
  704. try:
  705. os.remove(filename)
  706. except OSError as e:
  707. if e.errno != errno.ENOENT: # may only be packed
  708. raise
  709. self._remove_packed_ref(name)
  710. self._log(name, old_ref, None, committer=committer,
  711. timestamp=timestamp, timezone=timezone, message=message)
  712. finally:
  713. # never write, we just wanted the lock
  714. f.abort()
  715. # outside of the lock, clean-up any parent directory that might now
  716. # be empty. this ensures that re-creating a reference of the same
  717. # name of what was previously a directory works as expected
  718. parent = name
  719. while True:
  720. try:
  721. parent, _ = parent.rsplit(b'/', 1)
  722. except ValueError:
  723. break
  724. parent_filename = self.refpath(parent)
  725. try:
  726. os.rmdir(parent_filename)
  727. except OSError:
  728. # this can be caused by the parent directory being
  729. # removed by another process, being not empty, etc.
  730. # in any case, this is non fatal because we already
  731. # removed the reference, just ignore it
  732. break
  733. return True
  734. def _split_ref_line(line):
  735. """Split a single ref line into a tuple of SHA1 and name."""
  736. fields = line.rstrip(b'\n\r').split(b' ')
  737. if len(fields) != 2:
  738. raise PackedRefsException("invalid ref line %r" % line)
  739. sha, name = fields
  740. if not valid_hexsha(sha):
  741. raise PackedRefsException("Invalid hex sha %r" % sha)
  742. if not check_ref_format(name):
  743. raise PackedRefsException("invalid ref name %r" % name)
  744. return (sha, name)
  745. def read_packed_refs(f):
  746. """Read a packed refs file.
  747. Args:
  748. f: file-like object to read from
  749. Returns: Iterator over tuples with SHA1s and ref names.
  750. """
  751. for line in f:
  752. if line.startswith(b'#'):
  753. # Comment
  754. continue
  755. if line.startswith(b'^'):
  756. raise PackedRefsException(
  757. "found peeled ref in packed-refs without peeled")
  758. yield _split_ref_line(line)
  759. def read_packed_refs_with_peeled(f):
  760. """Read a packed refs file including peeled refs.
  761. Assumes the "# pack-refs with: peeled" line was already read. Yields tuples
  762. with ref names, SHA1s, and peeled SHA1s (or None).
  763. Args:
  764. f: file-like object to read from, seek'ed to the second line
  765. """
  766. last = None
  767. for line in f:
  768. if line[0] == b'#':
  769. continue
  770. line = line.rstrip(b'\r\n')
  771. if line.startswith(b'^'):
  772. if not last:
  773. raise PackedRefsException("unexpected peeled ref line")
  774. if not valid_hexsha(line[1:]):
  775. raise PackedRefsException("Invalid hex sha %r" % line[1:])
  776. sha, name = _split_ref_line(last)
  777. last = None
  778. yield (sha, name, line[1:])
  779. else:
  780. if last:
  781. sha, name = _split_ref_line(last)
  782. yield (sha, name, None)
  783. last = line
  784. if last:
  785. sha, name = _split_ref_line(last)
  786. yield (sha, name, None)
  787. def write_packed_refs(f, packed_refs, peeled_refs=None):
  788. """Write a packed refs file.
  789. Args:
  790. f: empty file-like object to write to
  791. packed_refs: dict of refname to sha of packed refs to write
  792. peeled_refs: dict of refname to peeled value of sha
  793. """
  794. if peeled_refs is None:
  795. peeled_refs = {}
  796. else:
  797. f.write(b'# pack-refs with: peeled\n')
  798. for refname in sorted(packed_refs.keys()):
  799. f.write(git_line(packed_refs[refname], refname))
  800. if refname in peeled_refs:
  801. f.write(b'^' + peeled_refs[refname] + b'\n')
  802. def read_info_refs(f):
  803. ret = {}
  804. for line in f.readlines():
  805. (sha, name) = line.rstrip(b"\r\n").split(b"\t", 1)
  806. ret[name] = sha
  807. return ret
  808. def write_info_refs(refs, store):
  809. """Generate info refs."""
  810. for name, sha in sorted(refs.items()):
  811. # get_refs() includes HEAD as a special case, but we don't want to
  812. # advertise it
  813. if name == b'HEAD':
  814. continue
  815. try:
  816. o = store[sha]
  817. except KeyError:
  818. continue
  819. peeled = store.peel_sha(sha)
  820. yield o.id + b'\t' + name + b'\n'
  821. if o.id != peeled.id:
  822. yield peeled.id + b'\t' + name + ANNOTATED_TAG_SUFFIX + b'\n'
  823. def is_local_branch(x):
  824. return x.startswith(LOCAL_BRANCH_PREFIX)
  825. def strip_peeled_refs(refs):
  826. """Remove all peeled refs"""
  827. return {ref: sha for (ref, sha) in refs.items()
  828. if not ref.endswith(ANNOTATED_TAG_SUFFIX)}