/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to transportgit.py

Commit initial content.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2010 Jelmer Vernooij <jelmer@samba.org>
2
 
#
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.
7
 
#
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.
12
 
#
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
16
 
 
17
 
"""A Git repository implementation that uses a Bazaar transport."""
18
 
 
19
 
from cStringIO import StringIO
20
 
 
21
 
from dulwich.errors import (
22
 
    NotGitRepository,
23
 
    NoIndexPresent,
24
 
    )
25
 
from dulwich.objects import (
26
 
    ShaFile,
27
 
    )
28
 
from dulwich.object_store import (
29
 
    PackBasedObjectStore,
30
 
    PACKDIR,
31
 
    )
32
 
from dulwich.pack import (
33
 
    PackData,
34
 
    Pack,
35
 
    iter_sha1,
36
 
    load_pack_index_file,
37
 
    write_pack_index_v2,
38
 
    )
39
 
from dulwich.repo import (
40
 
    BaseRepo,
41
 
    RefsContainer,
42
 
    INDEX_FILENAME,
43
 
    OBJECTDIR,
44
 
    REFSDIR,
45
 
    SYMREF,
46
 
    check_ref_format,
47
 
    read_packed_refs_with_peeled,
48
 
    read_packed_refs,
49
 
    write_packed_refs,
50
 
    )
51
 
 
52
 
from bzrlib.errors import (
53
 
    FileExists,
54
 
    NoSuchFile,
55
 
    TransportNotPossible,
56
 
    )
57
 
 
58
 
 
59
 
class TransportRefsContainer(RefsContainer):
60
 
    """Refs container that reads refs from a transport."""
61
 
 
62
 
    def __init__(self, transport):
63
 
        self.transport = transport
64
 
        self._packed_refs = None
65
 
        self._peeled_refs = None
66
 
 
67
 
    def __repr__(self):
68
 
        return "%s(%r)" % (self.__class__.__name__, self.transport)
69
 
 
70
 
    def _ensure_dir_exists(self, path):
71
 
        for n in range(path.count("/")):
72
 
            dirname = "/".join(path.split("/")[:n+1])
73
 
            try:
74
 
                self.transport.mkdir(dirname)
75
 
            except FileExists:
76
 
                pass
77
 
 
78
 
    def subkeys(self, base):
79
 
        keys = set()
80
 
        try:
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):
85
 
            pass
86
 
        for key in self.get_packed_refs():
87
 
            if key.startswith(base):
88
 
                keys.add(key[len(base):].strip("/"))
89
 
        return keys
90
 
 
91
 
    def allkeys(self):
92
 
        keys = set()
93
 
        if self.transport.has("HEAD"):
94
 
            keys.add("HEAD")
95
 
        try:
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):
100
 
                    keys.add(refname)
101
 
        except (TransportNotPossible, NoSuchFile):
102
 
            pass
103
 
        keys.update(self.get_packed_refs())
104
 
        return keys
105
 
 
106
 
    def get_packed_refs(self):
107
 
        """Get contents of the packed-refs file.
108
 
 
109
 
        :return: Dictionary mapping ref names to SHA1s
110
 
 
111
 
        :note: Will return an empty dictionary when no packed-refs file is
112
 
            present.
113
 
        """
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 = {}
120
 
            try:
121
 
                f = self.transport.get("packed-refs")
122
 
            except NoSuchFile:
123
 
                return {}
124
 
            try:
125
 
                first_line = iter(f).next().rstrip()
126
 
                if (first_line.startswith("# pack-refs") and " peeled" in
127
 
                        first_line):
128
 
                    for sha, name, peeled in read_packed_refs_with_peeled(f):
129
 
                        self._packed_refs[name] = sha
130
 
                        if peeled:
131
 
                            self._peeled_refs[name] = peeled
132
 
                else:
133
 
                    f.seek(0)
134
 
                    for sha, name in read_packed_refs(f):
135
 
                        self._packed_refs[name] = sha
136
 
            finally:
137
 
                f.close()
138
 
        return self._packed_refs
139
 
 
140
 
    def get_peeled(self, name):
141
 
        """Return the cached peeled value of a ref, if available.
142
 
 
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.
147
 
        """
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
151
 
            return None
152
 
        if name in self._peeled_refs:
153
 
            return self._peeled_refs[name]
154
 
        else:
155
 
            # Known not peelable
156
 
            return self[name]
157
 
 
158
 
    def read_loose_ref(self, name):
159
 
        """Read a reference file and return its contents.
160
 
 
161
 
        If the reference file a symbolic reference, only read the first line of
162
 
        the file. Otherwise, only read the first 40 bytes.
163
 
 
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
166
 
            exist.
167
 
        :raises IOError: if any other error occurs
168
 
        """
169
 
        try:
170
 
            f = self.transport.get(name)
171
 
        except NoSuchFile:
172
 
            return None
173
 
        try:
174
 
            header = f.read(len(SYMREF))
175
 
            if header == SYMREF:
176
 
                # Read only the first line
