/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

TranslateĀ moreĀ strings.

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
import os
 
22
 
 
23
from dulwich.errors import (
 
24
    NotGitRepository,
 
25
    NoIndexPresent,
 
26
    )
 
27
from dulwich.objects import (
 
28
    ShaFile,
 
29
    )
 
30
from dulwich.object_store import (
 
31
    PackBasedObjectStore,
 
32
    PACKDIR,
 
33
    )
 
34
from dulwich.pack import (
 
35
    MemoryPackIndex,
 
36
    PackData,
 
37
    Pack,
 
38
    iter_sha1,
 
39
    load_pack_index_file,
 
40
    write_pack_data,
 
41
    write_pack_index_v2,
 
42
    )
 
43
from dulwich.repo import (
 
44
    BaseRepo,
 
45
    RefsContainer,
 
46
    BASE_DIRECTORIES,
 
47
    INDEX_FILENAME,
 
48
    OBJECTDIR,
 
49
    REFSDIR,
 
50
    SYMREF,
 
51
    check_ref_format,
 
52
    read_packed_refs_with_peeled,
 
53
    read_packed_refs,
 
54
    write_packed_refs,
 
55
    )
 
56
 
 
57
from bzrlib import (
 
58
    transport as _mod_transport,
 
59
    )
 
60
from bzrlib.errors import (
 
61
    FileExists,
 
62
    NoSuchFile,
 
63
    TransportNotPossible,
 
64
    )
 
65
 
 
66
 
 
67
class TransportRefsContainer(RefsContainer):
 
68
    """Refs container that reads refs from a transport."""
 
69
 
 
70
    def __init__(self, transport):
 
71
        self.transport = transport
 
72
        self._packed_refs = None
 
73
        self._peeled_refs = None
 
74
 
 
75
    def __repr__(self):
 
76
        return "%s(%r)" % (self.__class__.__name__, self.transport)
 
77
 
 
78
    def _ensure_dir_exists(self, path):
 
79
        for n in range(path.count("/")):
 
80
            dirname = "/".join(path.split("/")[:n+1])
 
81
            try:
 
82
                self.transport.mkdir(dirname)
 
83
            except FileExists:
 
84
                pass
 
85
 
 
86
    def subkeys(self, base):
 
87
        keys = set()
 
88
        try:
 
89
            iter_files = self.transport.clone(base).iter_files_recursive()
 
90
            keys.update(("%s/%s" % (base, refname)).strip("/") for 
 
91
                    refname in iter_files if check_ref_format("%s/%s" % (base, refname)))
 
92
        except (TransportNotPossible, NoSuchFile):
 
93
            pass
 
94
        for key in self.get_packed_refs():
 
95
            if key.startswith(base):
 
96
                keys.add(key[len(base):].strip("/"))
 
97
        return keys
 
98
 
 
99
    def allkeys(self):
 
100
        keys = set()
 
101
        try:
 
102
            self.transport.get_bytes("HEAD")
 
103
        except NoSuchFile:
 
104
            pass
 
105
        else:
 
106
            keys.add("HEAD")
 
107
        try:
 
108
            iter_files = list(self.transport.clone("refs").iter_files_recursive())
 
109
            for filename in iter_files:
 
110
                refname = "refs/%s" % filename
 
111
                if check_ref_format(refname):
 
112
                    keys.add(refname)
 
113
        except (TransportNotPossible, NoSuchFile):
 
114
            pass
 
115
        keys.update(self.get_packed_refs())
 
116
        return keys
 
117
 
 
118
    def get_packed_refs(self):
 
119
        """Get contents of the packed-refs file.
 
120
 
 
121
        :return: Dictionary mapping ref names to SHA1s
 
122
 
 
123
        :note: Will return an empty dictionary when no packed-refs file is
 
124
            present.
 
125
        """
 
126
        # TODO: invalidate the cache on repacking
 
127
        if self._packed_refs is None:
 
128
            # set both to empty because we want _peeled_refs to be
 
129
            # None if and only if _packed_refs is also None.
 
130
            self._packed_refs = {}
 
131
            self._peeled_refs = {}
 
132
            try:
 
133
                f = self.transport.get("packed-refs")
 
134
            except NoSuchFile:
 
135
                return {}
 
136
            try:
 
137
                first_line = iter(f).next().rstrip()
 
