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 (
 
42
 
from dulwich.repo import (
 
50
 
    read_packed_refs_with_peeled,
 
55
 
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
 
class TransportRepo(BaseRepo):
 
282
 
    def __init__(self, transport):
 
283
 
        self.transport = transport
 
285
 
            if self.transport.has(".git/%s" % OBJECTDIR):
 
287
 
                self._controltransport = self.transport.clone('.git')
 
288
 
            elif self.transport.has_any(["info/refs", OBJECTDIR, REFSDIR]):
 
290
 
                self._controltransport = self.transport
 
292
 
                raise NotGitRepository(self.transport)
 
294
 
            raise NotGitRepository(self.transport)
 
295
 
        object_store = TransportObjectStore(
 
296
 
            self._controltransport.clone(OBJECTDIR))
 
297
 
        super(TransportRepo, self).__init__(object_store, 
 
298
 
                TransportRefsContainer(self._controltransport))
 
300
 
    def get_named_file(self, path):
 
301
 
        """Get a file from the control dir with a specific name.
 
303
 
        Although the filename should be interpreted as a filename relative to
 
304
 
        the control dir in a disk-baked Repo, the object returned need not be
 
305
 
        pointing to a file in that location.
 
307
 
        :param path: The path to the file, relative to the control dir.
 
308
 
        :return: An open file object, or None if the file does not exist.
 
311
 
            return self._controltransport.get(path.lstrip('/'))
 
315
 
    def index_path(self):
 
316
 
        """Return the path to the index file."""
 
317
 
        return self._controltransport.local_abspath(INDEX_FILENAME)
 
319
 
    def open_index(self):
 
320
 
        """Open the index for this repository."""
 
321
 
        from dulwich.index import Index
 
322
 
        if not self.has_index():
 
323
 
            raise NoIndexPresent()
 
324
 
        return Index(self.index_path())
 
327
 
        """Check if an index is present."""
 
328
 
        # Bare repos must never have index files; non-bare repos may have a
 
329
 
        # missing index file, which is treated as empty.
 
333
 
        return "<%s for %r>" % (self.__class__.__name__, self.transport)
 
336
 
class TransportObjectStore(PackBasedObjectStore):
 
337
 
    """Git-style object store that exists on disk."""
 
339
 
    def __init__(self, transport):
 
340
 
        """Open an object store.
 
342
 
        :param transport: Transport to open data from
 
344
 
        super(TransportObjectStore, self).__init__()
 
345
 
        self.transport = transport
 
346
 
        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
 
    def _load_packs(self):
 
373
 
        for name in self._pack_names():
 
374
 
            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),
 
385
 
                idxname = name.replace(".pack", ".idx")
 
386
 
                idx = load_pack_index_file(idxname, self.pack_transport.get(idxname))
 
387
 
                pack = Pack.from_objects(pd, idx)
 
391
 
    def _iter_loose_objects(self):
 
392
 
        for base in self.transport.list_dir('.'):
 
395
 
            for rest in self.transport.list_dir(base):
 
398
 
    def _split_loose_object(self, sha):
 
399
 
        return (sha[:2], sha[2:])
 
401
 
    def _remove_loose_object(self, sha):
 
402
 
        path = '%s/%s' % self._split_loose_object(sha)
 
403
 
        self.transport.delete(path)
 
405
 
    def _get_loose_object(self, sha):
 
406
 
        path = '%s/%s' % self._split_loose_object(sha)
 
408
 
            return ShaFile.from_file(self.transport.get(path))
 
412
 
    def add_object(self, obj):
 
413
 
        """Add a single object to this object store.
 
415
 
        :param obj: Object to add
 
417
 
        (dir, file) = self._split_loose_object(obj.id)
 
419
 
            self.transport.mkdir(dir)
 
422
 
        path = "%s/%s" % (dir, file)
 
423
 
        if self.transport.has(path):
 
424
 
            return # Already there, no need to write again
 
425
 
        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)
 
500
 
        """Add a new pack to this object store. 
 
502
 
        :return: Fileobject to write to and a commit function to 
 
503
 
            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)