31
32
from dulwich.pack import (
34
36
load_pack_index_file,
36
39
from dulwich.repo import (
47
read_packed_refs_with_peeled,
43
52
from bzrlib.errors import (
59
class TransportRefsContainer(RefsContainer):
60
"""Refs container that reads refs from a transport."""
62
def __init__(self, transport):
63
self.transport = transport
64
self._packed_refs = None
65
self._peeled_refs = None
68
return "%s(%r)" % (self.__class__.__name__, self.transport)
70
def _ensure_dir_exists(self, path):
71
for n in range(path.count("/")):
72
dirname = "/".join(path.split("/")[:n+1])
74
self.transport.mkdir(dirname)
78
def subkeys(self, base):
81
iter_files = self.transport.clone(base).iter_files_recursive()
82
keys.update(("%s/%s" % (base, refname)).strip("/") for
83
refname in iter_files if check_ref_format("%s/%s" % (base, refname)))
84
except (TransportNotPossible, NoSuchFile):
86
for key in self.get_packed_refs():
87
if key.startswith(base):
88
keys.add(key[len(base):].strip("/"))
93
if self.transport.has("HEAD"):
96
iter_files = list(self.transport.clone("refs").iter_files_recursive())
97
for filename in iter_files:
98
refname = "refs/%s" % filename
99
if check_ref_format(refname):
101
except (TransportNotPossible, NoSuchFile):
103
keys.update(self.get_packed_refs())
106
def get_packed_refs(self):
107
"""Get contents of the packed-refs file.
109
:return: Dictionary mapping ref names to SHA1s
111
:note: Will return an empty dictionary when no packed-refs file is
114
# TODO: invalidate the cache on repacking
115
if self._packed_refs is None:
116
# set both to empty because we want _peeled_refs to be
117
# None if and only if _packed_refs is also None.
118
self._packed_refs = {}
119
self._peeled_refs = {}
121
f = self.transport.get("packed-refs")
125
first_line = iter(f).next().rstrip()
126
if (first_line.startswith("# pack-refs") and " peeled" in
128
for sha, name, peeled in read_packed_refs_with_peeled(f):
129
self._packed_refs[name] = sha
131
self._peeled_refs[name] = peeled
134
for sha, name in read_packed_refs(f):
135
self._packed_refs[name] = sha
138
return self._packed_refs
140
def get_peeled(self, name):
141
"""Return the cached peeled value of a ref, if available.
143
:param name: Name of the ref to peel
144
:return: The peeled value of the ref. If the ref is known not point to a
145
tag, this will be the SHA the ref refers to. If the ref may point to
146
a tag, but no cached information is available, None is returned.
148
self.get_packed_refs()
149
if self._peeled_refs is None or name not in self._packed_refs:
150
# No cache: no peeled refs were read, or this ref is loose
152
if name in self._peeled_refs:
153
return self._peeled_refs[name]
158
def read_loose_ref(self, name):
159
"""Read a reference file and return its contents.
161
If the reference file a symbolic reference, only read the first line of
162
the file. Otherwise, only read the first 40 bytes.
164
:param name: the refname to read, relative to refpath
165
:return: The contents of the ref file, or None if the file does not
167
:raises IOError: if any other error occurs
170
f = self.transport.get(name)
174
header = f.read(len(SYMREF))
176
# Read only the first line
177
return header + iter(f).next().rstrip("\r\n")
179
# Read only the first 40 bytes
180
return header + f.read(40-len(SYMREF))
184
def _remove_packed_ref(self, name):
185
if self._packed_refs is None:
187
# reread cached refs from disk, while holding the lock
189
self._packed_refs = None
190
self.get_packed_refs()
192
if name not in self._packed_refs:
195
del self._packed_refs[name]
196
if name in self._peeled_refs:
197
del self._peeled_refs[name]
198
f = self.transport.open_write_stream("packed-refs")
200
write_packed_refs(f, self._packed_refs, self._peeled_refs)
204
def set_symbolic_ref(self, name, other):
205
"""Make a ref point at another ref.
207
:param name: Name of the ref to set
208
:param other: Name of the ref to point at
210
self._check_refname(name)
211
self._check_refname(other)
212
self._ensure_dir_exists(name)
213
self.transport.put_bytes(name, SYMREF + other + '\n')
215
def set_if_equals(self, name, old_ref, new_ref):
216
"""Set a refname to new_ref only if it currently equals old_ref.
218
This method follows all symbolic references, and can be used to perform
219
an atomic compare-and-swap operation.
221
:param name: The refname to set.
222
:param old_ref: The old sha the refname must refer to, or None to set
224
:param new_ref: The new sha the refname will refer to.
225
:return: True if the set was successful, False otherwise.
228
realname, _ = self._follow(name)
231
self._ensure_dir_exists(realname)
232
self.transport.put_bytes(realname, new_ref+"\n")
235
def add_if_new(self, name, ref):
236
"""Add a new reference only if it does not already exist.
238
This method follows symrefs, and only ensures that the last ref in the
239
chain does not exist.
241
:param name: The refname to set.
242
:param ref: The new sha the refname will refer to.
243
:return: True if the add was successful, False otherwise.
246
realname, contents = self._follow(name)
247
if contents is not None:
251
self._check_refname(realname)
252
self._ensure_dir_exists(realname)
253
self.transport.put_bytes(realname, ref+"\n")
256
def remove_if_equals(self, name, old_ref):
257
"""Remove a refname only if it currently equals old_ref.
259
This method does not follow symbolic references. It can be used to
260
perform an atomic compare-and-delete operation.
262
:param name: The refname to delete.
263
:param old_ref: The old sha the refname must refer to, or None to delete
265
:return: True if the delete was successful, False otherwise.
267
self._check_refname(name)
270
self.transport.delete(name)
273
self._remove_packed_ref(name)
48
277
class TransportRepo(BaseRepo):
50
279
def __init__(self, transport):
51
280
self.transport = transport
53
if self.transport.has(".git/info/refs"):
282
if self.transport.has(".git/%s" % OBJECTDIR):
55
284
self._controltransport = self.transport.clone('.git')
56
elif self.transport.has("info/refs"):
285
elif self.transport.has(OBJECTDIR) or self.transport.has(REFSDIR):
58
287
self._controltransport = self.transport
102
341
super(TransportObjectStore, self).__init__()
103
342
self.transport = transport
104
343
self.pack_transport = self.transport.clone(PACKDIR)
345
def _pack_cache_stale(self):
348
def _pack_names(self):
350
f = self.transport.get('info/packs')
352
return self.pack_transport.list_dir(".")
355
for line in f.readlines():
356
line = line.rstrip("\n")
359
(kind, name) = line.split(" ", 1)
106
365
def _load_packs(self):
108
for line in self.transport.get('info/packs').readlines():
109
line = line.rstrip("\n")
112
(kind, name) = line.split(" ", 1)
367
for name in self._pack_names():
115
368
if name.startswith("pack-") and name.endswith(".pack"):
116
pd = PackData(name, self.pack_transport.get(name))
370
size = self.pack_transport.stat(name).st_size
371
except TransportNotPossible:
372
# FIXME: This reads the whole pack file at once
373
f = self.pack_transport.get(name)
375
pd = PackData(name, StringIO(contents), size=len(contents))
377
pd = PackData(name, self.pack_transport.get(name),
117
379
idxname = name.replace(".pack", ".idx")
118
380
idx = load_pack_index_file(idxname, self.pack_transport.get(idxname))
119
ret.append(Pack.from_objects(pd, idx))
381
pack = Pack.from_objects(pd, idx)
122
385
def _iter_loose_objects(self):
142
409
:param obj: Object to add
144
411
(dir, file) = self._split_loose_object(obj.id)
145
self.transport.mkdir(dir)
413
self.transport.mkdir(dir)
146
416
path = "%s/%s" % (dir, file)
147
417
if self.transport.has(path):
148
418
return # Already there, no need to write again
149
419
self.transport.put_bytes(path, obj.as_legacy_object())
421
def move_in_pack(self, f):
422
"""Move a specific file containing a pack into the pack directory.
424
:note: The file should be on the same file system as the
427
:param path: Path to the pack file.
430
p = PackData(None, f, len(f.getvalue()))
431
entries = p.sorted_entries()
432
basename = "pack-%s" % iter_sha1(entry[0] for entry in entries)
434
self.pack_transport.put_file(basename + ".pack", f)
435
idxfile = self.pack_transport.open_write_stream(basename + ".idx")
437
write_pack_index_v2(idxfile, entries, p.get_stored_checksum())
440
idxfile = self.pack_transport.get(basename + ".idx")
441
idx = load_pack_index_file(basename+".idx", idxfile)
442
final_pack = Pack.from_objects(p, idx)
443
self._add_known_pack(final_pack)
151
446
def add_pack(self):
152
447
"""Add a new pack to this object store.
154
449
:return: Fileobject to write to and a commit function to
155
450
call when the pack is finished.
157
raise NotImplementedError(self.add_pack)
452
from cStringIO import StringIO
455
if len(f.getvalue()) > 0:
456
return self.move_in_pack(f)
462
def init(cls, transport):
463
transport.mkdir('info')
464
transport.mkdir(PACKDIR)
465
return cls(transport)