177
 
                return header + iter(f).next().rstrip("\r\n")
178
 
            else:
179
 
                # Read only the first 40 bytes
180
 
                return header + f.read(40-len(SYMREF))
181
 
        finally:
182
 
            f.close()
183
 
 
184
 
    def _remove_packed_ref(self, name):
185
 
        if self._packed_refs is None:
186
 
            return
187
 
        # reread cached refs from disk, while holding the lock
188
 
 
189
 
        self._packed_refs = None
190
 
        self.get_packed_refs()
191
 
 
192
 
        if name not in self._packed_refs:
193
 
            return
194
 
 
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")
199
 
        try:
200
 
            write_packed_refs(f, self._packed_refs, self._peeled_refs)
201
 
        finally:
202
 
            f.close()
203
 
 
204
 
    def set_symbolic_ref(self, name, other):
205
 
        """Make a ref point at another ref.
206
 
 
207
 
        :param name: Name of the ref to set
208
 
        :param other: Name of the ref to point at
209
 
        """
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')
214
 
 
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.
217
 
 
218
 
        This method follows all symbolic references, and can be used to perform
219
 
        an atomic compare-and-swap operation.
220
 
 
221
 
        :param name: The refname to set.
222
 
        :param old_ref: The old sha the refname must refer to, or None to set
223
 
            unconditionally.
224
 
        :param new_ref: The new sha the refname will refer to.
225
 
        :return: True if the set was successful, False otherwise.
226
 
        """
227
 
        try:
228
 
            realname, _ = self._follow(name)
229
 
        except KeyError:
230
 
            realname = name
231
 
        self._ensure_dir_exists(realname)
232
 
        self.transport.put_bytes(realname, new_ref+"\n")
233
 
        return True
234
 
 
235
 
    def add_if_new(self, name, ref):
236
 
        """Add a new reference only if it does not already exist.
237
 
 
238
 
        This method follows symrefs, and only ensures that the last ref in the
239
 
        chain does not exist.
240
 
 
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.
244
 
        """
245
 
        try:
246
 
            realname, contents = self._follow(name)
247
 
            if contents is not None:
248
 
                return False
249
 
        except KeyError:
250
 
            realname = name
251
 
        self._check_refname(realname)
252
 
        self._ensure_dir_exists(realname)
253
 
        self.transport.put_bytes(realname, ref+"\n")
254
 
        return True
255
 
 
256
 
    def remove_if_equals(self, name, old_ref):
257
 
        """Remove a refname only if it currently equals old_ref.
258
 
 
259
 
        This method does not follow symbolic references. It can be used to
260
 
        perform an atomic compare-and-delete operation.
261
 
 
262
 
        :param name: The refname to delete.
263
 
        :param old_ref: The old sha the refname must refer to, or None to delete
264
 
            unconditionally.
265
 
        :return: True if the delete was successful, False otherwise.
266
 
        """
267
 
        self._check_refname(name)
268
 
        # may only be packed
269
 
        try:
270
 
            self.transport.delete(name)
271
 
        except NoSuchFile:
272
 
            pass
273
 
        self._remove_packed_ref(name)
274
 
        return True
275
 
 
276
 
 
277
 
class TransportRepo(BaseRepo):
278
 
 
279
 
    def __init__(self, transport):
280
 
        self.transport = transport
281
 
        try:
282
 
            if self.transport.has(".git/%s" % OBJECTDIR):
283
 
                self.bare = False
284
 
                self._controltransport = self.transport.clone('.git')
285
 
            elif self.transport.has(OBJECTDIR) or self.transport.has(REFSDIR):
286
 
                self.bare = True
287
 
                self._controltransport = self.transport
288
 
            else:
289
 
                raise NotGitRepository(self.transport)
290
 
        except NoSuchFile:
291
 
            raise NotGitRepository(self.transport)
292
 
        object_store = TransportObjectStore(
293
 
            self._controltransport.clone(OBJECTDIR))
294
 
        super(TransportRepo, self).__init__(object_store, 
295
 
                TransportRefsContainer(self._controltransport))
296
 
 
297
 
    def get_named_file(self, path):
298
 
        """Get a file from the control dir with a specific name.
299
 
 
300
 
        Although the filename should be interpreted as a filename relative to
301
 
        the control dir in a disk-baked Repo, the object returned need not be
302
 
        pointing to a file in that location.
303
 
 
304
 
        :param path: The path to the file, relative to the control dir.
305
 
        :return: An open file object, or None if the file does not exist.
