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, NotGitRepository
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
if os.path.isdir(os.path.join(root, ".git", "objects")):
51
self._controldir = os.path.join(root, ".git")
52
elif os.path.isdir(os.path.join(root, "objects")):
54
self._controldir = root
56
raise NotGitRepository(root)
58
self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
59
self._object_store = None
62
return self._controldir
64
def fetch_objects(self, determine_wants, graph_walker, progress):
65
wants = determine_wants(self.heads())
67
ref = graph_walker.next()
69
commits_to_send.append(ref)
70
if ref in self.object_store:
72
ref = graph_walker.next()
74
for sha in commits_to_send:
81
def parse_tree(tree, sha_done):
82
for mode, name, x in tree.entries():
87
parse_tree(t, sha_done)
92
if treesha not in sha_done:
93
t = self.tree(treesha)
95
parse_tree(t, sha_done)
97
progress("counting objects: %d\r" % len(sha_done))
100
yield self.get_object(sha)
102
def object_dir(self):
103
return os.path.join(self.controldir(), OBJECTDIR)
106
def object_store(self):
107
if self._object_store is None:
108
self._object_store = ObjectStore(self.object_dir())
109
return self._object_store
112
return os.path.join(self.object_dir(), PACKDIR)
114
def _get_ref(self, file):
118
if contents.startswith(SYMREF):
119
ref = contents[len(SYMREF):]
123
assert len(contents) == 41, 'Invalid ref in %s' % file
129
for dir in self.ref_locs:
130
file = os.path.join(self.controldir(), dir, name)
131
if os.path.exists(file):
132
return self._get_ref(file)
135
ret = {"HEAD": self.head()}
136
for dir in ["refs/heads", "refs/tags"]:
137
for name in os.listdir(os.path.join(self.controldir(), dir)):
138
path = os.path.join(self.controldir(), dir, name)
139
if os.path.isfile(path):
140
ret["/".join([dir, name])] = self._get_ref(path)
143
def set_ref(self, name, value):
144
file = os.path.join(self.controldir(), name)
145
open(file, 'w').write(value+"\n")
147
def remove_ref(self, name):
148
file = os.path.join(self.controldir(), name)
149
if os.path.exists(file):
155
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
157
ret[name] = self._get_ref(os.path.join(root, name))
162
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'heads')):
164
ret[name] = self._get_ref(os.path.join(root, name))
168
return self.ref('HEAD')
170
def _get_object(self, sha, cls):
171
assert len(sha) in (20, 40)
172
ret = self.get_object(sha)
173
if ret._type != cls._type:
175
raise NotCommitError(ret)
177
raise NotBlobError(ret)
179
raise NotTreeError(ret)
181
raise Exception("Type invalid: %r != %r" % (ret._type, cls._type))
184
def get_object(self, sha):
185
return self.object_store[sha]
187
def get_parents(self, sha):
188
return self.commit(sha).parents
190
def commit(self, sha):
191
return self._get_object(sha, Commit)
194
return self._get_object(sha, Tree)
196
def get_blob(self, sha):
197
return self._get_object(sha, Blob)
199
def revision_history(self, head):
200
"""Returns a list of the commits reachable from head.
202
Returns a list of commit objects. the first of which will be the commit
203
of head, then following theat will be the parents.
205
Raises NotCommitError if any no commits are referenced, including if the
206
head parameter isn't the sha of a commit.
208
XXX: work out how to handle merges.
210
# We build the list backwards, as parents are more likely to be older
212
pending_commits = [head]
214
while pending_commits != []:
215
head = pending_commits.pop(0)
217
commit = self.commit(head)
219
raise MissingCommitError(head)
220
if commit in history:
223
for known_commit in history:
224
if known_commit.commit_time > commit.commit_time:
227
history.insert(i, commit)
228
parents = commit.parents
229
pending_commits += parents
234
return "<Repo at %r>" % self.path
237
def init_bare(cls, path, mkdir=True):
238
for d in [["objects"],
247
os.mkdir(os.path.join(path, *d))
248
open(os.path.join(path, 'HEAD'), 'w').write("ref: refs/heads/master\n")
249
open(os.path.join(path, 'description'), 'w').write("Unnamed repository")
250
open(os.path.join(path, 'info', 'excludes'), 'w').write("")
255
class ObjectStore(object):
257
def __init__(self, path):
262
return os.path.join(self.path, PACKDIR)
264
def __contains__(self, sha):
265
# TODO: This can be more efficient
274
"""List with pack objects."""
275
if self._packs is None:
276
self._packs = list(load_packs(self.pack_dir()))
279
def _get_shafile(self, sha):
282
# Check from object dir
283
path = os.path.join(self.path, dir, file)
284
if os.path.exists(path):
285
return ShaFile.from_file(path)
288
def get_raw(self, sha):
289
"""Obtain the raw text for an object.
291
:param sha: Sha for the object.
292
:return: tuple with object type and object contents.
294
for pack in self.packs:
296
return pack.get_raw(sha, self.get_raw)
297
# FIXME: Are pack deltas ever against on-disk shafiles ?
298
ret = self._get_shafile(sha)
300
return ret.as_raw_string()
303
def __getitem__(self, sha):
304
assert len(sha) == 40, "Incorrect length sha: %s" % str(sha)
305
ret = self._get_shafile(sha)
309
type, uncomp = self.get_raw(sha)
310
return ShaFile.from_raw_string(type, uncomp)
312
def move_in_pack(self, path):
313
"""Move a specific file containing a pack into the pack directory.
315
:note: The file should be on the same file system as the
318
:param path: Path to the pack file.
321
entries = p.sorted_entries(self.get_raw)
322
basename = os.path.join(self.pack_dir(),
323
"pack-%s" % iter_sha1(entry[0] for entry in entries))
324
write_pack_index_v2(basename+".idx", entries, p.calculate_checksum())
325
os.rename(path, basename + ".pack")
328
"""Add a new pack to this object store.
330
:return: Fileobject to write to and a commit function to
331
call when the pack is finished.
333
fd, path = tempfile.mkstemp(dir=self.pack_dir(), suffix=".pack")
334
f = os.fdopen(fd, 'w')
336
if os.path.getsize(path) > 0:
337
self.move_in_pack(path)