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>
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
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.
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,
22 from commit import Commit
23 from errors import MissingCommitError, NotBlobError, NotTreeError, NotCommitError
24 from objects import (ShaFile,
29 from pack import load_packs, iter_sha1, PackData, write_pack_index_v2
39 def __init__(self, name, ref):
46 ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
48 def __init__(self, root):
49 controldir = os.path.join(root, ".git")
50 if os.path.exists(os.path.join(controldir, "objects")):
52 self._controldir = controldir
55 self._controldir = root
57 self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
58 self._object_store = None
61 return self._controldir
63 def fetch_objects(self, determine_wants, graph_walker, progress):
64 wants = determine_wants(self.heads())
66 ref = graph_walker.next()
68 commits_to_send.append(ref)
69 if ref in self.object_store:
71 ref = graph_walker.next()
73 for sha in commits_to_send:
80 def parse_tree(tree, sha_done):
81 for mode, name, x in tree.entries():
86 parse_tree(t, sha_done)
91 if treesha not in sha_done:
92 t = self.tree(treesha)
94 parse_tree(t, sha_done)
96 progress("counting objects: %d\r" % len(sha_done))
99 yield self.get_object(sha)
101 def object_dir(self):
102 return os.path.join(self.controldir(), OBJECTDIR)
105 def object_store(self):
106 if self._object_store is None:
107 self._object_store = ObjectStore(self.object_dir())
108 return self._object_store
111 return os.path.join(self.object_dir(), PACKDIR)
113 def _get_ref(self, file):
117 if contents.startswith(SYMREF):
118 ref = contents[len(SYMREF):]
122 assert len(contents) == 41, 'Invalid ref in %s' % file
128 for dir in self.ref_locs:
129 file = os.path.join(self.controldir(), dir, name)
130 if os.path.exists(file):
131 return self._get_ref(file)
134 ret = {"HEAD": self.head()}
135 for dir in ["refs/heads", "refs/tags"]:
136 for name in os.listdir(os.path.join(self.controldir(), dir)):
137 path = os.path.join(self.controldir(), dir, name)
138 if os.path.isfile(path):
139 ret["/".join([dir, name])] = self._get_ref(path)
142 def set_ref(self, name, value):
143 file = os.path.join(self.controldir(), name)
144 open(file, 'w').write(value+"\n")
146 def remove_ref(self, name):
147 file = os.path.join(self.controldir(), name)
148 if os.path.exists(file):
154 for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
156 ret[name] = self._get_ref(os.path.join(root, name))
161 for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
163 ret[name] = self._get_ref(os.path.join(root, name))
167 return self.ref('HEAD')
169 def _get_object(self, sha, cls):
170 ret = self.get_object(sha)
171 if ret._type != cls._type:
173 raise NotCommitError(ret)
175 raise NotBlobError(ret)
177 raise NotTreeError(ret)
179 raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
182 def get_object(self, sha):
183 return self.object_store[sha]
185 def get_parents(self, sha):
186 return self.commit(sha).parents
188 def commit(self, sha):
189 return self._get_object(sha, Commit)
192 return self._get_object(sha, Tree)
194 def get_blob(self, sha):
195 return self._get_object(sha, Blob)
197 def revision_history(self, head):
198 """Returns a list of the commits reachable from head.
200 Returns a list of commit objects. the first of which will be the commit
201 of head, then following theat will be the parents.
203 Raises NotCommitError if any no commits are referenced, including if the
204 head parameter isn't the sha of a commit.
206 XXX: work out how to handle merges.
208 # We build the list backwards, as parents are more likely to be older
210 pending_commits = [head]
212 while pending_commits != []:
213 head = pending_commits.pop(0)
215 commit = self.commit(head)
217 raise MissingCommitError(head)
218 if commit in history:
221 for known_commit in history:
222 if known_commit.commit_time > commit.commit_time:
225 history.insert(i, commit)
226 parents = commit.parents
227 pending_commits += parents
232 return "<Repo at %r>" % self.path
235 def init_bare(cls, path, mkdir=True):
236 for d in [["objects"],
245 os.mkdir(os.path.join(path, *d))
246 open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
247 open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
248 open(os.path.join(path, 'info', 'excludes'), 'w').write("")
253 class ObjectStore(object):
255 def __init__(self, path):
260 return os.path.join(self.path, PACKDIR)
262 def __contains__(self, sha):
263 # TODO: This can be more efficient
272 """List with pack objects."""
273 if self._packs is None:
274 self._packs = list(load_packs(self.pack_dir()))
277 def _get_shafile(self, sha):
280 # Check from object dir
281 path = os.path.join(self.path, dir, file)
282 if os.path.exists(path):
283 return ShaFile.from_file(path)
286 def get_raw(self, sha):
287 """Obtain the raw text for an object.
289 :param sha: Sha for the object.
290 :return: tuple with object type and object contents.
292 for pack in self.packs:
294 return pack.get_raw(sha, self.get_raw)
295 # FIXME: Are pack deltas ever against on-disk shafiles ?
296 ret = self._get_shafile(sha)
298 return ret.as_raw_string()
301 def __getitem__(self, sha):
302 assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
303 ret = self._get_shafile(sha)
307 type, uncomp = self.get_raw(sha)
308 return ShaFile.from_raw_string(type, uncomp)
310 def move_in_pack(self, path):
311 """Move a specific file containing a pack into the pack directory.
313 :note: The file should be on the same file system as the
316 :param path: Path to the pack file.
319 entries = p.sorted_entries(self.get_raw)
320 basename = os.path.join(self.pack_dir(),
321 "pack-%s" % iter_sha1(entry[0] for entry in entries))
322 write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
323 os.rename(path, basename + ".pack")
326 """Add a new pack to this object store.
328 :return: Fileobject to write to and a commit function to
329 call when the pack is finished.
331 fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
332 f = os.fdopen(fd, 'w')
334 if os.path.getsize(path) > 0:
335 self.move_in_pack(path)