306
 
        """
307
 
        try:
308
 
            return self._controltransport.get(path.lstrip('/'))
309
 
        except NoSuchFile:
310
 
            return None
311
 
 
312
 
    def index_path(self):
313
 
        """Return the path to the index file."""
314
 
        return self._controltransport.local_abspath(INDEX_FILENAME)
315
 
 
316
 
    def open_index(self):
317
 
        """Open the index for this repository."""
318
 
        from dulwich.index import Index
319
 
        if not self.has_index():
320
 
            raise NoIndexPresent()
321
 
        return Index(self.index_path())
322
 
 
323
 
    def has_index(self):
324
 
        """Check if an index is present."""
325
 
        # Bare repos must never have index files; non-bare repos may have a
326
 
        # missing index file, which is treated as empty.
327
 
        return not self.bare
328
 
 
329
 
    def __repr__(self):
330
 
        return "<TransportRepo for %r>" % self.transport
331
 
 
332
 
 
333
 
class TransportObjectStore(PackBasedObjectStore):
334
 
    """Git-style object store that exists on disk."""
335
 
 
336
 
    def __init__(self, transport):
337
 
        """Open an object store.
338
 
 
339
 
        :param transport: Transport to open data from
340
 
        """
341
 
        super(TransportObjectStore, self).__init__()
342
 
        self.transport = transport
343
 
        self.pack_transport = self.transport.clone(PACKDIR)
344
 
    
345
 
    def _pack_cache_stale(self):
346
 
        return False # FIXME
347
 
 
348
 
    def _pack_names(self):
349
 
        try:
350
 
            f = self.transport.get('info/packs')
351
 
        except NoSuchFile:
352
 
            return self.pack_transport.list_dir(".")
353
 
        else:
354
 
            ret = []
355
 
            for line in f.readlines():
356
 
                line = line.rstrip("\n")
357
 
                if not line:
358
 
                    continue
359
 
                (kind, name) = line.split(" ", 1)
360
 
                if kind != "P":
361
 
                    continue
362
 
                ret.append(name)
363
 
            return ret
364
 
 
365
 
    def _load_packs(self):
366
 
        ret = []
367
 
        for name in self._pack_names():
368
 
            if name.startswith("pack-") and name.endswith(".pack"):
369
 
                try:
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)
374
 
                    contents = f.read()
375
 
                    pd = PackData(name, StringIO(contents), size=len(contents))
376
 
                else:
377
 
                    pd = PackData(name, self.pack_transport.get(name),
378
 
                            size=size)
379
 
                idxname = name.replace(".pack", ".idx")
380
 
                idx = load_pack_index_file(idxname, self.pack_transport.get(idxname))
381
 
                pack = Pack.from_objects(pd, idx)
382
 
                ret.append(pack)
383
 
        return ret
384
 
 
385
 
    def _iter_loose_objects(self):
386
 
        for base in self.transport.list_dir('.'):
387
 
            if len(base) != 2:
388
 
                continue
389
 
            for rest in self.transport.list_dir(base):
390
 
                yield base+rest
391
 
 
392
 
    def _split_loose_object(self, sha):
393
 
        return (sha[:2], sha[2:])
394
 
 
395
 
    def _remove_loose_object(self, sha):
396
 
        path = '%s/%s' % self._split_loose_object(sha)
397
 
        self.transport.delete(path)
398
 
 
399
 
    def _get_loose_object(self, sha):
400
 
        path = '%s/%s' % self._split_loose_object(sha)
401
 
        try:
402
 
            return ShaFile.from_file(self.transport.get(path))
403
 
        except NoSuchFile:
404
 
            return None
405
 
 
406
 
    def add_object(self, obj):
407
 
        """Add a single object to this object store.
408
 
 
409
 
        :param obj: Object to add
410
 
        """
411
 
        (dir, file) = self._split_loose_object(obj.id)
412
 
        try:
413
 
            self.transport.mkdir(dir)
414
 
        except FileExists:
415
 
            pass
416
 
        path = "%s/%s" % (dir, file)
417
 
        if self.transport.has(path):
418
 
            return # Already there, no need to write again
419
 
        self.transport.put_bytes(path, obj.as_legacy_object())
420
 
 
421
 
    def move_in_pack(self, f):
422
 
        """Move a specific file containing a pack into the pack directory.
423
 
 
424
 
        :note: The file should be on the same file system as the
425
 
            packs directory.
426
 
 
427
 
        :param path: Path to the pack file.
428
 
        """
429
 
        f.seek(0)
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)
433
 
        f.seek(0)
434
 
        self.pack_transport.put_file(basename + ".pack", f)
435
 
        idxfile = self.pack_transport.open_write_stream(basename + ".idx")
436
 
        try:
437
 
            write_pack_index_v2(idxfile, entries, p.get_stored_checksum())
438
 
        finally:
439
 
            idxfile.close()
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)
444
 
        return final_pack
445
 
 
446
 
    def add_pack(self):
447
 
        """Add a new pack to this object store. 
448
 
 
449
 
        :return: Fileobject to write to and a commit function to 
450
 
            call when the pack is finished.
451
 
        """
452
 
        from cStringIO import StringIO
453
 
        f = StringIO()
454
 
        def commit():
455
 
            if len(f.getvalue()) > 0:
456
 
                return self.move_in_pack(f)
457
 
            else:
458
 
                return None
459
 
        return f, commit
460
 
 
461
 
    @classmethod
462
 
    def init(cls, transport):
463
 
        transport.mkdir('info')
464
 
        transport.mkdir(PACKDIR)
465
 
        return cls(transport)