45
class ObjectStore(object):
53
class BaseObjectStore(object):
54
"""Object store interface."""
56
def determine_wants_all(self, refs):
57
return [sha for (ref, sha) in refs.iteritems() if not sha in self and not ref.endswith("^{}")]
59
def iter_shas(self, shas):
60
"""Iterate over the objects for the specified shas.
62
:param shas: Iterable object with SHAs
63
:return: Object iterator
65
return ObjectStoreIterator(self, shas)
67
def __contains__(self, sha):
68
"""Check if a particular object is present by SHA1."""
69
raise NotImplementedError(self.__contains__)
71
def get_raw(self, name):
72
"""Obtain the raw text for an object.
74
:param name: sha for the object.
75
:return: tuple with object type and object contents.
77
raise NotImplementedError(self.get_raw)
79
def __getitem__(self, sha):
80
"""Obtain an object by SHA1."""
81
type, uncomp = self.get_raw(sha)
82
return ShaFile.from_raw_string(type, uncomp)
85
"""Iterate over the SHAs that are present in this store."""
86
raise NotImplementedError(self.__iter__)
88
def add_object(self, obj):
89
"""Add a single object to this object store.
92
raise NotImplementedError(self.add_object)
94
def add_objects(self, objects):
95
"""Add a set of objects to this object store.
97
:param objects: Iterable over a list of objects.
99
raise NotImplementedError(self.add_objects)
101
def find_missing_objects(self, haves, wants, progress=None):
102
"""Find the missing objects required for a set of revisions.
104
:param haves: Iterable over SHAs already in common.
105
:param wants: Iterable over SHAs of objects to fetch.
106
:param progress: Simple progress function that will be called with
107
updated progress strings.
108
:return: Iterator over (sha, path) pairs.
110
return iter(MissingObjectFinder(self, haves, wants, progress).next, None)
112
def find_common_revisions(self, graphwalker):
113
"""Find which revisions this store has in common using graphwalker.
115
:param graphwalker: A graphwalker object.
116
:return: List of SHAs that are in common
119
sha = graphwalker.next()
124
sha = graphwalker.next()
127
def get_graph_walker(self, heads):
128
"""Obtain a graph walker for this object store.
130
:param heads: Local heads to start search with
131
:return: GraphWalker object
133
return ObjectStoreGraphWalker(heads, lambda sha: self[sha].parents)
136
def generate_pack_contents(self, have, want):
137
return self.iter_shas(self.find_missing_objects(have, want))
140
class DiskObjectStore(BaseObjectStore):
141
"""Git-style object store that exists on disk."""
48
143
def __init__(self, path):
49
144
"""Open an object store.
54
149
self._pack_cache = None
55
150
self.pack_dir = os.path.join(self.path, PACKDIR)
57
def determine_wants_all(self, refs):
58
return [sha for (ref, sha) in refs.iteritems() if not sha in self and not ref.endswith("^{}")]
60
def iter_shas(self, shas):
61
"""Iterate over the objects for the specified shas.
63
:param shas: Iterable object with SHAs
65
return ObjectStoreIterator(self, shas)
67
152
def __contains__(self, sha):
153
"""Check if a particular object is present by SHA1."""
68
154
for pack in self.packs:
163
"""Iterate over the SHAs that are present in this store."""
164
iterables = self.packs + [self._iter_shafile_shas()]
165
return itertools.chain(*iterables)
78
169
"""List with pack objects."""
79
170
if self._pack_cache is None:
80
self._pack_cache = list(load_packs(self.pack_dir))
171
self._pack_cache = list(self._load_packs())
81
172
return self._pack_cache
174
def _load_packs(self):
175
if not os.path.exists(self.pack_dir):
177
for name in os.listdir(self.pack_dir):
178
if name.startswith("pack-") and name.endswith(".pack"):
179
yield Pack(os.path.join(self.pack_dir, name[:-len(".pack")]))
83
181
def _add_known_pack(self, path):
84
182
"""Add a newly appeared pack to the cache by path.
102
207
def _add_shafile(self, sha, o):
103
path = self._get_shafile_path(sha)
104
f = os.path.open(path, 'w')
208
dir = os.path.join(self.path, sha[:2])
209
if not os.path.isdir(dir):
211
path = os.path.join(dir, sha[2:])
212
if os.path.exists(path):
213
return # Already there, no need to write again
216
f.write(o.as_legacy_object())
131
240
hexsha = sha_to_hex(name)
132
241
ret = self._get_shafile(hexsha)
133
242
if ret is not None:
134
return ret.as_raw_string()
243
return ret.type, ret.as_raw_string()
135
244
raise KeyError(hexsha)
137
def __getitem__(self, sha):
138
type, uncomp = self.get_raw(sha)
139
return ShaFile.from_raw_string(type, uncomp)
141
246
def move_in_thin_pack(self, path):
142
247
"""Move a specific file containing a pack into the pack directory.
147
252
:param path: Path to the pack file.
254
data = PackData(path)
256
# Write index for the thin pack (do we really need this?)
257
temppath = os.path.join(self.pack_dir,
258
sha_to_hex(urllib2.randombytes(20))+".tempidx")
259
data.create_index_v2(temppath, self.get_raw)
260
p = Pack.from_objects(data, load_pack_index(temppath))
262
# Write a full pack version
150
263
temppath = os.path.join(self.pack_dir,
151
264
sha_to_hex(urllib2.randombytes(20))+".temppack")
152
write_pack(temppath, p.iterobjects(self.get_raw), len(p))
265
write_pack(temppath, ((o, None) for o in p.iterobjects(self.get_raw)),
153
267
pack_sha = load_pack_index(temppath+".idx").objects_sha1()
154
268
newbasename = os.path.join(self.pack_dir, "pack-%s" % pack_sha)
155
269
os.rename(temppath+".pack", newbasename+".pack")
337
class MemoryObjectStore(BaseObjectStore):
340
super(MemoryObjectStore, self).__init__()
343
def __contains__(self, sha):
344
return sha in self._data
347
"""Iterate over the SHAs that are present in this store."""
348
return self._data.iterkeys()
350
def get_raw(self, name):
351
"""Obtain the raw text for an object.
353
:param name: sha for the object.
354
:return: tuple with object type and object contents.
356
return self[name].as_raw_string()
358
def __getitem__(self, name):
359
return self._data[name]
361
def add_object(self, obj):
362
"""Add a single object to this object store.
365
self._data[obj.id] = obj
367
def add_objects(self, objects):
368
"""Add a set of objects to this object store.
370
:param objects: Iterable over a list of objects.
373
self._data[obj.id] = obj
217
376
class ObjectImporter(object):
218
377
"""Interface for importing objects."""
279
438
return len(list(self.itershas()))
282
def tree_lookup_path(object_store, root_sha, path):
441
def tree_lookup_path(lookup_obj, root_sha, path):
442
"""Lookup an object in a Git tree.
444
:param lookup_obj: Callback for retrieving object by SHA1
445
:param root_sha: SHA1 of the root tree
446
:param path: Path to lookup
283
448
parts = path.split("/")
286
obj = object_store[sha]
451
obj = lookup_obj(sha)
287
452
if type(obj) is not Tree:
288
453
raise NotTreeError(sha)
289
456
mode, sha = obj[p]
290
return object_store[sha]
457
return lookup_obj(sha)
460
class MissingObjectFinder(object):
461
"""Find the objects missing from another object store.
463
:param object_store: Object store containing at least all objects to be
465
:param haves: SHA1s of commits not to send (already present in target)
466
:param wants: SHA1s of commits to send
467
:param progress: Optional function to report progress to.
470
def __init__(self, object_store, haves, wants, progress=None):
471
self.sha_done = set(haves)
472
self.objects_to_send = set([(w, None, False) for w in wants if w not in haves])
473
self.object_store = object_store
475
self.progress = lambda x: None
477
self.progress = progress
479
def add_todo(self, entries):
480
self.objects_to_send.update([e for e in entries if not e[0] in self.sha_done])
482
def parse_tree(self, tree):
483
self.add_todo([(sha, name, not stat.S_ISDIR(mode)) for (mode, name, sha) in tree.entries()])
485
def parse_commit(self, commit):
486
self.add_todo([(commit.tree, "", False)])
487
self.add_todo([(p, None, False) for p in commit.parents])
489
def parse_tag(self, tag):
490
self.add_todo([(tag.object[1], None, False)])
493
if not self.objects_to_send:
495
(sha, name, leaf) = self.objects_to_send.pop()
497
o = self.object_store[sha]
498
if isinstance(o, Commit):
500
elif isinstance(o, Tree):
502
elif isinstance(o, Tag):
504
self.sha_done.add(sha)
505
self.progress("counting objects: %d\r" % len(self.sha_done))
509
class ObjectStoreGraphWalker(object):
510
"""Graph walker that finds out what commits are missing from an object store."""
512
def __init__(self, local_heads, get_parents):
513
"""Create a new instance.
515
:param local_heads: Heads to start search with
516
:param get_parents: Function for finding the parents of a SHA1.
518
self.heads = set(local_heads)
519
self.get_parents = get_parents
523
"""Ack that a particular revision and its ancestors are present in the source."""
524
if sha in self.heads:
525
self.heads.remove(sha)
526
if sha in self.parents:
527
for p in self.parents[sha]:
531
"""Iterate over ancestors of heads in the target."""
533
ret = self.heads.pop()
534
ps = self.get_parents(ret)
535
self.parents[ret] = ps
536
self.heads.update(ps)