/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 breezy/plugins/git/transportgit.py

  • Committer: Martin
  • Date: 2018-07-01 22:21:53 UTC
  • mto: This revision was merged to the branch mainline in revision 7020.
  • Revision ID: gzlist@googlemail.com-20180701222153-9x858n5ujntl9ucc
Update _known_graph_pyx for modern cython

Show diffs side-by-side

added added

removed removed

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