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._basedir = controldir
56 self.path = controldir
57 self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
58 self._object_store = None
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.basedir(), 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'
128 for dir in self.ref_locs:
129 file = os.path.join(self.basedir(), dir, name)
130 if os.path.exists(file):
131 return self._get_ref(file)
133 def set_ref(self, name, value):
134 file = os.path.join(self.basedir(), name)
135 open(file, 'w').write(value+"\n")
137 def remove_ref(self, name):
138 file = os.path.join(self.basedir(), name)
139 if os.path.exists(file):
145 for root, dirs, files in os.walk(os.path.join(self.basedir(), 'refs', 'tags')):
147 ret[name] = self._get_ref(os.path.join(root, name))
152 for root, dirs, files in os.walk(os.path.join(self.basedir(), 'refs', 'heads')):
154 ret[name] = self._get_ref(os.path.join(root, name))
158 return self.ref('HEAD')
160 def _get_object(self, sha, cls):
161 ret = self.get_object(sha)
162 if ret._type != cls._type:
164 raise NotCommitError(ret)
166 raise NotBlobError(ret)
168 raise NotTreeError(ret)
170 raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
173 def get_object(self, sha):
174 return self.object_store[sha]
176 def get_parents(self, sha):
177 return self.commit(sha).parents
179 def commit(self, sha):
180 return self._get_object(sha, Commit)
183 return self._get_object(sha, Tree)
185 def get_blob(self, sha):
186 return self._get_object(sha, Blob)
188 def revision_history(self, head):
189 """Returns a list of the commits reachable from head.
191 Returns a list of commit objects. the first of which will be the commit
192 of head, then following theat will be the parents.
194 Raises NotCommitError if any no commits are referenced, including if the
195 head parameter isn't the sha of a commit.
197 XXX: work out how to handle merges.
199 # We build the list backwards, as parents are more likely to be older
201 pending_commits = [head]
203 while pending_commits != []:
204 head = pending_commits.pop(0)
206 commit = self.commit(head)
208 raise MissingCommitError(head)
209 if commit in history:
212 for known_commit in history:
213 if known_commit.commit_time > commit.commit_time:
216 history.insert(i, commit)
217 parents = commit.parents
218 pending_commits += parents
223 def init_bare(cls, path, mkdir=True):
224 for d in [["objects"],
233 os.mkdir(os.path.join(path, *d))
234 open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
235 open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
236 open(os.path.join(path, 'info', 'excludes'), 'w').write("")
241 class ObjectStore(object):
243 def __init__(self, path):
248 return os.path.join(self.path, PACKDIR)
250 def __contains__(self, sha):
251 # TODO: This can be more efficient
260 if self._packs is None:
261 self._packs = list(load_packs(self.pack_dir()))
264 def _get_shafile(self, sha):
267 # Check from object dir
268 path = os.path.join(self.path, dir, file)
269 if os.path.exists(path):
270 return ShaFile.from_file(path)
273 def get_raw(self, sha):
274 for pack in self.packs:
276 return pack.get_raw(sha, self.get_raw)
277 # FIXME: Are pack deltas ever against on-disk shafiles ?
278 ret = self._get_shafile(sha)
280 return ret.as_raw_string()
283 def __getitem__(self, sha):
284 assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
285 ret = self._get_shafile(sha)
289 type, uncomp = self.get_raw(sha)
290 return ShaFile.from_raw_string(type, uncomp)
292 def move_in_pack(self, path):
294 entries = p.sorted_entries(self.get_raw)
295 basename = os.path.join(self.pack_dir(), "pack-%s" % iter_sha1(entry[0] for entry in entries))
296 write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
297 os.rename(path, basename + ".pack")
300 fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
301 f = os.fdopen(fd, 'w')
303 if os.path.getsize(path) > 0:
304 self.move_in_pack(path)