/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

Fix two mistakes in 'bzr help git'.

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