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
64 return os.path.join(self.basedir(), OBJECTDIR)
67 def object_store(self):
68 if self._object_store is None:
69 self._object_store = ObjectStore(self.object_dir())
70 return self._object_store
73 return os.path.join(self.object_dir(), PACKDIR)
75 def _get_ref(self, file):
79 if contents.startswith(SYMREF):
80 ref = contents[len(SYMREF):]
84 assert len(contents) == 41, 'Invalid ref'
90 for dir in self.ref_locs:
91 file = os.path.join(self.basedir(), dir, name)
92 if os.path.exists(file):
93 return self._get_ref(file)
95 def set_ref(self, name, value):
96 file = os.path.join(self.basedir(), name)
97 open(file, 'w').write(value+"\n")
99 def remove_ref(self, name):
100 file = os.path.join(self.basedir(), name)
101 if os.path.exists(file):
107 for root, dirs, files in os.walk(os.path.join(self.basedir(), 'refs', 'tags')):
109 ret[name] = self._get_ref(os.path.join(root, name))
114 for root, dirs, files in os.walk(os.path.join(self.basedir(), 'refs', 'heads')):
116 ret[name] = self._get_ref(os.path.join(root, name))
120 return self.ref('HEAD')
122 def _get_object(self, sha, cls):
123 ret = self.get_object(sha)
124 if ret._type != cls._type:
126 raise NotCommitError(ret)
128 raise NotBlobError(ret)
130 raise NotTreeError(ret)
132 raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
135 def get_object(self, sha):
136 return self.object_store[sha]
138 def get_parents(self, sha):
139 return self.commit(sha).parents
141 def commit(self, sha):
142 return self._get_object(sha, Commit)
145 return self._get_object(sha, Tree)
147 def get_blob(self, sha):
148 return self._get_object(sha, Blob)
150 def revision_history(self, head):
151 """Returns a list of the commits reachable from head.
153 Returns a list of commit objects. the first of which will be the commit
154 of head, then following theat will be the parents.
156 Raises NotCommitError if any no commits are referenced, including if the
157 head parameter isn't the sha of a commit.
159 XXX: work out how to handle merges.
161 # We build the list backwards, as parents are more likely to be older
163 pending_commits = [head]
165 while pending_commits != []:
166 head = pending_commits.pop(0)
168 commit = self.commit(head)
170 raise MissingCommitError(head)
171 if commit in history:
174 for known_commit in history:
175 if known_commit.commit_time > commit.commit_time:
178 history.insert(i, commit)
179 parents = commit.parents
180 pending_commits += parents
185 def init_bare(cls, path, mkdir=True):
186 for d in [["objects"],
195 os.mkdir(os.path.join(path, *d))
196 open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
197 open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
198 open(os.path.join(path, 'info', 'excludes'), 'w').write("")
203 class ObjectStore(object):
205 def __init__(self, path):
210 return os.path.join(self.path, PACKDIR)
212 def __contains__(self, sha):
213 # TODO: This can be more efficient
222 if self._packs is None:
223 self._packs = list(load_packs(self.pack_dir()))
226 def _get_shafile(self, sha):
229 # Check from object dir
230 path = os.path.join(self.path, dir, file)
231 if os.path.exists(path):
232 return ShaFile.from_file(path)
235 def get_raw(self, sha):
236 for pack in self.packs:
238 return pack.get_raw(sha, self.get_raw)
239 # FIXME: Are pack deltas ever against on-disk shafiles ?
240 ret = self._get_shafile(sha)
242 return ret.as_raw_string()
245 def __getitem__(self, sha):
246 assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
247 ret = self._get_shafile(sha)
251 type, uncomp = self.get_raw(sha)
252 return ShaFile.from_raw_string(type, uncomp)
254 def move_in_pack(self, path):
256 entries = p.sorted_entries(self.get_raw)
257 basename = os.path.join(self.pack_dir(), "pack-%s" % iter_sha1(entry[0] for entry in entries))
258 write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
259 os.rename(path, basename + ".pack")
262 fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
263 f = os.fdopen(fd, 'w')
265 if os.path.getsize(path) > 0:
266 self.move_in_pack(path)