138
                if (first_line.startswith("# pack-refs") and " peeled" in
 
139
                        first_line):
 
140
                    for sha, name, peeled in read_packed_refs_with_peeled(f):
 
141
                        self._packed_refs[name] = sha
 
142
                        if peeled:
 
143
                            self._peeled_refs[name] = peeled
 
144
                else:
 
145
                    f.seek(0)
 
146
                    for sha, name in read_packed_refs(f):
 
147
                        self._packed_refs[name] = sha
 
148
            finally:
 
149
                f.close()
 
150
        return self._packed_refs
 
151
 
 
152
    def get_peeled(self, name):
 
153
        """Return the cached peeled value of a ref, if available.
 
154
 
 
155
        :param name: Name of the ref to peel
 
156
        :return: The peeled value of the ref. If the ref is known not point to a
 
157
            tag, this will be the SHA the ref refers to. If the ref may point to
 
158
            a tag, but no cached information is available, None is returned.
 
159
        """
 
160
        self.get_packed_refs()
 
161
        if self._peeled_refs is None or name not in self._packed_refs:
 
162
            # No cache: no peeled refs were read, or this ref is loose
 
163
            return None
 
164
        if name in self._peeled_refs:
 
165
            return self._peeled_refs[name]
 
166
        else:
 
167
            # Known not peelable
 
168
            return self[name]
 
169
 
 
170
    def read_loose_ref(self, name):
 
171
        """Read a reference file and return its contents.
 
172
 
 
173
        If the reference file a symbolic reference, only read the first line of
 
174
        the file. Otherwise, only read the first 40 bytes.
 
175
 
 
176
        :param name: the refname to read, relative to refpath
 
177
        :return: The contents of the ref file, or None if the file does not
 
178
            exist.
 
179
        :raises IOError: if any other error occurs
 
180
        """
 
181
        try:
 
182
            f = self.transport.get(name)
 
183
        except NoSuchFile:
 
184
            return None
 
185
        try:
 
186
            header = f.read(len(SYMREF))
 
187
            if header == SYMREF:
 
188
                # Read only the first line
 
189
                return header + iter(f).next().rstrip("\r\n")
 
190
            else:
 
191
                # Read only the first 40 bytes
 
192
                return header + f.read(40-len(SYMREF))
 
193
        finally:
 
194
            f.close()
 
195
 
 
196
    def _remove_packed_ref(self, name):
 
197
        if self._packed_refs is None:
 
198
            return
 
199
        # reread cached refs from disk, while holding the lock
 
200
 
 
201
        self._packed_refs = None
 
202
        self.get_packed_refs()
 
203
 
 
204
        if name not in self._packed_refs:
 
205
            return
 
206
 
 
207
        del self._packed_refs[name]
 
208
        if name in self._peeled_refs:
 
209
            del self._peeled_refs[name]
 
210
        f = self.transport.open_write_stream("packed-refs")
 
211
        try:
 
212
            write_packed_refs(f, self._packed_refs, self._peeled_refs)
 
213
        finally:
 
214
            f.close()
 
215
 
 
216
    def set_symbolic_ref(self, name, other):
 
217
        """Make a ref point at another ref.
 
218
 
 
219
        :param name: Name of the ref to set
 
220
        :param other: Name of the ref to point at
 
221
        """
 
222
        self._check_refname(name)
 
223
        self._check_refname(other)
 
224
        self._ensure_dir_exists(name)
 
225
        self.transport.put_bytes(name, SYMREF + other + '\n')
 
226
 
 
227
    def set_if_equals(self, name, old_ref, new_ref):
 
228
        """Set a refname to new_ref only if it currently equals old_ref.
 
229
 
 
230
        This method follows all symbolic references, and can be used to perform
 
231
        an atomic compare-and-swap operation.
 
232
 
 
233
        :param name: The refname to set.
 
234
        :param old_ref: The old sha the refname must refer to, or None to set
 
235
            unconditionally.
 
236
        :param new_ref: The new sha the refname will refer to.
 
237
        :return: True if the set was successful, False otherwise.
 
238
        """
 
239
        try:
 
240
            realname, _ = self._follow(name)
 
241
        except KeyError:
 
242
            realname = name
 
243
        self._ensure_dir_exists(realname)
 
244
        self.transport.put_bytes(realname, new_ref+"\n")
 
