/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

Raise SettingFileIdUnsupported

Show diffs side-by-side

added added

removed removed

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