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
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,
23
from commit import Commit
31
from object_store import ObjectStore
45
def __init__(self, tagdir, tags):
49
def __getitem__(self, name):
50
return self.tags[name]
52
def __setitem__(self, name, ref):
54
f = open(os.path.join(self.tagdir, name), 'wb')
70
ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
72
def __init__(self, root):
73
if os.path.isdir(os.path.join(root, ".git", "objects")):
75
self._controldir = os.path.join(root, ".git")
76
elif os.path.isdir(os.path.join(root, "objects")):
78
self._controldir = root
80
raise NotGitRepository(root)
82
self.tags = Tags(self.tagdir(), self.get_tags())
83
self._object_store = None
86
return self._controldir
88
def find_missing_objects(self, determine_wants, graph_walker, progress):
89
"""Fetch the missing objects required for a set of revisions.
91
:param determine_wants: Function that takes a dictionary with heads
92
and returns the list of heads to fetch.
93
:param graph_walker: Object that can iterate over the list of revisions
94
to fetch and has an "ack" method that will be called to acknowledge
95
that a revision is present.
96
:param progress: Simple progress function that will be called with
97
updated progress strings.
99
wants = determine_wants(self.get_refs())
100
commits_to_send = set(wants)
102
ref = graph_walker.next()
105
if ref in self.object_store:
106
graph_walker.ack(ref)
107
ref = graph_walker.next()
108
while commits_to_send:
109
sha = commits_to_send.pop()
114
assert isinstance(c, Commit)
117
commits_to_send.update([p for p in c.parents if not p in sha_done])
119
def parse_tree(tree, sha_done):
120
for mode, name, x in tree.entries():
121
if not x in sha_done:
125
parse_tree(t, sha_done)
130
if treesha not in sha_done:
131
t = self.tree(treesha)
132
sha_done.add(treesha)
133
parse_tree(t, sha_done)
135
progress("counting objects: %d\r" % len(sha_done))
138
def fetch_objects(self, determine_wants, graph_walker, progress):
139
"""Fetch the missing objects required for a set of revisions.
141
:param determine_wants: Function that takes a dictionary with heads
142
and returns the list of heads to fetch.
143
:param graph_walker: Object that can iterate over the list of revisions
144
to fetch and has an "ack" method that will be called to acknowledge
145
that a revision is present.
146
:param progress: Simple progress function that will be called with
147
updated progress strings.
149
shas = self.find_missing_objects(determine_wants, graph_walker, progress)
151
yield self.get_object(sha)
153
def object_dir(self):
154
return os.path.join(self.controldir(), OBJECTDIR)
157
def object_store(self):
158
if self._object_store is None:
159
self._object_store = ObjectStore(self.object_dir())
160
return self._object_store
163
return os.path.join(self.object_dir(), PACKDIR)
165
def _get_ref(self, file):
169
if contents.startswith(SYMREF):
170
ref = contents[len(SYMREF):]
174
assert len(contents) == 41, 'Invalid ref in %s' % file
180
for dir in self.ref_locs:
181
file = os.path.join(self.controldir(), dir, name)
182
if os.path.exists(file):
183
return self._get_ref(file)
188
ret['HEAD'] = self.head()
189
for dir in ["refs/heads", "refs/tags"]:
190
for name in os.listdir(os.path.join(self.controldir(), dir)):
191
path = os.path.join(self.controldir(), dir, name)
192
if os.path.isfile(path):
193
ret["/".join([dir, name])] = self._get_ref(path)
196
def set_ref(self, name, value):
197
file = os.path.join(self.controldir(), name)
198
open(file, 'w').write(value+"\n")
200
def remove_ref(self, name):
201
file = os.path.join(self.controldir(), name)
202
if os.path.exists(file):
207
return os.path.join(self.controldir(), 'refs', 'tags')
211
for root, dirs, files in os.walk(self.tagdir()):
213
ret[name] = self._get_ref(os.path.join(root, name))
218
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
220
ret[name] = self._get_ref(os.path.join(root, name))
224
return self.ref('HEAD')
226
def _get_object(self, sha, cls):
227
assert len(sha) in (20, 40)
228
ret = self.get_object(sha)
229
if ret._type != cls._type:
231
raise NotCommitError(ret)
233
raise NotBlobError(ret)
235
raise NotTreeError(ret)
237
raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
240
def get_object(self, sha):
241
return self.object_store[sha]
243
def get_parents(self, sha):
244
return self.commit(sha).parents
246
def commit(self, sha):
247
return self._get_object(sha, Commit)
250
return self._get_object(sha, Tree)
252
def get_blob(self, sha):
253
return self._get_object(sha, Blob)
255
def revision_history(self, head):
256
"""Returns a list of the commits reachable from head.
258
Returns a list of commit objects. the first of which will be the commit
259
of head, then following theat will be the parents.
261
Raises NotCommitError if any no commits are referenced, including if the
262
head parameter isn't the sha of a commit.
264
XXX: work out how to handle merges.
266
# We build the list backwards, as parents are more likely to be older
268
pending_commits = [head]
270
while pending_commits != []:
271
head = pending_commits.pop(0)
273
commit = self.commit(head)
275
raise MissingCommitError(head)
276
if commit in history:
279
for known_commit in history:
280
if known_commit.commit_time > commit.commit_time:
283
history.insert(i, commit)
284
parents = commit.parents
285
pending_commits += parents
290
return "<Repo at %r>" % self.path
293
def init(cls, path, mkdir=True):
294
controldir = os.path.join(path, ".git")
296
cls.init_bare(controldir)
299
def init_bare(cls, path, mkdir=True):
300
for d in [["objects"],
309
os.mkdir(os.path.join(path, *d))
310
open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
311
open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
312
open(os.path.join(path, 'info', 'excludes'), 'w').write("")