22
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
30
from object_store import ObjectStore
32
38
OBJECTDIR = 'objects'
39
def __init__(self, name, ref):
44
def __init__(self, tagdir, tags):
48
def __getitem__(self, name):
49
return self.tags[name]
51
def __setitem__(self, name, ref):
53
f = open(os.path.join(self.tagdir, name), 'wb')
44
67
class Repo(object):
56
79
raise NotGitRepository(root)
58
self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
81
self.tags = Tags(self.tagdir(), self.get_tags())
59
82
self._object_store = None
61
84
def controldir(self):
62
85
return self._controldir
64
def fetch_objects(self, determine_wants, graph_walker, progress):
65
wants = determine_wants(self.heads())
87
def find_missing_objects(self, determine_wants, graph_walker, progress):
88
"""Fetch the missing objects required for a set of revisions.
90
:param determine_wants: Function that takes a dictionary with heads
91
and returns the list of heads to fetch.
92
:param graph_walker: Object that can iterate over the list of revisions
93
to fetch and has an "ack" method that will be called to acknowledge
94
that a revision is present.
95
:param progress: Simple progress function that will be called with
96
updated progress strings.
98
wants = determine_wants(self.get_refs())
99
commits_to_send = set(wants)
67
101
ref = graph_walker.next()
69
commits_to_send.append(ref)
70
104
if ref in self.object_store:
71
105
graph_walker.ack(ref)
72
106
ref = graph_walker.next()
74
for sha in commits_to_send:
107
while commits_to_send:
108
sha = commits_to_send.pop()
75
109
if sha in sha_done:
78
112
c = self.commit(sha)
113
assert isinstance(c, Commit)
116
commits_to_send.update([p for p in c.parents if not p in sha_done])
81
118
def parse_tree(tree, sha_done):
82
119
for mode, name, x in tree.entries():
83
120
if not x in sha_done:
95
132
parse_tree(t, sha_done)
97
134
progress("counting objects: %d\r" % len(sha_done))
100
yield self.get_object(sha)
137
def fetch_objects(self, determine_wants, graph_walker, progress):
138
"""Fetch the missing objects required for a set of revisions.
140
:param determine_wants: Function that takes a dictionary with heads
141
and returns the list of heads to fetch.
142
:param graph_walker: Object that can iterate over the list of revisions
143
to fetch and has an "ack" method that will be called to acknowledge
144
that a revision is present.
145
:param progress: Simple progress function that will be called with
146
updated progress strings.
148
shas = self.find_missing_objects(determine_wants, graph_walker, progress)
150
yield self.get_object(sha)
102
152
def object_dir(self):
103
153
return os.path.join(self.controldir(), OBJECTDIR)
132
182
return self._get_ref(file)
134
184
def get_refs(self):
135
ret = {"HEAD": self.head()}
187
ret['HEAD'] = self.head()
136
188
for dir in ["refs/heads", "refs/tags"]:
137
189
for name in os.listdir(os.path.join(self.controldir(), dir)):
138
190
path = os.path.join(self.controldir(), dir, name)
206
return os.path.join(self.controldir(), 'refs', 'tags')
153
208
def get_tags(self):
155
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
210
for root, dirs, files in os.walk(self.tagdir()):
156
211
for name in files:
157
212
ret[name] = self._get_ref(os.path.join(root, name))
234
289
return "<Repo at %r>" % self.path
292
def init(cls, path, mkdir=True):
293
controldir = os.path.join(path, ".git")
295
cls.init_bare(controldir)
237
298
def init_bare(cls, path, mkdir=True):
238
299
for d in [["objects"],
239
300
["objects", "info"],
252
313
create = init_bare
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)