/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: 2018-03-17 17:54:17 UTC
  • mto: (0.200.1859 work)
  • mto: This revision was merged to the branch mainline in revision 6960.
  • Revision ID: jelmer@jelmer.uk-20180317175417-4ag21da38udunec9
Add tests for memorytree.

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