32
31
from dulwich.pack import (
38
34
load_pack_index_file,
42
36
from dulwich.repo import (
50
read_packed_refs_with_peeled,
55
43
from bzrlib.errors import (
62
class TransportRefsContainer(RefsContainer):
63
"""Refs container that reads refs from a transport."""
65
def __init__(self, transport):
66
self.transport = transport
67
self._packed_refs = None
68
self._peeled_refs = None
71
return "%s(%r)" % (self.__class__.__name__, self.transport)
73
def _ensure_dir_exists(self, path):
74
for n in range(path.count("/")):
75
dirname = "/".join(path.split("/")[:n+1])
77
self.transport.mkdir(dirname)
81
def subkeys(self, base):
84
iter_files = self.transport.clone(base).iter_files_recursive()
85
keys.update(("%s/%s" % (base, refname)).strip("/") for
86
refname in iter_files if check_ref_format("%s/%s" % (base, refname)))
87
except (TransportNotPossible, NoSuchFile):
89
for key in self.get_packed_refs():
90
if key.startswith(base):
91
keys.add(key[len(base):].strip("/"))
96
if self.transport.has("HEAD"):
99
iter_files = list(self.transport.clone("refs").iter_files_recursive())
100
for filename in iter_files:
101
refname = "refs/%s" % filename
102
if check_ref_format(refname):
104
except (TransportNotPossible, NoSuchFile):
106
keys.update(self.get_packed_refs())
109
def get_packed_refs(self):
110
"""Get contents of the packed-refs file.
112
:return: Dictionary mapping ref names to SHA1s
114
:note: Will return an empty dictionary when no packed-refs file is
117
# TODO: invalidate the cache on repacking
118
if self._packed_refs is None:
119
# set both to empty because we want _peeled_refs to be
120
# None if and only if _packed_refs is also None.
121
self._packed_refs = {}
122
self._peeled_refs = {}
124
f = self.transport.get("packed-refs")
128
first_line = iter(f).next().rstrip()
129
if (first_line.startswith("# pack-refs") and " peeled" in
131
for sha, name, peeled in read_packed_refs_with_peeled(f):
132
self._packed_refs[name] = sha
134
self._peeled_refs[name] = peeled
137
for sha, name in read_packed_refs(f):
138
self._packed_refs[name] = sha
141
return self._packed_refs
143
def get_peeled(self, name):
144
"""Return the cached peeled value of a ref, if available.
146
:param name: Name of the ref to peel
147
:return: The peeled value of the ref. If the ref is known not point to a
148
tag, this will be the SHA the ref refers to. If the ref may point to
149
a tag, but no cached information is available, None is returned.
151
self.get_packed_refs()
152
if self._peeled_refs is None or name not in self._packed_refs:
153
# No cache: no peeled refs were read, or this ref is loose
155
if name in self._peeled_refs:
156
return self._peeled_refs[name]
161
def read_loose_ref(self, name):
162
"""Read a reference file and return its contents.
164
If the reference file a symbolic reference, only read the first line of
165
the file. Otherwise, only read the first 40 bytes.
167
:param name: the refname to read, relative to refpath
168
:return: The contents of the ref file, or None if the file does not
170
:raises IOError: if any other error occurs
173
f = self.transport.get(name)
177
header = f.read(len(SYMREF))
179
# Read only the first line
180
return header + iter(f).next().rstrip("\r\n")
182
# Read only the first 40 bytes
183
return header + f.read(40-len(SYMREF))
187
def _remove_packed_ref(self, name):
188
if self._packed_refs is None:
190
# reread cached refs from disk, while holding the lock
192
self._packed_refs = None
193
self.get_packed_refs()
195
if name not in self._packed_refs:
198
del self._packed_refs[name]
199
if name in self._peeled_refs:
200
del self._peeled_refs[name]
201
f = self.transport.open_write_stream("packed-refs")
203
write_packed_refs(f, self._packed_refs, self._peeled_refs)
207
def set_symbolic_ref(self, name, other):
208
"""Make a ref point at another ref.
210
:param name: Name of the ref to set
211
:param other: Name of the ref to point at
213
self._check_refname(name)
214
self._check_refname(other)
215
self._ensure_dir_exists(name)
216
self.transport.put_bytes(name, SYMREF + other + '\n')
218
def set_if_equals(self, name, old_ref, new_ref):
219
"""Set a refname to new_ref only if it currently equals old_ref.
221
This method follows all symbolic references, and can be used to perform
222
an atomic compare-and-swap operation.
224
:param name: The refname to set.
225
:param old_ref: The old sha the refname must refer to, or None to set
227
:param new_ref: The new sha the refname will refer to.
228
:return: True if the set was successful, False otherwise.
231
realname, _ = self._follow(name)
234
self._ensure_dir_exists(realname)
235
self.transport.put_bytes(realname, new_ref+"\n")
238
def add_if_new(self, name, ref):
239
"""Add a new reference only if it does not already exist.
241
This method follows symrefs, and only ensures that the last ref in the
242
chain does not exist.
244
:param name: The refname to set.
245
:param ref: The new sha the refname will refer to.
246
:return: True if the add was successful, False otherwise.
249
realname, contents = self._follow(name)
250
if contents is not None:
254
self._check_refname(realname)
255
self._ensure_dir_exists(realname)
256
self.transport.put_bytes(realname, ref+"\n")
259
def remove_if_equals(self, name, old_ref):
260
"""Remove a refname only if it currently equals old_ref.
262
This method does not follow symbolic references. It can be used to
263
perform an atomic compare-and-delete operation.
265
:param name: The refname to delete.
266
:param old_ref: The old sha the refname must refer to, or None to delete
268
:return: True if the delete was successful, False otherwise.
270
self._check_refname(name)
273
self.transport.delete(name)
276
self._remove_packed_ref(name)
280
48
class TransportRepo(BaseRepo):
282
50
def __init__(self, transport):
283
51
self.transport = transport
285
if self.transport.has(".git/%s" % OBJECTDIR):
53
if self.transport.has(".git/info/refs"):
287
55
self._controltransport = self.transport.clone('.git')
288
elif self.transport.has_any(["info/refs", OBJECTDIR, REFSDIR]):
56
elif self.transport.has("info/refs"):
290
58
self._controltransport = self.transport
345
103
self.transport = transport
346
104
self.pack_transport = self.transport.clone(PACKDIR)
349
return "%s(%r)" % (self.__class__.__name__, self.transport)
351
def _pack_cache_stale(self):
354
def _pack_names(self):
356
f = self.transport.get('info/packs')
358
return self.pack_transport.list_dir(".")
361
for line in f.readlines():
362
line = line.rstrip("\n")
365
(kind, name) = line.split(" ", 1)
371
106
def _load_packs(self):
373
for name in self._pack_names():
108
for line in self.transport.get('info/packs').readlines():
109
line = line.rstrip("\n")
112
(kind, name) = line.split(" ", 1)
374
115
if name.startswith("pack-") and name.endswith(".pack"):
376
size = self.pack_transport.stat(name).st_size
377
except TransportNotPossible:
378
# FIXME: This reads the whole pack file at once
379
f = self.pack_transport.get(name)
381
pd = PackData(name, StringIO(contents), size=len(contents))
383
pd = PackData(name, self.pack_transport.get(name),
116
pd = PackData(name, self.pack_transport.get(name))
385
117
idxname = name.replace(".pack", ".idx")
386
118
idx = load_pack_index_file(idxname, self.pack_transport.get(idxname))
387
pack = Pack.from_objects(pd, idx)
119
ret.append(Pack.from_objects(pd, idx))
391
122
def _iter_loose_objects(self):
415
142
:param obj: Object to add
417
144
(dir, file) = self._split_loose_object(obj.id)
419
self.transport.mkdir(dir)
145
self.transport.mkdir(dir)
422
146
path = "%s/%s" % (dir, file)
423
147
if self.transport.has(path):
424
148
return # Already there, no need to write again
425
149
self.transport.put_bytes(path, obj.as_legacy_object())
427
def move_in_pack(self, f):
428
"""Move a specific file containing a pack into the pack directory.
430
:note: The file should be on the same file system as the
433
:param path: Path to the pack file.
436
p = PackData(None, f, len(f.getvalue()))
437
entries = p.sorted_entries()
438
basename = "pack-%s" % iter_sha1(entry[0] for entry in entries)
440
self.pack_transport.put_file(basename + ".pack", f)
441
idxfile = self.pack_transport.open_write_stream(basename + ".idx")
443
write_pack_index_v2(idxfile, entries, p.get_stored_checksum())
446
idxfile = self.pack_transport.get(basename + ".idx")
447
idx = load_pack_index_file(basename+".idx", idxfile)
448
final_pack = Pack.from_objects(p, idx)
449
self._add_known_pack(final_pack)
452
def add_thin_pack(self):
453
"""Add a new thin pack to this object store.
455
Thin packs are packs that contain deltas with parents that exist
458
from cStringIO import StringIO
461
if len(f.getvalue()) > 0:
462
return self.move_in_thin_pack(f)
467
def move_in_thin_pack(self, f):
468
"""Move a specific file containing a pack into the pack directory.
470
:note: The file should be on the same file system as the
473
:param path: Path to the pack file.
476
data = ThinPackData.from_file(self.get_raw, f, len(f.getvalue()))
477
idx = MemoryPackIndex(data.sorted_entries(), data.get_stored_checksum())
478
p = Pack.from_objects(data, idx)
480
pack_sha = idx.objects_sha1()
482
datafile = self.pack_transport.open_write_stream("pack-%s.pack" % pack_sha)
484
entries, data_sum = write_pack_data(datafile, ((o, None) for o in p.iterobjects()), len(p))
488
idxfile = self.pack_transport.open_write_stream("pack-%s.idx" % pack_sha)
490
write_pack_index_v2(idxfile, data.sorted_entries(), data_sum)
493
final_pack = Pack("pack-%s" % pack_sha)
494
self._add_known_pack(final_pack)
499
151
def add_pack(self):
500
152
"""Add a new pack to this object store.
502
154
:return: Fileobject to write to and a commit function to
503
155
call when the pack is finished.
505
from cStringIO import StringIO
508
if len(f.getvalue()) > 0:
509
return self.move_in_pack(f)
515
def init(cls, transport):
516
transport.mkdir('info')
517
transport.mkdir(PACKDIR)
518
return cls(transport)
157
raise NotImplementedError(self.add_pack)