1 # repo.py -- For dealing wih git repositories.
2 # Copyright (C) 2007 James Westby <jw+debian@jameswestby.net>
3 # Copyright (C) 2008-2009 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
8 # of the License or (at your option) any later version of
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
24 from dulwich.errors import (
31 from dulwich.object_store import (
34 from dulwich.objects import (
45 INDEX_FILENAME = "index"
50 def __init__(self, tagdir, tags):
54 def __getitem__(self, name):
55 return self.tags[name]
57 def __setitem__(self, name, ref):
59 f = open(os.path.join(self.tagdir, name), 'wb')
73 def read_packed_refs(f):
74 """Read a packed refs file.
76 Yields tuples with ref names and SHA1s.
78 :param f: file-like object to read from
81 for l in f.readlines():
86 # FIXME: Return somehow
88 yield tuple(l.rstrip("\n").split(" ", 2))
92 """A local git repository."""
94 ref_locs = ['', REFSDIR, 'refs/tags', 'refs/heads', 'refs/remotes']
96 def __init__(self, root):
97 if os.path.isdir(os.path.join(root, ".git", OBJECTDIR)):
99 self._controldir = os.path.join(root, ".git")
100 elif os.path.isdir(os.path.join(root, OBJECTDIR)):
102 self._controldir = root
104 raise NotGitRepository(root)
106 self.tags = Tags(self.tagdir(), self.get_tags())
107 self._object_store = None
109 def controldir(self):
110 """Return the path of the control directory."""
111 return self._controldir
113 def index_path(self):
114 return os.path.join(self.controldir(), INDEX_FILENAME)
116 def open_index(self):
117 """Open the index for this repository."""
118 from dulwich.index import Index
119 return Index(self.index_path())
122 """Check if an index is present."""
123 return os.path.exists(self.index_path())
125 def find_missing_objects(self, determine_wants, graph_walker, progress):
126 """Find the missing objects required for a set of revisions.
128 :param determine_wants: Function that takes a dictionary with heads
129 and returns the list of heads to fetch.
130 :param graph_walker: Object that can iterate over the list of revisions
131 to fetch and has an "ack" method that will be called to acknowledge
132 that a revision is present.
133 :param progress: Simple progress function that will be called with
134 updated progress strings.
135 :return: Iterator over (sha, path) pairs.
137 wants = determine_wants(self.get_refs())
138 return self.object_store.find_missing_objects(wants,
139 graph_walker, progress)
141 def fetch_objects(self, determine_wants, graph_walker, progress):
142 """Fetch the missing objects required for a set of revisions.
144 :param determine_wants: Function that takes a dictionary with heads
145 and returns the list of heads to fetch.
146 :param graph_walker: Object that can iterate over the list of revisions
147 to fetch and has an "ack" method that will be called to acknowledge
148 that a revision is present.
149 :param progress: Simple progress function that will be called with
150 updated progress strings.
151 :return: tuple with number of objects, iterator over objects
153 return self.object_store.iter_shas(
154 self.find_missing_objects(determine_wants, graph_walker, progress))
156 def object_dir(self):
157 return os.path.join(self.controldir(), OBJECTDIR)
160 def object_store(self):
161 if self._object_store is None:
162 self._object_store = ObjectStore(self.object_dir())
163 return self._object_store
166 return os.path.join(self.object_dir(), PACKDIR)
168 def _get_ref(self, file):
172 if contents.startswith(SYMREF):
173 ref = contents[len(SYMREF):]
177 assert len(contents) == 41, 'Invalid ref in %s' % file
183 """Return the SHA1 a ref is pointing to."""
184 for dir in self.ref_locs:
185 file = os.path.join(self.controldir(), dir, name)
186 if os.path.exists(file):
187 return self._get_ref(file)
188 packed_refs = self.get_packed_refs()
189 if name in packed_refs:
190 return packed_refs[name]
195 ret['HEAD'] = self.head()
196 for dir in ["refs/heads", "refs/tags"]:
197 for name in os.listdir(os.path.join(self.controldir(), dir)):
198 path = os.path.join(self.controldir(), dir, name)
199 if os.path.isfile(path):
200 ret["/".join([dir, name])] = self._get_ref(path)
201 ret.update(self.get_packed_refs())
204 def get_packed_refs(self):
205 path = os.path.join(self.controldir(), 'packed-refs')
206 if not os.path.exists(path):
211 for entry in read_packed_refs(f):
212 ret[entry[1]] = entry[0]
217 def set_ref(self, name, value):
218 file = os.path.join(self.controldir(), name)
219 dirpath = os.path.dirname(file)
220 if not os.path.exists(dirpath):
228 def remove_ref(self, name):
229 file = os.path.join(self.controldir(), name)
230 if os.path.exists(file):
235 return os.path.join(self.controldir(), REFSDIR, 'tags')
239 for root, dirs, files in os.walk(self.tagdir()):
241 ret[name] = self._get_ref(os.path.join(root, name))
246 for root, dirs, files in os.walk(os.path.join(self.controldir(), REFSDIR, 'heads')):
248 ret[name] = self._get_ref(os.path.join(root, name))
252 return self.ref('HEAD')
254 def _get_object(self, sha, cls):
255 assert len(sha) in (20, 40)
256 ret = self.get_object(sha)
257 if ret._type != cls._type:
259 raise NotCommitError(ret)
261 raise NotBlobError(ret)
263 raise NotTreeError(ret)
265 raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
268 def get_object(self, sha):
269 return self.object_store[sha]
271 def get_parents(self, sha):
272 return self.commit(sha).parents
274 def commit(self, sha):
275 return self._get_object(sha, Commit)
278 return self._get_object(sha, Tree)
281 return self._get_object(sha, Tag)
283 def get_blob(self, sha):
284 return self._get_object(sha, Blob)
286 def revision_history(self, head):
287 """Returns a list of the commits reachable from head.
289 Returns a list of commit objects. the first of which will be the commit
290 of head, then following theat will be the parents.
292 Raises NotCommitError if any no commits are referenced, including if the
293 head parameter isn't the sha of a commit.
295 XXX: work out how to handle merges.
297 # We build the list backwards, as parents are more likely to be older
299 pending_commits = [head]
301 while pending_commits != []:
302 head = pending_commits.pop(0)
304 commit = self.commit(head)
306 raise MissingCommitError(head)
307 if commit in history:
310 for known_commit in history:
311 if known_commit.commit_time > commit.commit_time:
314 history.insert(i, commit)
315 parents = commit.parents
316 pending_commits += parents
321 return "<Repo at %r>" % self.path
324 def init(cls, path, mkdir=True):
325 controldir = os.path.join(path, ".git")
327 cls.init_bare(controldir)
330 def init_bare(cls, path, mkdir=True):
331 for d in [[OBJECTDIR],
340 os.mkdir(os.path.join(path, *d))
341 open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
342 open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
343 open(os.path.join(path, 'info', 'excludes'), 'w').write("")