5
5
# This program is free software; you can redistribute it and/or
6
6
# modify it under the terms of the GNU General Public License
7
7
# as published by the Free Software Foundation; version 2
8
# of the License or (at your option) any later version of
10
11
# This program is distributed in the hope that it will be useful,
11
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
22
23
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
31
from object_store import ObjectStore
32
39
OBJECTDIR = 'objects'
39
def __init__(self, name, ref):
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')
44
68
class Repo(object):
56
80
raise NotGitRepository(root)
58
self.tags = [Tag(name, ref) for name, ref in self.get_tags().items()]
82
self.tags = Tags(self.tagdir(), self.get_tags())
59
83
self._object_store = None
61
85
def controldir(self):
62
86
return self._controldir
64
def fetch_objects(self, determine_wants, graph_walker, progress):
65
wants = determine_wants(self.heads())
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)
67
102
ref = graph_walker.next()
69
commits_to_send.append(ref)
70
105
if ref in self.object_store:
71
106
graph_walker.ack(ref)
72
107
ref = graph_walker.next()
74
for sha in commits_to_send:
108
while commits_to_send:
109
sha = commits_to_send.pop()
75
110
if sha in sha_done:
78
113
c = self.commit(sha)
114
assert isinstance(c, Commit)
117
commits_to_send.update([p for p in c.parents if not p in sha_done])
81
119
def parse_tree(tree, sha_done):
82
120
for mode, name, x in tree.entries():
83
121
if not x in sha_done:
95
133
parse_tree(t, sha_done)
97
135
progress("counting objects: %d\r" % len(sha_done))
100
yield self.get_object(sha)
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)
102
153
def object_dir(self):
103
154
return os.path.join(self.controldir(), OBJECTDIR)
132
183
return self._get_ref(file)
134
185
def get_refs(self):
135
ret = {"HEAD": self.head()}
188
ret['HEAD'] = self.head()
136
189
for dir in ["refs/heads", "refs/tags"]:
137
190
for name in os.listdir(os.path.join(self.controldir(), dir)):
138
191
path = os.path.join(self.controldir(), dir, name)
207
return os.path.join(self.controldir(), 'refs', 'tags')
153
209
def get_tags(self):
155
for root, dirs, files in os.walk(os.path.join(self.controldir(), 'refs', 'tags')):
211
for root, dirs, files in os.walk(self.tagdir()):
156
212
for name in files:
157
213
ret[name] = self._get_ref(os.path.join(root, name))
234
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)
237
299
def init_bare(cls, path, mkdir=True):
238
300
for d in [["objects"],
239
301
["objects", "info"],
252
314
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)