245
        return True
 
246
 
 
247
    def add_if_new(self, name, ref):
 
248
        """Add a new reference only if it does not already exist.
 
249
 
 
250
        This method follows symrefs, and only ensures that the last ref in the
 
251
        chain does not exist.
 
252
 
 
253
        :param name: The refname to set.
 
254
        :param ref: The new sha the refname will refer to.
 
255
        :return: True if the add was successful, False otherwise.
 
256
        """
 
257
        try:
 
258
            realname, contents = self._follow(name)
 
259
            if contents is not None:
 
260
                return False
 
261
        except KeyError:
 
262
            realname = name
 
263
        self._check_refname(realname)
 
264
        self._ensure_dir_exists(realname)
 
265
        self.transport.put_bytes(realname, ref+"\n")
 
266
        return True
 
267
 
 
268
    def remove_if_equals(self, name, old_ref):
 
269
        """Remove a refname only if it currently equals old_ref.
 
270
 
 
271
        This method does not follow symbolic references. It can be used to
 
272
        perform an atomic compare-and-delete operation.
 
273
 
 
274
        :param name: The refname to delete.
 
275
        :param old_ref: The old sha the refname must refer to, or None to delete
 
276
            unconditionally.
 
277
        :return: True if the delete was successful, False otherwise.
 
278
        """
 
279
        self._check_refname(name)
 
280
        # may only be packed
 
281
        try:
 
282
            self.transport.delete(name)
 
283
        except NoSuchFile:
 
284
            pass
 
285
        self._remove_packed_ref(name)
 
286
        return True
 
287
 
 
288
    def get(self, name, default=None):
 
289
        try:
 
290
            return self[name]
 
291
        except KeyError:
 
292
            return default
 
293
 
 
294
 
 
295
class TransportRepo(BaseRepo):
 
296
 
 
297
    def __init__(self, transport, bare):
 
298
        self.transport = transport
 
299
        self.bare = bare
 
300
        if self.bare:
 
301
            self._controltransport = self.transport
 
302
        else:
 
303
            self._controltransport = self.transport.clone('.git')
 
304
        object_store = TransportObjectStore(
 
305
            self._controltransport.clone(OBJECTDIR))
 
306
        super(TransportRepo, self).__init__(object_store, 
 
307
                TransportRefsContainer(self._controltransport))
 
308
 
 
309
    def get_named_file(self, path):
 
310
        """Get a file from the control dir with a specific name.
 
311
 
 
312
        Although the filename should be interpreted as a filename relative to
 
313
        the control dir in a disk-baked Repo, the object returned need not be
 
314
        pointing to a file in that location.
 
315
 
 
316
        :param path: The path to the file, relative to the control dir.
 
317
        :return: An open file object, or None if the file does not exist.
 
318
        """
 
319
        try:
 
320
            return self._controltransport.get(path.lstrip('/'))
 
321
        except NoSuchFile:
 
322
            return None
 
323
 
 
324
    def _put_named_file(self, relpath, contents):
 
325
        self._controltransport.put_bytes(relpath, contents)
 
326
 
 
327
    def index_path(self):
 
328
        """Return the path to the index file."""
 
329
        return self._controltransport.local_abspath(INDEX_FILENAME)
 
330
 
 
331
    def open_index(self):
 
332
        """Open the index for this repository."""
 
333
        from dulwich.index import Index
 
334
        if not self.has_index():
 
335
            raise NoIndexPresent()
 
336
        return Index(self.index_path())
 
337
 
 
338
    def has_index(self):
 
339
        """Check if an index is present."""
 
340
        # Bare repos must never have index files; non-bare repos may have a
 
341
        # missing index file, which is treated as empty.
 
342
        return not self.bare
 
343
 
 
344
    def __repr__(self):
 
345
        return "<%s for %r>" % (self.__class__.__name__, self.transport)
 
346
 
 
347
    @classmethod
 
348
    def init(cls, transport, bare=False):
 
349
        if not bare:
 
350
            transport.mkdir(".git")
 
351
            control_transport = transport.clone(".git")
 
352
        else:
 
353
            control_transport = transport
 
354
        for d in BASE_DIRECTORIES:
 
355
            control_transport.mkdir("/".join(d))
 
356
        control_transport.mkdir(OBJECTDIR)
 
