repo.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. # repo.py -- For dealing wih git repositories.
  2. # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
  3. # Copyright (C) 2008 Jelmer Vernooij <jelmer@samba.org>
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU General Public License
  7. # as published by the Free Software Foundation; version 2
  8. # of 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. import os
  20. from commit import Commit
  21. from errors import MissingCommitError
  22. from objects import (ShaFile,
  23. Commit,
  24. Tree,
  25. Blob,
  26. )
  27. from pack import load_packs
  28. OBJECTDIR = 'objects'
  29. PACKDIR = 'pack'
  30. SYMREF = 'ref: '
  31. class Tag(object):
  32. def __init__(self, name, ref):
  33. self.name = name
  34. self.ref = ref
  35. class Repo(object):
  36. ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
  37. def __init__(self, root):
  38. controldir = os.path.join(root, ".git")
  39. if os.path.exists(os.path.join(controldir, "objects")):
  40. self.bare = False
  41. self._basedir = controldir
  42. else:
  43. self.bare = True
  44. self._basedir = root
  45. self.path = controldir
  46. self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
  47. self._packs = None
  48. def basedir(self):
  49. return self._basedir
  50. def object_dir(self):
  51. return os.path.join(self.basedir(), OBJECTDIR)
  52. def pack_dir(self):
  53. return os.path.join(self.object_dir(), PACKDIR)
  54. def _get_packs(self):
  55. if self._packs is None:
  56. self._packs = list(load_packs(self.pack_dir()))
  57. return self._packs
  58. def _get_ref(self, file):
  59. f = open(file, 'rb')
  60. try:
  61. contents = f.read()
  62. if contents.startswith(SYMREF):
  63. ref = contents[len(SYMREF):]
  64. if ref[-1] == '\n':
  65. ref = ref[:-1]
  66. return self.ref(ref)
  67. assert len(contents) == 41, 'Invalid ref'
  68. return contents[:-1]
  69. finally:
  70. f.close()
  71. def ref(self, name):
  72. for dir in self.ref_locs:
  73. file = os.path.join(self.basedir(), dir, name)
  74. if os.path.exists(file):
  75. return self._get_ref(file)
  76. def get_tags(self):
  77. ret = {}
  78. for root, dirs, files in os.walk(os.path.join(self.basedir(), 'refs', 'tags')):
  79. for name in files:
  80. ret[name] = self._get_ref(os.path.join(root, name))
  81. return ret
  82. def heads(self):
  83. ret = {}
  84. for root, dirs, files in os.walk(os.path.join(self.basedir(), 'refs', 'heads')):
  85. for name in files:
  86. ret[name] = self._get_ref(os.path.join(root, name))
  87. return ret
  88. def head(self):
  89. return self.ref('HEAD')
  90. def _get_object(self, sha, cls):
  91. assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
  92. dir = sha[:2]
  93. file = sha[2:]
  94. # Check from object dir
  95. path = os.path.join(self.object_dir(), dir, file)
  96. if os.path.exists(path):
  97. return cls.from_file(path)
  98. # Check from packs
  99. for pack in self._get_packs():
  100. if sha in pack:
  101. return pack[sha]
  102. # Should this raise instead?
  103. return None
  104. def get_object(self, sha):
  105. return self._get_object(sha, ShaFile)
  106. def commit(self, sha):
  107. return self._get_object(sha, Commit)
  108. def get_tree(self, sha):
  109. return self._get_object(sha, Tree)
  110. def get_blob(self, sha):
  111. return self._get_object(sha, Blob)
  112. def revision_history(self, head):
  113. """Returns a list of the commits reachable from head.
  114. Returns a list of commit objects. the first of which will be the commit
  115. of head, then following theat will be the parents.
  116. Raises NotCommitError if any no commits are referenced, including if the
  117. head parameter isn't the sha of a commit.
  118. XXX: work out how to handle merges.
  119. """
  120. # We build the list backwards, as parents are more likely to be older
  121. # than children
  122. pending_commits = [head]
  123. history = []
  124. while pending_commits != []:
  125. head = pending_commits.pop(0)
  126. commit = self.commit(head)
  127. if commit is None:
  128. raise MissingCommitError(head)
  129. if commit in history:
  130. continue
  131. i = 0
  132. for known_commit in history:
  133. if known_commit.commit_time > commit.commit_time:
  134. break
  135. i += 1
  136. history.insert(i, commit)
  137. parents = commit.parents
  138. pending_commits += parents
  139. history.reverse()
  140. return history
  141. @classmethod
  142. def init_bare(cls, path, mkdir=True):
  143. for d in [["objects"],
  144. ["objects", "info"],
  145. ["objects", "pack"],
  146. ["branches"],
  147. ["refs"],
  148. ["refs", "tags"],
  149. ["refs", "heads"],
  150. ["hooks"],
  151. ["info"]]:
  152. os.mkdir(os.path.join(path, *d))
  153. open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
  154. open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
  155. open(os.path.join(path, 'info', 'excludes'), 'w').write("")
  156. create = init_bare