1
# Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""A Git repository implementation that uses a Bazaar transport."""
19
from cStringIO import StringIO
21
from dulwich.errors import (
25
from dulwich.objects import (
28
from dulwich.object_store import (
32
from dulwich.pack import (
39
from dulwich.repo import (
47
read_packed_refs_with_peeled,
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 subkeys(self, base):
72
path = self.refpath(base)
74
iter_files = self.transport.clone(base).iter_files_recursive()
75
except TransportNotPossible:
78
for refname in iter_files:
79
# check_ref_format requires at least one /, so we prepend the
80
# base before calling it.
81
if check_ref_format("%s/%s" % (base, refname)):
82
keys.add(("%s/%s" % refname).strip("/"))
83
for key in self.get_packed_refs():
84
if key.startswith(base):
85
keys.add(key[len(base):].strip("/"))
90
if self.transport.has("HEAD"):
94
iter_files = self.transport.clone("refs").iter_files_recursive()
95
except TransportNotPossible:
98
for refname in iter_files:
99
if check_ref_format(refname):
101
keys.update(self.get_packed_refs())
104
def get_packed_refs(self):
105
"""Get contents of the packed-refs file.
107
:return: Dictionary mapping ref names to SHA1s
109
:note: Will return an empty dictionary when no packed-refs file is
112
# TODO: invalidate the cache on repacking
113
if self._packed_refs is None:
114
# set both to empty because we want _peeled_refs to be
115
# None if and only if _packed_refs is also None.
116
self._packed_refs = {}
117
self._peeled_refs = {}
119
f = self.transport.get("packed-refs")
123
first_line = iter(f).next().rstrip()
124
if (first_line.startswith("# pack-refs") and " peeled" in
126
for sha, name, peeled in read_packed_refs_with_peeled(f):
127
self._packed_refs[name] = sha
129
self._peeled_refs[name] = peeled
132
for sha, name in read_packed_refs(f):
133
self._packed_refs[name] = sha
136
return self._packed_refs
138
def get_peeled(self, name):
139
"""Return the cached peeled value of a ref, if available.
141
:param name: Name of the ref to peel
142
:return: The peeled value of the ref. If the ref is known not point to a
143
tag, this will be the SHA the ref refers to. If the ref may point to
144
a tag, but no cached information is available, None is returned.
146
self.get_packed_refs()
147
if self._peeled_refs is None or name not in self._packed_refs:
148
# No cache: no peeled refs were read, or this ref is loose
150
if name in self._peeled_refs:
151
return self._peeled_refs[name]
156
def read_loose_ref(self, name):
157
"""Read a reference file and return its contents.
159
If the reference file a symbolic reference, only read the first line of
160
the file. Otherwise, only read the first 40 bytes.
162
:param name: the refname to read, relative to refpath
163
:return: The contents of the ref file, or None if the file does not
165
:raises IOError: if any other error occurs
168
f = self.transport.get(name)
172
header = f.read(len(SYMREF))
174
# Read only the first line
175
return header + iter(f).next().rstrip("\r\n")
177
# Read only the first 40 bytes
178
return header + f.read(40-len(SYMREF))
182
def _remove_packed_ref(self, name):
183
if self._packed_refs is None:
185
# reread cached refs from disk, while holding the lock
187
self._packed_refs = None
188
self.get_packed_refs()
190
if name not in self._packed_refs:
193
del self._packed_refs[name]
194
if name in self._peeled_refs:
195
del self._peeled_refs[name]
197
write_packed_refs(f, self._packed_refs, self._peeled_refs)
199
self.transport.put_file("packed-refs", f)
201
def set_symbolic_ref(self, name, other):
202
"""Make a ref point at another ref.
204
:param name: Name of the ref to set
205
:param other: Name of the ref to point at
207
self._check_refname(name)
208
self._check_refname(other)
209
self.transport.put_bytes(name, SYMREF + other + '\n')
211
def set_if_equals(self, name, old_ref, new_ref):
212
"""Set a refname to new_ref only if it currently equals old_ref.
214
This method follows all symbolic references, and can be used to perform
215
an atomic compare-and-swap operation.
217
:param name: The refname to set.
218
:param old_ref: The old sha the refname must refer to, or None to set
220
:param new_ref: The new sha the refname will refer to.
221
:return: True if the set was successful, False otherwise.
224
realname, _ = self._follow(name)
227
self.transport.put_bytes_non_atomic(realname, new_ref+"\n",
228
create_parent_dir=True)
231
def add_if_new(self, name, ref):
232
"""Add a new reference only if it does not already exist.
234
This method follows symrefs, and only ensures that the last ref in the
235
chain does not exist.
237
:param name: The refname to set.
238
:param ref: The new sha the refname will refer to.
239
:return: True if the add was successful, False otherwise.
242
realname, contents = self._follow(name)
243
if contents is not None:
247
self._check_refname(realname)
248
self.transport.put_bytes_non_atomic(realname, ref+"\n",
249
create_parent_dir=True)
252
def remove_if_equals(self, name, old_ref):
253
"""Remove a refname only if it currently equals old_ref.
255
This method does not follow symbolic references. It can be used to
256
perform an atomic compare-and-delete operation.
258
:param name: The refname to delete.
259
:param old_ref: The old sha the refname must refer to, or None to delete
261
:return: True if the delete was successful, False otherwise.
263
self._check_refname(name)
266
self.transport.remove(name)
269
self._remove_packed_ref(name)
273
class TransportRepo(BaseRepo):
275
def __init__(self, transport):
276
self.transport = transport
278
if self.transport.has(".git/%s" % OBJECTDIR):
280
self._controltransport = self.transport.clone('.git')
281
elif self.transport.has(OBJECTDIR) or self.transport.has(REFSDIR):
283
self._controltransport = self.transport
285
raise NotGitRepository(self.transport)
287
raise NotGitRepository(self.transport)
288
object_store = TransportObjectStore(
289
self._controltransport.clone(OBJECTDIR))
290
super(TransportRepo, self).__init__(object_store,
291
TransportRefsContainer(self._controltransport))
293
def get_named_file(self, path):
294
"""Get a file from the control dir with a specific name.
296
Although the filename should be interpreted as a filename relative to
297
the control dir in a disk-baked Repo, the object returned need not be
298
pointing to a file in that location.
300
:param path: The path to the file, relative to the control dir.
301
:return: An open file object, or None if the file does not exist.
304
return self._controltransport.get(path.lstrip('/'))
308
def index_path(self):
309
"""Return the path to the index file."""
310
return self._controltransport.local_abspath(INDEX_FILENAME)
312
def open_index(self):
313
"""Open the index for this repository."""
314
from dulwich.index import Index
315
if not self.has_index():
316
raise NoIndexPresent()
317
return Index(self.index_path())
320
return self._controltransport.has(INDEX_FILENAME)
323
return "<TransportRepo for %r>" % self.transport
326
class TransportObjectStore(PackBasedObjectStore):
327
"""Git-style object store that exists on disk."""
329
def __init__(self, transport):
330
"""Open an object store.
332
:param transport: Transport to open data from
334
super(TransportObjectStore, self).__init__()
335
self.transport = transport
336
self.pack_transport = self.transport.clone(PACKDIR)
338
def _pack_cache_stale(self):
341
def _pack_names(self):
343
f = self.transport.get('info/packs')
345
return self.pack_transport.list_dir(".")
348
for line in f.readlines():
349
line = line.rstrip("\n")
352
(kind, name) = line.split(" ", 1)
358
def _load_packs(self):
360
for name in self._pack_names():
361
if name.startswith("pack-") and name.endswith(".pack"):
363
size = self.pack_transport.stat(name).st_size
364
except TransportNotPossible:
366
# FIXME: This reads the whole pack file at once
367
f = self.pack_transport.get(name)
369
return PackData(name, StringIO(contents), size=len(contents))
371
pd = lambda: PackData(name, self.pack_transport.get(name),
373
idxname = name.replace(".pack", ".idx")
374
idx = lambda: load_pack_index_file(idxname, self.pack_transport.get(idxname))
375
pack = Pack.from_lazy_objects(pd, idx)
379
def _iter_loose_objects(self):
380
for base in self.transport.list_dir('.'):
383
for rest in self.transport.list_dir(base):
386
def _split_loose_object(self, sha):
387
return (sha[:2], sha[2:])
389
def _remove_loose_object(self, sha):
390
path = '%s/%s' % self._split_loose_object(sha)
391
self.transport.remove(path)
393
def _get_loose_object(self, sha):
394
path = '%s/%s' % self._split_loose_object(sha)
396
return ShaFile.from_file(self.transport.get(path))
400
def add_object(self, obj):
401
"""Add a single object to this object store.
403
:param obj: Object to add
405
(dir, file) = self._split_loose_object(obj.id)
407
self.transport.mkdir(dir)
410
path = "%s/%s" % (dir, file)
411
if self.transport.has(path):
412
return # Already there, no need to write again
413
self.transport.put_bytes(path, obj.as_legacy_object())
415
def move_in_pack(self, f):
416
"""Move a specific file containing a pack into the pack directory.
418
:note: The file should be on the same file system as the
421
:param path: Path to the pack file.
424
p = PackData(None, f, len(f.getvalue()))
425
entries = p.sorted_entries()
426
basename = "pack-%s" % iter_sha1(entry[0] for entry in entries)
428
self.pack_transport.put_file(basename + ".pack", f)
430
write_pack_index_v2(idxfile, entries, p.get_stored_checksum())
432
self.pack_transport.put_file(basename + ".idx", idxfile)
434
idx = load_pack_index_file(basename+".idx", idxfile)
435
final_pack = Pack.from_objects(p, idx)
436
self._add_known_pack(final_pack)
440
"""Add a new pack to this object store.
442
:return: Fileobject to write to and a commit function to
443
call when the pack is finished.
445
from cStringIO import StringIO
448
if len(f.getvalue()) > 0:
449
return self.move_in_pack(f)
455
def init(cls, transport):
456
transport.mkdir('info')
457
transport.mkdir(PACKDIR)
458
return cls(transport)