357
        TransportObjectStore.init(control_transport.clone(OBJECTDIR))
 
358
        ret = cls(transport, bare)
 
359
        ret.refs.set_symbolic_ref("HEAD", "refs/heads/master")
 
360
        ret._init_files(bare)
 
361
        return ret
 
362
 
 
363
 
 
364
class TransportObjectStore(PackBasedObjectStore):
 
365
    """Git-style object store that exists on disk."""
 
366
 
 
367
    def __init__(self, transport):
 
368
        """Open an object store.
 
369
 
 
370
        :param transport: Transport to open data from
 
371
        """
 
372
        super(TransportObjectStore, self).__init__()
 
373
        self.transport = transport
 
374
        self.pack_transport = self.transport.clone(PACKDIR)
 
375
        self._alternates = None
 
376
 
 
377
    def __repr__(self):
 
378
        return "%s(%r)" % (self.__class__.__name__, self.transport)
 
379
 
 
380
    def _pack_cache_stale(self):
 
381
        return False # FIXME
 
382
 
 
383
    @property
 
384
    def alternates(self):
 
385
        if self._alternates is not None:
 
386
            return self._alternates
 
387
        self._alternates = []
 
388
        for path in self._read_alternate_paths():
 
389
            # FIXME: Check path
 
390
            t = _mod_transport.get_transport_from_path(path)
 
391
            self._alternates.append(self.__class__(t))
 
392
        return self._alternates
 
393
 
 
394
    def _read_alternate_paths(self):
 
395
        try:
 
396
            f = self.transport.get("info/alternates")
 
397
        except NoSuchFile:
 
398
            return []
 
399
        ret = []
 
400
        try:
 
401
            for l in f.readlines():
 
402
                l = l.rstrip("\n")
 
403
                if l[0] == "#":
 
404
                    continue
 
405
                if os.path.isabs(l):
 
406
                    continue
 
407
                ret.append(l)
 
408
            return ret
 
409
        finally:
 
410
            f.close()
 
411
 
 
412
    def _pack_names(self):
 
413
        try:
 
414
            f = self.transport.get('info/packs')
 
415
        except NoSuchFile:
 
416
            return self.pack_transport.list_dir(".")
 
417
        else:
 
418
            ret = []
 
419
            for line in f.readlines():
 
420
                line = line.rstrip("\n")
 
421
                if not line:
 
422
                    continue
 
423
                (kind, name) = line.split(" ", 1)
 
424
                if kind != "P":
 
425
                    continue
 
426
                ret.append(name)
 
427
            return ret
 
428
 
 
429
    def _load_packs(self):
 
430
        ret = []
 
431
        for name in self._pack_names():
 
432
            if name.startswith("pack-") and name.endswith(".pack"):
 
433
                try:
 
434
                    size = self.pack_transport.stat(name).st_size
 
435
                except TransportNotPossible:
 
436
                    # FIXME: This reads the whole pack file at once
 
437
                    f = self.pack_transport.get(name)
 
438
                    contents = f.read()
 
439
                    pd = PackData(name, StringIO(contents), size=len(contents))
 
440
                else:
 
441
                    pd = PackData(name, self.pack_transport.get(name),
 
442
                            size=size)
 
443
                idxname = name.replace(".pack", ".idx")
 
444
                idx = load_pack_index_file(idxname, self.pack_transport.get(idxname))
 
445
                pack = Pack.from_objects(pd, idx)
 
446
                ret.append(pack)
 
447
        return ret
 
448
 
 
449
    def _iter_loose_objects(self):
 
450
        for base in self.transport.list_dir('.'):
 
451
            if len(base) != 2:
 
452
                continue
 
453
            for rest in self.transport.list_dir(base):
 
454
                yield base+rest
 
455
 
 
456
    def _split_loose_object(self, sha):
 
457
        return (sha[:2], sha[2:])
 
458
 
 
459
    def _remove_loose_object(self, sha):
 
460
        path = '%s/%s' % self._split_loose_object(sha)
 
461
        self.transport.delete(path)
 
462
 
 
463
    def _get_loose_object(self, sha):
 
464
        path = '%s/%s' % self._split_loose_object(sha)
 
465
        try:
 
466
            return ShaFile.from_file(self.transport.get(path))
 
467
        except NoSuchFile:
 
468
            return None
 
469
 
 
470
    def add_object(self, obj):
 
