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
30 from object_store import ObjectStore
45 def __init__(self, name, ref):
52 ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
54 def __init__(self, root):
55 if os.path.isdir(os.path.join(root, ".git", "objects")):
57 self._controldir = os.path.join(root, ".git")
58 elif os.path.isdir(os.path.join(root, "objects")):
60 self._controldir = root
62 raise NotGitRepository(root)
64 self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
65 self._object_store = None
68 return self._controldir
70 def find_missing_objects(self, determine_wants, graph_walker, progress):
71 """Fetch the missing objects required for a set of revisions.
73 :param determine_wants: Function that takes a dictionary with heads
74 and returns the list of heads to fetch.
75 :param graph_walker: Object that can iterate over the list of revisions
76 to fetch and has an "ack" method that will be called to acknowledge
77 that a revision is present.
78 :param progress: Simple progress function that will be called with
79 updated progress strings.
81 wants = determine_wants(self.get_refs())
82 commits_to_send = set(wants)
84 ref = graph_walker.next()
87 if ref in self.object_store:
89 ref = graph_walker.next()
90 while commits_to_send:
91 sha = commits_to_send.pop()
96 assert isinstance(c, Commit)
99 commits_to_send.update([p for p in c.parents if not p in sha_done])
101 def parse_tree(tree, sha_done):
102 for mode, name, x in tree.entries():
103 if not x in sha_done:
107 parse_tree(t, sha_done)
112 if treesha not in sha_done:
113 t = self.tree(treesha)
114 sha_done.add(treesha)
115 parse_tree(t, sha_done)
117 progress("counting objects: %d\r" % len(sha_done))
120 def fetch_objects(self, determine_wants, graph_walker, progress):
121 """Fetch the missing objects required for a set of revisions.
123 :param determine_wants: Function that takes a dictionary with heads
124 and returns the list of heads to fetch.
125 :param graph_walker: Object that can iterate over the list of revisions
126 to fetch and has an "ack" method that will be called to acknowledge
127 that a revision is present.
128 :param progress: Simple progress function that will be called with
129 updated progress strings.
131 shas = self.find_missing_objects(determine_wants, graph_walker, progress)
133 yield self.get_object(sha)
135 def object_dir(self):
136 return os.path.join(self.controldir(), OBJECTDIR)
139 def object_store(self):
140 if self._object_store is None:
141 self._object_store = ObjectStore(self.object_dir())
142 return self._object_store
145 return os.path.join(self.object_dir(), PACKDIR)
147 def _get_ref(self, file):
151 if contents.startswith(SYMREF):
152 ref = contents[len(SYMREF):]
156 assert len(contents) == 41, 'Invalid ref in %s' % file
162 for dir in self.ref_locs:
163 file = os.path.join(self.controldir(), dir, name)
164 if os.path.exists(file):
165 return self._get_ref(file)
170 ret['HEAD'] = self.head()
171 for dir in ["refs/heads", "refs/tags"]:
172 for name in os.listdir(os.path.join(self.controldir(), dir)):
173 path = os.path.join(self.controldir(), dir, name)
174 if os.path.isfile(path):
175 ret["/".join([dir, name])] = self._get_ref(path)
178 def set_ref(self, name, value):
179 file = os.path.join(self.controldir(), name)
180 open(file, 'w').write(value+"\n")
182 def remove_ref(self, name):
183 file = os.path.join(self.controldir(), name)
184 if os.path.exists(file):
190 for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
192 ret[name] = self._get_ref(os.path.join(root, name))
197 for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
199 ret[name] = self._get_ref(os.path.join(root, name))
203 return self.ref('HEAD')
205 def _get_object(self, sha, cls):
206 assert len(sha) in (20, 40)
207 ret = self.get_object(sha)
208 if ret._type != cls._type:
210 raise NotCommitError(ret)
212 raise NotBlobError(ret)
214 raise NotTreeError(ret)
216 raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
219 def get_object(self, sha):
220 return self.object_store[sha]
222 def get_parents(self, sha):
223 return self.commit(sha).parents
225 def commit(self, sha):
226 return self._get_object(sha, Commit)
229 return self._get_object(sha, Tree)
231 def get_blob(self, sha):
232 return self._get_object(sha, Blob)
234 def revision_history(self, head):
235 """Returns a list of the commits reachable from head.
237 Returns a list of commit objects. the first of which will be the commit
238 of head, then following theat will be the parents.
240 Raises NotCommitError if any no commits are referenced, including if the
241 head parameter isn't the sha of a commit.
243 XXX: work out how to handle merges.
245 # We build the list backwards, as parents are more likely to be older
247 pending_commits = [head]
249 while pending_commits != []:
250 head = pending_commits.pop(0)
252 commit = self.commit(head)
254 raise MissingCommitError(head)
255 if commit in history:
258 for known_commit in history:
259 if known_commit.commit_time > commit.commit_time:
262 history.insert(i, commit)
263 parents = commit.parents
264 pending_commits += parents
269 return "<Repo at %r>" % self.path
272 def init_bare(cls, path, mkdir=True):
273 for d in [["objects"],
282 os.mkdir(os.path.join(path, *d))
283 open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
284 open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
285 open(os.path.join(path, 'info', 'excludes'), 'w').write("")