22
from commit import Commit
30
from object_store import ObjectStore
44
def __init__(self, name, ref):
21
from objects import ShaFile
26
class Repository(object):
51
28
ref_locs = ['', 'refs', 'refs/tags', 'refs/heads', 'refs/remotes']
53
30
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
36
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)
37
return os.path.join(self.basedir(), objectdir)
146
39
def _get_ref(self, file):
147
40
f = open(file, 'rb')
149
42
contents = f.read()
150
if contents.startswith(SYMREF):
151
ref = contents[len(SYMREF):]
43
if contents.startswith(symref):
44
ref = contents[len(symref):]
152
45
if ref[-1] == '\n':
154
47
return self.ref(ref)
155
assert len(contents) == 41, 'Invalid ref in %s' % file
48
assert len(contents) == 41, 'Invalid ref'
156
49
return contents[:-1]
160
53
def ref(self, name):
161
54
for dir in self.ref_locs:
162
file = os.path.join(self.controldir(), dir, name)
55
file = os.path.join(self.basedir(), dir, name)
163
56
if os.path.exists(file):
164
57
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
60
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
62
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("")
63
assert len(sha) == 40, "Incorrect sha length"
66
path = os.path.join(self.object_dir(), dir, file)
67
if not os.path.exists(path):
69
return ShaFile.from_file(path)