/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: Jelmer Vernooij
  • Date: 2010-06-28 23:48:36 UTC
  • mto: (0.200.953 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20100628234836-96qxjh7g2xxedblp
Fix transportgit.

Show diffs side-by-side

added added

removed removed

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