/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-02-12 00:40:38 UTC
  • mto: (0.200.718 trunk)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@samba.org-20100212004038-9fvt8214fnhla0xs
Use TransportGit by default.

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