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
44
def __init__(self, name, ref):
51
ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
53
def __init__(self, root):
54
if os.path.isdir(os.path.join(root, ".git", "objects")):
56
self._controldir = os.path.join(root, ".git")
57
elif os.path.isdir(os.path.join(root, "objects")):
59
self._controldir = root
61
raise NotGitRepository(root)
63
self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
64
self._object_store = None
67
return self._controldir
69
def find_missing_objects(self, determine_wants, graph_walker, progress):
70
"""Fetch the missing objects required for a set of revisions.
72
:param determine_wants: Function that takes a dictionary with heads
73
and returns the list of heads to fetch.
74
:param graph_walker: Object that can iterate over the list of revisions
75
to fetch and has an "ack" method that will be called to acknowledge
76
that a revision is present.
77
:param progress: Simple progress function that will be called with
78
updated progress strings.
80
wants = determine_wants(self.get_refs())
81
commits_to_send = set(wants)
83
ref = graph_walker.next()
86
if ref in self.object_store:
88
ref = graph_walker.next()
89
while commits_to_send:
90
sha = commits_to_send.pop()
95
assert isinstance(c, Commit)
98
commits_to_send.update([p for p in c.parents if not p in sha_done])
100
def parse_tree(tree, sha_done):
101
for mode, name, x in tree.entries():
102
if not x in sha_done:
106
parse_tree(t, sha_done)
111
if treesha not in sha_done:
112
t = self.tree(treesha)
113
sha_done.add(treesha)
114
parse_tree(t, sha_done)
116
progress("counting objects: %d\r" % len(sha_done))
119
def fetch_objects(self, determine_wants, graph_walker, progress):
120
"""Fetch the missing objects required for a set of revisions.
122
:param determine_wants: Function that takes a dictionary with heads
123
and returns the list of heads to fetch.
124
:param graph_walker: Object that can iterate over the list of revisions
125
to fetch and has an "ack" method that will be called to acknowledge
126
that a revision is present.
127
:param progress: Simple progress function that will be called with
128
updated progress strings.
130
shas = self.find_missing_objects(determine_wants, graph_walker, progress)
132
yield self.get_object(sha)
134
def object_dir(self):
135
return os.path.join(self.controldir(), OBJECTDIR)
138
def object_store(self):
139
if self._object_store is None:
140
self._object_store = ObjectStore(self.object_dir())
141
return self._object_store
144
return os.path.join(self.object_dir(), PACKDIR)
146
def _get_ref(self, file):
150
if contents.startswith(SYMREF):
151
ref = contents[len(SYMREF):]
155
assert len(contents) == 41, 'Invalid ref in %s' % file
161
for dir in self.ref_locs:
162
file = os.path.join(self.controldir(), dir, name)
163
if os.path.exists(file):
164
return self._get_ref(file)
169
ret['HEAD'] = self.head()
170
for dir in ["refs/heads", "refs/tags"]:
171
for name in os.listdir(os.path.join(self.controldir(), dir)):
172
path = os.path.join(self.controldir(), dir, name)
173
if os.path.isfile(path):
174
ret["/".join([dir, name])] = self._get_ref(path)
177
def set_ref(self, name, value):
178
file = os.path.join(self.controldir(), name)
179
open(file, 'w').write(value+"\n")
181
def remove_ref(self, name):
182
file = os.path.join(self.controldir(), name)
183
if os.path.exists(file):
189
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
191
ret[name] = self._get_ref(os.path.join(root, name))
196
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
198
ret[name] = self._get_ref(os.path.join(root, name))
202
return self.ref('HEAD')
204
def _get_object(self, sha, cls):
205
assert len(sha) in (20, 40)
206
ret = self.get_object(sha)
207
if ret._type != cls._type:
209
raise NotCommitError(ret)
211
raise NotBlobError(ret)
213
raise NotTreeError(ret)
215
raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
218
def get_object(self, sha):
219
return self.object_store[sha]
221
def get_parents(self, sha):
222
return self.commit(sha).parents
224
def commit(self, sha):
225
return self._get_object(sha, Commit)
228
return self._get_object(sha, Tree)
230
def get_blob(self, sha):
231
return self._get_object(sha, Blob)
233
def revision_history(self, head):
234
"""Returns a list of the commits reachable from head.
236
Returns a list of commit objects. the first of which will be the commit
237
of head, then following theat will be the parents.
239
Raises NotCommitError if any no commits are referenced, including if the
240
head parameter isn't the sha of a commit.
242
XXX: work out how to handle merges.
244
# We build the list backwards, as parents are more likely to be older
246
pending_commits = [head]
248
while pending_commits != []:
249
head = pending_commits.pop(0)
251
commit = self.commit(head)
253
raise MissingCommitError(head)
254
if commit in history:
257
for known_commit in history:
258
if known_commit.commit_time > commit.commit_time:
261
history.insert(i, commit)
262
parents = commit.parents
263
pending_commits += parents
268
return "<Repo at %r>" % self.path
271
def init(cls, path, mkdir=True):
272
controldir = os.path.join(path, ".git")
274
cls.init_bare(controldir)
277
def init_bare(cls, path, mkdir=True):
278
for d in [["objects"],
287
os.mkdir(os.path.join(path, *d))
288
open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
289
open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
290
open(os.path.join(path, 'info', 'excludes'), 'w').write("")