/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

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-04-13 23:16:57 UTC
  • mfrom: (1662.1.1 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060413231657-bce3d67d3e7a4f2b
(mbp/olaf) push/pull/merge --remember improvements

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)