471
        """Add a single object to this object store.
 
472
 
 
473
        :param obj: Object to add
 
474
        """
 
475
        (dir, file) = self._split_loose_object(obj.id)
 
476
        try:
 
477
            self.transport.mkdir(dir)
 
478
        except FileExists:
 
479
            pass
 
480
        path = "%s/%s" % (dir, file)
 
481
        if self.transport.has(path):
 
482
            return # Already there, no need to write again
 
483
        self.transport.put_bytes(path, obj.as_legacy_object())
 
484
 
 
485
    def move_in_pack(self, f):
 
486
        """Move a specific file containing a pack into the pack directory.
 
487
 
 
488
        :note: The file should be on the same file system as the
 
489
            packs directory.
 
490
 
 
491
        :param path: Path to the pack file.
 
492
        """
 
493
        f.seek(0)
 
494
        p = PackData(None, f, len(f.getvalue()))
 
495
        entries = p.sorted_entries()
 
496
        basename = "pack-%s" % iter_sha1(entry[0] for entry in entries)
 
497
        f.seek(0)
 
498
        self.pack_transport.put_file(basename + ".pack", f)
 
499
        idxfile = self.pack_transport.open_write_stream(basename + ".idx")
 
500
        try:
 
501
            write_pack_index_v2(idxfile, entries, p.get_stored_checksum())
 
502
        finally:
 
503
            idxfile.close()
 
504
        idxfile = self.pack_transport.get(basename + ".idx")
 
505
        idx = load_pack_index_file(basename+".idx", idxfile)
 
506
        final_pack = Pack.from_objects(p, idx)
 
507
        self._add_known_pack(final_pack)
 
508
        return final_pack
 
509
 
 
510
    def add_thin_pack(self):
 
511
        """Add a new thin pack to this object store.
 
512
 
 
513
        Thin packs are packs that contain deltas with parents that exist
 
514
        in a different pack.
 
515
        """
 
516
        from cStringIO import StringIO
 
517
        f = StringIO()
 
518
        def commit():
 
519
            if len(f.getvalue()) > 0:
 
520
                return self.move_in_thin_pack(f)
 
521
            else:
 
522
                return None
 
523
        return f, commit
 
524
 
 
525
    def move_in_thin_pack(self, f):
 
526
        """Move a specific file containing a pack into the pack directory.
 
527
 
 
528
        :note: The file should be on the same file system as the
 
529
            packs directory.
 
530
 
 
531
        :param path: Path to the pack file.
 
532
        """
 
533
        f.seek(0)
 
534
        data = PackData.from_file(self.get_raw, f, len(f.getvalue()))
 
535
        idx = MemoryPackIndex(data.sorted_entries(), data.get_stored_checksum())
 
536
        p = Pack.from_objects(data, idx)
 
537
 
 
538
        pack_sha = idx.objects_sha1()
 
539
 
 
540
        datafile = self.pack_transport.open_write_stream(
 
541
                "pack-%s.pack" % pack_sha)
 
542
        try:
 
543
            entries, data_sum = write_pack_data(datafile, p.pack_tuples())
 
544
        finally:
 
545
            datafile.close()
 
546
        entries.sort()
 
547
        idxfile = self.pack_transport.open_write_stream(
 
548
            "pack-%s.idx" % pack_sha)
 
549
        try:
 
550
            write_pack_index_v2(idxfile, data.sorted_entries(), data_sum)
 
551
        finally:
 
552
            idxfile.close()
 
553
        final_pack = Pack("pack-%s" % pack_sha)
 
554
        self._add_known_pack(final_pack)
 
555
        return final_pack
 
556
 
 
557
    def add_pack(self):
 
558
        """Add a new pack to this object store. 
 
559
 
 
560
        :return: Fileobject to write to and a commit function to 
 
561
            call when the pack is finished.
 
562
        """
 
563
        from cStringIO import StringIO
 
564
        f = StringIO()
 
565
        def commit():
 
566
            if len(f.getvalue()) > 0:
 
567
                return self.move_in_pack(f)
 
568
            else:
 
569
                return None
 
570
        return f, commit
 
571
 
 
572
    @classmethod
 
573
    def init(cls, transport):
 
574
        transport.mkdir('info')
 
575
        transport.mkdir(PACKDIR)
 
576
        return cls(transport)