~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-10-08 00:02:33 UTC
  • mfrom: (1421)
  • mto: This revision was merged to the branch mainline in revision 1422.
  • Revision ID: robertc@robertcollins.net-20051008000233-c7c5d0d2b7000da0
merge from newformat stuff and upgrade

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import sys
19
19
import os
 
20
import errno
 
21
from warnings import warn
 
22
from cStringIO import StringIO
 
23
 
20
24
 
21
25
import bzrlib
 
26
from bzrlib.inventory import InventoryEntry
 
27
import bzrlib.inventory as inventory
22
28
from bzrlib.trace import mutter, note
23
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
 
     rename, splitpath, sha_file, appendpath, file_kind
25
 
 
26
 
from bzrlib.store import copy_all
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
 
     DivergedBranches, NotBranchError, UnlistableStore, UnlistableBranch
 
29
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
 
30
                            rename, splitpath, sha_file, appendpath, 
 
31
                            file_kind)
 
32
import bzrlib.errors as errors
 
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
35
                           DivergedBranches, LockError, UnlistableStore,
 
36
                           UnlistableBranch, NoSuchFile)
29
37
from bzrlib.textui import show_status
30
 
from bzrlib.revision import Revision, is_ancestor
 
38
from bzrlib.revision import Revision
31
39
from bzrlib.delta import compare_trees
32
40
from bzrlib.tree import EmptyTree, RevisionTree
33
 
import bzrlib.xml
 
41
from bzrlib.inventory import Inventory
 
42
from bzrlib.store import copy_all
 
43
from bzrlib.store.compressed_text import CompressedTextStore
 
44
from bzrlib.store.text import TextStore
 
45
from bzrlib.store.weave import WeaveStore
 
46
import bzrlib.transactions as transactions
 
47
from bzrlib.transport import Transport, get_transport
 
48
import bzrlib.xml5
34
49
import bzrlib.ui
35
50
 
36
51
 
37
 
 
38
 
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
 
52
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
53
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
39
54
## TODO: Maybe include checks for common corruption of newlines, etc?
40
55
 
41
56
 
42
57
# TODO: Some operations like log might retrieve the same revisions
43
58
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
 
# cache in memory to make this faster.
 
59
# cache in memory to make this faster.  In general anything can be
 
60
# cached in memory between lock and unlock operations.
45
61
 
46
62
def find_branch(*ignored, **ignored_too):
47
63
    # XXX: leave this here for about one release, then remove it
48
64
    raise NotImplementedError('find_branch() is not supported anymore, '
49
65
                              'please use one of the new branch constructors')
50
 
 
51
66
def _relpath(base, path):
52
67
    """Return path relative to base, or raise exception.
53
68
 
75
90
    return os.sep.join(s)
76
91
        
77
92
 
78
 
def find_branch_root(f=None):
79
 
    """Find the branch root enclosing f, or pwd.
80
 
 
81
 
    f may be a filename or a URL.
82
 
 
83
 
    It is not necessary that f exists.
 
93
def find_branch_root(t):
 
94
    """Find the branch root enclosing the transport's base.
 
95
 
 
96
    t is a Transport object.
 
97
 
 
98
    It is not necessary that the base of t exists.
84
99
 
85
100
    Basically we keep looking up until we find the control directory or
86
101
    run into the root.  If there isn't one, raises NotBranchError.
87
102
    """
88
 
    if f == None:
89
 
        f = os.getcwd()
90
 
    elif hasattr(os.path, 'realpath'):
91
 
        f = os.path.realpath(f)
92
 
    else:
93
 
        f = os.path.abspath(f)
94
 
    if not os.path.exists(f):
95
 
        raise BzrError('%r does not exist' % f)
96
 
        
97
 
 
98
 
    orig_f = f
99
 
 
 
103
    orig_base = t.base
100
104
    while True:
101
 
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
102
 
            return f
103
 
        head, tail = os.path.split(f)
104
 
        if head == f:
 
105
        if t.has(bzrlib.BZRDIR):
 
106
            return t
 
107
        new_t = t.clone('..')
 
108
        if new_t.base == t.base:
105
109
            # reached the root, whatever that may be
106
 
            raise NotBranchError('%s is not in a branch' % orig_f)
107
 
        f = head
108
 
 
109
 
 
 
110
            raise NotBranchError('%s is not in a branch' % orig_base)
 
111
        t = new_t
110
112
 
111
113
 
112
114
######################################################################
124
126
        raise NotImplementedError('The Branch class is abstract')
125
127
 
126
128
    @staticmethod
 
129
    def open_downlevel(base):
 
130
        """Open a branch which may be of an old format.
 
131
        
 
132
        Only local branches are supported."""
 
133
        return _Branch(get_transport(base), relax_version_check=True)
 
134
        
 
135
    @staticmethod
127
136
    def open(base):
128
137
        """Open an existing branch, rooted at 'base' (url)"""
129
 
        if base and (base.startswith('http://') or base.startswith('https://')):
130
 
            from bzrlib.remotebranch import RemoteBranch
131
 
            return RemoteBranch(base, find_root=False)
132
 
        else:
133
 
            return LocalBranch(base, find_root=False)
 
138
        t = get_transport(base)
 
139
        mutter("trying to open %r with transport %r", base, t)
 
140
        return _Branch(t)
134
141
 
135
142
    @staticmethod
136
143
    def open_containing(url):
138
145
        
139
146
        This probes for a branch at url, and searches upwards from there.
140
147
        """
141
 
        if url and (url.startswith('http://') or url.startswith('https://')):
142
 
            from bzrlib.remotebranch import RemoteBranch
143
 
            return RemoteBranch(url)
144
 
        else:
145
 
            return LocalBranch(url)
 
148
        t = get_transport(url)
 
149
        t = find_branch_root(t)
 
150
        return _Branch(t)
146
151
 
147
152
    @staticmethod
148
153
    def initialize(base):
149
154
        """Create a new branch, rooted at 'base' (url)"""
150
 
        if base and (base.startswith('http://') or base.startswith('https://')):
151
 
            from bzrlib.remotebranch import RemoteBranch
152
 
            return RemoteBranch(base, init=True)
153
 
        else:
154
 
            return LocalBranch(base, init=True)
 
155
        t = get_transport(base)
 
156
        return _Branch(t, init=True)
155
157
 
156
158
    def setup_caching(self, cache_root):
157
159
        """Subclasses that care about caching should override this, and set
158
160
        up cached stores located under cache_root.
159
161
        """
160
 
 
161
 
 
162
 
class LocalBranch(Branch):
 
162
        self.cache_root = cache_root
 
163
 
 
164
 
 
165
class _Branch(Branch):
163
166
    """A branch stored in the actual filesystem.
164
167
 
165
168
    Note that it's "local" in the context of the filesystem; it doesn't
183
186
    _lock_mode = None
184
187
    _lock_count = None
185
188
    _lock = None
186
 
 
187
 
    def __init__(self, base, init=False, find_root=True):
 
189
    _inventory_weave = None
 
190
    
 
191
    # Map some sort of prefix into a namespace
 
192
    # stuff like "revno:10", "revid:", etc.
 
193
    # This should match a prefix with a function which accepts
 
194
    REVISION_NAMESPACES = {}
 
195
 
 
196
    def push_stores(self, branch_to):
 
197
        """Copy the content of this branches store to branch_to."""
 
198
        if (self._branch_format != branch_to._branch_format
 
199
            or self._branch_format != 4):
 
200
            from bzrlib.fetch import greedy_fetch
 
201
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
202
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
203
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
204
                         revision=self.last_revision())
 
205
            return
 
206
 
 
207
        store_pairs = ((self.text_store,      branch_to.text_store),
 
208
                       (self.inventory_store, branch_to.inventory_store),
 
209
                       (self.revision_store,  branch_to.revision_store))
 
210
        try:
 
211
            for from_store, to_store in store_pairs: 
 
212
                copy_all(from_store, to_store)
 
213
        except UnlistableStore:
 
214
            raise UnlistableBranch(from_store)
 
215
 
 
216
    def __init__(self, transport, init=False,
 
217
                 relax_version_check=False):
188
218
        """Create new branch object at a particular location.
189
219
 
190
 
        base -- Base directory for the branch. May be a file:// url.
 
220
        transport -- A Transport object, defining how to access files.
 
221
                (If a string, transport.transport() will be used to
 
222
                create a Transport object)
191
223
        
192
224
        init -- If True, create new control files in a previously
193
225
             unversioned directory.  If False, the branch must already
194
226
             be versioned.
195
227
 
196
 
        find_root -- If true and init is false, find the root of the
197
 
             existing branch containing base.
 
228
        relax_version_check -- If true, the usual check for the branch
 
229
            version is not applied.  This is intended only for
 
230
            upgrade/recovery type use; it's not guaranteed that
 
231
            all operations will work on old format branches.
198
232
 
199
233
        In the test suite, creation of new trees is tested using the
200
234
        `ScratchBranch` class.
201
235
        """
202
 
        from bzrlib.store import ImmutableStore
 
236
        assert isinstance(transport, Transport), \
 
237
            "%r is not a Transport" % transport
 
238
        self._transport = transport
203
239
        if init:
204
 
            self.base = os.path.realpath(base)
205
240
            self._make_control()
206
 
        elif find_root:
207
 
            self.base = find_branch_root(base)
208
 
        else:
209
 
            if base.startswith("file://"):
210
 
                base = base[7:]
211
 
            self.base = os.path.realpath(base)
212
 
            if not isdir(self.controlfilename('.')):
213
 
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
214
 
                                     ['use "bzr init" to initialize a new working tree',
215
 
                                      'current bzr can only operate from top-of-tree'])
216
 
        self._check_format()
217
 
 
218
 
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
219
 
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
220
 
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
221
 
 
 
241
        self._check_format(relax_version_check)
 
242
 
 
243
        def get_store(name, compressed=True):
 
244
            # FIXME: This approach of assuming stores are all entirely compressed
 
245
            # or entirely uncompressed is tidy, but breaks upgrade from 
 
246
            # some existing branches where there's a mixture; we probably 
 
247
            # still want the option to look for both.
 
248
            relpath = self._rel_controlfilename(name)
 
249
            if compressed:
 
250
                store = CompressedTextStore(self._transport.clone(relpath))
 
251
            else:
 
252
                store = TextStore(self._transport.clone(relpath))
 
253
            #if self._transport.should_cache():
 
254
            #    cache_path = os.path.join(self.cache_root, name)
 
255
            #    os.mkdir(cache_path)
 
256
            #    store = bzrlib.store.CachedStore(store, cache_path)
 
257
            return store
 
258
        def get_weave(name):
 
259
            relpath = self._rel_controlfilename(name)
 
260
            ws = WeaveStore(self._transport.clone(relpath))
 
261
            if self._transport.should_cache():
 
262
                ws.enable_cache = True
 
263
            return ws
 
264
 
 
265
        if self._branch_format == 4:
 
266
            self.inventory_store = get_store('inventory-store')
 
267
            self.text_store = get_store('text-store')
 
268
            self.revision_store = get_store('revision-store')
 
269
        elif self._branch_format == 5:
 
270
            self.control_weaves = get_weave([])
 
271
            self.weave_store = get_weave('weaves')
 
272
            self.revision_store = get_store('revision-store', compressed=False)
 
273
        self._transaction = None
222
274
 
223
275
    def __str__(self):
224
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
276
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
225
277
 
226
278
 
227
279
    __repr__ = __str__
229
281
 
230
282
    def __del__(self):
231
283
        if self._lock_mode or self._lock:
232
 
            from bzrlib.warnings import warn
 
284
            # XXX: This should show something every time, and be suitable for
 
285
            # headless operation and embedding
233
286
            warn("branch %r was not explicitly unlocked" % self)
234
287
            self._lock.unlock()
235
288
 
 
289
        # TODO: It might be best to do this somewhere else,
 
290
        # but it is nice for a Branch object to automatically
 
291
        # cache it's information.
 
292
        # Alternatively, we could have the Transport objects cache requests
 
293
        # See the earlier discussion about how major objects (like Branch)
 
294
        # should never expect their __del__ function to run.
 
295
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
296
            try:
 
297
                import shutil
 
298
                shutil.rmtree(self.cache_root)
 
299
            except:
 
300
                pass
 
301
            self.cache_root = None
 
302
 
 
303
    def _get_base(self):
 
304
        if self._transport:
 
305
            return self._transport.base
 
306
        return None
 
307
 
 
308
    base = property(_get_base)
 
309
 
 
310
    def _finish_transaction(self):
 
311
        """Exit the current transaction."""
 
312
        if self._transaction is None:
 
313
            raise errors.LockError('Branch %s is not in a transaction' %
 
314
                                   self)
 
315
        transaction = self._transaction
 
316
        self._transaction = None
 
317
        transaction.finish()
 
318
 
 
319
    def get_transaction(self):
 
320
        """Return the current active transaction.
 
321
 
 
322
        If no transaction is active, this returns a passthrough object
 
323
        for which all data is immedaitely flushed and no caching happens.
 
324
        """
 
325
        if self._transaction is None:
 
326
            return transactions.PassThroughTransaction()
 
327
        else:
 
328
            return self._transaction
 
329
 
 
330
    def _set_transaction(self, new_transaction):
 
331
        """Set a new active transaction."""
 
332
        if self._transaction is not None:
 
333
            raise errors.LockError('Branch %s is in a transaction already.' %
 
334
                                   self)
 
335
        self._transaction = new_transaction
 
336
 
236
337
    def lock_write(self):
 
338
        # TODO: Upgrade locking to support using a Transport,
 
339
        # and potentially a remote locking protocol
237
340
        if self._lock_mode:
238
341
            if self._lock_mode != 'w':
239
 
                from bzrlib.errors import LockError
240
342
                raise LockError("can't upgrade to a write lock from %r" %
241
343
                                self._lock_mode)
242
344
            self._lock_count += 1
243
345
        else:
244
 
            from bzrlib.lock import WriteLock
245
 
 
246
 
            self._lock = WriteLock(self.controlfilename('branch-lock'))
 
346
            self._lock = self._transport.lock_write(
 
347
                    self._rel_controlfilename('branch-lock'))
247
348
            self._lock_mode = 'w'
248
349
            self._lock_count = 1
 
350
            self._set_transaction(transactions.PassThroughTransaction())
249
351
 
250
352
 
251
353
    def lock_read(self):
254
356
                   "invalid lock mode %r" % self._lock_mode
255
357
            self._lock_count += 1
256
358
        else:
257
 
            from bzrlib.lock import ReadLock
258
 
 
259
 
            self._lock = ReadLock(self.controlfilename('branch-lock'))
 
359
            self._lock = self._transport.lock_read(
 
360
                    self._rel_controlfilename('branch-lock'))
260
361
            self._lock_mode = 'r'
261
362
            self._lock_count = 1
 
363
            self._set_transaction(transactions.ReadOnlyTransaction())
262
364
                        
263
365
    def unlock(self):
264
366
        if not self._lock_mode:
265
 
            from bzrlib.errors import LockError
266
367
            raise LockError('branch %r is not locked' % (self))
267
368
 
268
369
        if self._lock_count > 1:
269
370
            self._lock_count -= 1
270
371
        else:
 
372
            self._finish_transaction()
271
373
            self._lock.unlock()
272
374
            self._lock = None
273
375
            self._lock_mode = self._lock_count = None
274
376
 
275
377
    def abspath(self, name):
276
378
        """Return absolute filename for something in the branch"""
277
 
        return os.path.join(self.base, name)
 
379
        return self._transport.abspath(name)
278
380
 
279
381
    def relpath(self, path):
280
382
        """Return path relative to this branch of something inside it.
281
383
 
282
384
        Raises an error if path is not in this branch."""
283
 
        return _relpath(self.base, path)
 
385
        return self._transport.relpath(path)
 
386
 
 
387
 
 
388
    def _rel_controlfilename(self, file_or_path):
 
389
        if isinstance(file_or_path, basestring):
 
390
            file_or_path = [file_or_path]
 
391
        return [bzrlib.BZRDIR] + file_or_path
284
392
 
285
393
    def controlfilename(self, file_or_path):
286
394
        """Return location relative to branch."""
287
 
        if isinstance(file_or_path, basestring):
288
 
            file_or_path = [file_or_path]
289
 
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
395
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
290
396
 
291
397
 
292
398
    def controlfile(self, file_or_path, mode='r'):
300
406
        Controlfiles should almost never be opened in write mode but
301
407
        rather should be atomically copied and replaced using atomicfile.
302
408
        """
303
 
 
304
 
        fn = self.controlfilename(file_or_path)
305
 
 
306
 
        if mode == 'rb' or mode == 'wb':
307
 
            return file(fn, mode)
308
 
        elif mode == 'r' or mode == 'w':
309
 
            # open in binary mode anyhow so there's no newline translation;
310
 
            # codecs uses line buffering by default; don't want that.
311
 
            import codecs
312
 
            return codecs.open(fn, mode + 'b', 'utf-8',
313
 
                               buffering=60000)
 
409
        import codecs
 
410
 
 
411
        relpath = self._rel_controlfilename(file_or_path)
 
412
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
413
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
414
        if mode == 'rb': 
 
415
            return self._transport.get(relpath)
 
416
        elif mode == 'wb':
 
417
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
 
418
        elif mode == 'r':
 
419
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
420
        elif mode == 'w':
 
421
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
314
422
        else:
315
423
            raise BzrError("invalid controlfile mode %r" % mode)
316
424
 
 
425
    def put_controlfile(self, path, f, encode=True):
 
426
        """Write an entry as a controlfile.
 
427
 
 
428
        :param path: The path to put the file, relative to the .bzr control
 
429
                     directory
 
430
        :param f: A file-like or string object whose contents should be copied.
 
431
        :param encode:  If true, encode the contents as utf-8
 
432
        """
 
433
        self.put_controlfiles([(path, f)], encode=encode)
 
434
 
 
435
    def put_controlfiles(self, files, encode=True):
 
436
        """Write several entries as controlfiles.
 
437
 
 
438
        :param files: A list of [(path, file)] pairs, where the path is the directory
 
439
                      underneath the bzr control directory
 
440
        :param encode:  If true, encode the contents as utf-8
 
441
        """
 
442
        import codecs
 
443
        ctrl_files = []
 
444
        for path, f in files:
 
445
            if encode:
 
446
                if isinstance(f, basestring):
 
447
                    f = f.encode('utf-8', 'replace')
 
448
                else:
 
449
                    f = codecs.getwriter('utf-8')(f, errors='replace')
 
450
            path = self._rel_controlfilename(path)
 
451
            ctrl_files.append((path, f))
 
452
        self._transport.put_multi(ctrl_files)
 
453
 
317
454
    def _make_control(self):
318
455
        from bzrlib.inventory import Inventory
 
456
        from bzrlib.weavefile import write_weave_v5
 
457
        from bzrlib.weave import Weave
319
458
        
320
 
        os.mkdir(self.controlfilename([]))
321
 
        self.controlfile('README', 'w').write(
322
 
            "This is a Bazaar-NG control directory.\n"
323
 
            "Do not change any files in this directory.\n")
324
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
325
 
        for d in ('text-store', 'inventory-store', 'revision-store'):
326
 
            os.mkdir(self.controlfilename(d))
327
 
        for f in ('revision-history', 'merged-patches',
328
 
                  'pending-merged-patches', 'branch-name',
329
 
                  'branch-lock',
330
 
                  'pending-merges'):
331
 
            self.controlfile(f, 'w').write('')
332
 
        mutter('created control directory in ' + self.base)
333
 
 
 
459
        # Create an empty inventory
 
460
        sio = StringIO()
334
461
        # if we want per-tree root ids then this is the place to set
335
462
        # them; they're not needed for now and so ommitted for
336
463
        # simplicity.
337
 
        f = self.controlfile('inventory','w')
338
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
339
 
 
340
 
 
341
 
    def _check_format(self):
 
464
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
465
        empty_inv = sio.getvalue()
 
466
        sio = StringIO()
 
467
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
468
        empty_weave = sio.getvalue()
 
469
 
 
470
        dirs = [[], 'revision-store', 'weaves']
 
471
        files = [('README', 
 
472
            "This is a Bazaar-NG control directory.\n"
 
473
            "Do not change any files in this directory.\n"),
 
474
            ('branch-format', BZR_BRANCH_FORMAT_5),
 
475
            ('revision-history', ''),
 
476
            ('branch-name', ''),
 
477
            ('branch-lock', ''),
 
478
            ('pending-merges', ''),
 
479
            ('inventory', empty_inv),
 
480
            ('inventory.weave', empty_weave),
 
481
            ('ancestry.weave', empty_weave)
 
482
        ]
 
483
        cfn = self._rel_controlfilename
 
484
        self._transport.mkdir_multi([cfn(d) for d in dirs])
 
485
        self.put_controlfiles(files)
 
486
        mutter('created control directory in ' + self._transport.base)
 
487
 
 
488
    def _check_format(self, relax_version_check):
342
489
        """Check this branch format is supported.
343
490
 
344
 
        The current tool only supports the current unstable format.
 
491
        The format level is stored, as an integer, in
 
492
        self._branch_format for code that needs to check it later.
345
493
 
346
494
        In the future, we might need different in-memory Branch
347
495
        classes to support downlevel branches.  But not yet.
348
496
        """
349
 
        # This ignores newlines so that we can open branches created
350
 
        # on Windows from Linux and so on.  I think it might be better
351
 
        # to always make all internal files in unix format.
352
 
        fmt = self.controlfile('branch-format', 'r').read()
353
 
        fmt = fmt.replace('\r\n', '\n')
354
 
        if fmt != BZR_BRANCH_FORMAT:
 
497
        try:
 
498
            fmt = self.controlfile('branch-format', 'r').read()
 
499
        except NoSuchFile:
 
500
            raise NotBranchError(self.base)
 
501
        mutter("got branch format %r", fmt)
 
502
        if fmt == BZR_BRANCH_FORMAT_5:
 
503
            self._branch_format = 5
 
504
        elif fmt == BZR_BRANCH_FORMAT_4:
 
505
            self._branch_format = 4
 
506
 
 
507
        if (not relax_version_check
 
508
            and self._branch_format != 5):
355
509
            raise BzrError('sorry, branch format %r not supported' % fmt,
356
510
                           ['use a different bzr version',
357
 
                            'or remove the .bzr directory and "bzr init" again'])
 
511
                            'or remove the .bzr directory'
 
512
                            ' and "bzr init" again'])
358
513
 
359
514
    def get_root_id(self):
360
515
        """Return the id of this branches root"""
375
530
 
376
531
    def read_working_inventory(self):
377
532
        """Read the working inventory."""
378
 
        from bzrlib.inventory import Inventory
379
533
        self.lock_read()
380
534
        try:
381
535
            # ElementTree does its own conversion from UTF-8, so open in
382
536
            # binary.
383
537
            f = self.controlfile('inventory', 'rb')
384
 
            return bzrlib.xml.serializer_v4.read_inventory(f)
 
538
            return bzrlib.xml5.serializer_v5.read_inventory(f)
385
539
        finally:
386
540
            self.unlock()
387
541
            
392
546
        That is to say, the inventory describing changes underway, that
393
547
        will be committed to the next revision.
394
548
        """
395
 
        from bzrlib.atomicfile import AtomicFile
396
 
        
 
549
        from cStringIO import StringIO
397
550
        self.lock_write()
398
551
        try:
399
 
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
400
 
            try:
401
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
402
 
                f.commit()
403
 
            finally:
404
 
                f.close()
 
552
            sio = StringIO()
 
553
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
554
            sio.seek(0)
 
555
            # Transport handles atomicity
 
556
            self.put_controlfile('inventory', sio)
405
557
        finally:
406
558
            self.unlock()
407
559
        
408
560
        mutter('wrote working inventory')
409
561
            
410
 
 
411
562
    inventory = property(read_working_inventory, _write_inventory, None,
412
563
                         """Inventory for the working copy.""")
413
564
 
414
 
 
415
565
    def add(self, files, ids=None):
416
566
        """Make files versioned.
417
567
 
465
615
                    kind = file_kind(fullpath)
466
616
                except OSError:
467
617
                    # maybe something better?
468
 
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
618
                    raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
469
619
 
470
 
                if kind != 'file' and kind != 'directory':
471
 
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
620
                if not InventoryEntry.versionable_kind(kind):
 
621
                    raise BzrError('cannot add: not a versionable file ('
 
622
                                   'i.e. regular file, symlink or directory): %s' % quotefn(f))
472
623
 
473
624
                if file_id is None:
474
625
                    file_id = gen_file_id(f)
539
690
        finally:
540
691
            self.unlock()
541
692
 
542
 
 
543
693
    # FIXME: this doesn't need to be a branch method
544
694
    def set_inventory(self, new_inventory_list):
545
695
        from bzrlib.inventory import Inventory, InventoryEntry
548
698
            name = os.path.basename(path)
549
699
            if name == "":
550
700
                continue
551
 
            inv.add(InventoryEntry(file_id, name, kind, parent))
 
701
            # fixme, there should be a factory function inv,add_?? 
 
702
            if kind == 'directory':
 
703
                inv.add(inventory.InventoryDirectory(file_id, name, parent))
 
704
            elif kind == 'file':
 
705
                inv.add(inventory.InventoryFile(file_id, name, parent))
 
706
            elif kind == 'symlink':
 
707
                inv.add(inventory.InventoryLink(file_id, name, parent))
 
708
            else:
 
709
                raise BzrError("unknown kind %r" % kind)
552
710
        self._write_inventory(inv)
553
711
 
554
 
 
555
712
    def unknowns(self):
556
713
        """Return all unknown files.
557
714
 
572
729
 
573
730
 
574
731
    def append_revision(self, *revision_ids):
575
 
        from bzrlib.atomicfile import AtomicFile
576
 
 
577
732
        for revision_id in revision_ids:
578
733
            mutter("add {%s} to revision-history" % revision_id)
579
 
 
580
 
        rev_history = self.revision_history()
581
 
        rev_history.extend(revision_ids)
582
 
 
583
 
        f = AtomicFile(self.controlfilename('revision-history'))
 
734
        self.lock_write()
584
735
        try:
585
 
            for rev_id in rev_history:
586
 
                print >>f, rev_id
587
 
            f.commit()
 
736
            rev_history = self.revision_history()
 
737
            rev_history.extend(revision_ids)
 
738
            self.put_controlfile('revision-history', '\n'.join(rev_history))
588
739
        finally:
589
 
            f.close()
590
 
 
 
740
            self.unlock()
 
741
 
 
742
    def has_revision(self, revision_id):
 
743
        """True if this branch has a copy of the revision.
 
744
 
 
745
        This does not necessarily imply the revision is merge
 
746
        or on the mainline."""
 
747
        return (revision_id is None
 
748
                or revision_id in self.revision_store)
591
749
 
592
750
    def get_revision_xml_file(self, revision_id):
593
751
        """Return XML file object for revision object."""
603
761
        finally:
604
762
            self.unlock()
605
763
 
606
 
 
607
764
    #deprecated
608
765
    get_revision_xml = get_revision_xml_file
609
766
 
 
767
    def get_revision_xml(self, revision_id):
 
768
        return self.get_revision_xml_file(revision_id).read()
 
769
 
610
770
 
611
771
    def get_revision(self, revision_id):
612
772
        """Return the Revision object for a named revision"""
613
773
        xml_file = self.get_revision_xml_file(revision_id)
614
774
 
615
775
        try:
616
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
 
776
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
617
777
        except SyntaxError, e:
618
778
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
619
779
                                         [revision_id,
622
782
        assert r.revision_id == revision_id
623
783
        return r
624
784
 
625
 
 
626
785
    def get_revision_delta(self, revno):
627
786
        """Return the delta for one revision.
628
787
 
644
803
 
645
804
        return compare_trees(old_tree, new_tree)
646
805
 
647
 
        
648
 
 
649
806
    def get_revision_sha1(self, revision_id):
650
807
        """Hash the stored value of a revision, and return it."""
651
808
        # In the future, revision entries will be signed. At that
654
811
        # the revision, (add signatures/remove signatures) and still
655
812
        # have all hash pointers stay consistent.
656
813
        # But for now, just hash the contents.
657
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
658
 
 
659
 
 
660
 
    def get_inventory(self, inventory_id):
661
 
        """Get Inventory object by hash.
662
 
 
663
 
        TODO: Perhaps for this and similar methods, take a revision
664
 
               parameter which can be either an integer revno or a
665
 
               string hash."""
666
 
        from bzrlib.inventory import Inventory
667
 
 
668
 
        f = self.get_inventory_xml_file(inventory_id)
669
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
670
 
 
671
 
 
672
 
    def get_inventory_xml(self, inventory_id):
 
814
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
815
 
 
816
    def get_ancestry(self, revision_id):
 
817
        """Return a list of revision-ids integrated by a revision.
 
818
        
 
819
        This currently returns a list, but the ordering is not guaranteed:
 
820
        treat it as a set.
 
821
        """
 
822
        if revision_id is None:
 
823
            return [None]
 
824
        w = self.get_inventory_weave()
 
825
        return [None] + map(w.idx_to_name,
 
826
                            w.inclusions([w.lookup(revision_id)]))
 
827
 
 
828
    def get_inventory_weave(self):
 
829
        return self.control_weaves.get_weave('inventory',
 
830
                                             self.get_transaction())
 
831
 
 
832
    def get_inventory(self, revision_id):
 
833
        """Get Inventory object by hash."""
 
834
        xml = self.get_inventory_xml(revision_id)
 
835
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
836
 
 
837
    def get_inventory_xml(self, revision_id):
673
838
        """Get inventory XML as a file object."""
674
 
        return self.inventory_store[inventory_id]
675
 
 
676
 
    get_inventory_xml_file = get_inventory_xml
677
 
            
678
 
 
679
 
    def get_inventory_sha1(self, inventory_id):
 
839
        try:
 
840
            assert isinstance(revision_id, basestring), type(revision_id)
 
841
            iw = self.get_inventory_weave()
 
842
            return iw.get_text(iw.lookup(revision_id))
 
843
        except IndexError:
 
844
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
845
 
 
846
    def get_inventory_sha1(self, revision_id):
680
847
        """Return the sha1 hash of the inventory entry
681
848
        """
682
 
        return sha_file(self.get_inventory_xml(inventory_id))
683
 
 
 
849
        return self.get_revision(revision_id).inventory_sha1
684
850
 
685
851
    def get_revision_inventory(self, revision_id):
686
852
        """Return inventory of a past revision."""
687
 
        # bzr 0.0.6 imposes the constraint that the inventory_id
 
853
        # TODO: Unify this with get_inventory()
 
854
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
688
855
        # must be the same as its revision, so this is trivial.
689
856
        if revision_id == None:
690
 
            from bzrlib.inventory import Inventory
691
857
            return Inventory(self.get_root_id())
692
858
        else:
693
859
            return self.get_inventory(revision_id)
694
860
 
695
 
 
696
861
    def revision_history(self):
697
 
        """Return sequence of revision hashes on to this branch.
698
 
 
699
 
        >>> ScratchBranch().revision_history()
700
 
        []
701
 
        """
 
862
        """Return sequence of revision hashes on to this branch."""
702
863
        self.lock_read()
703
864
        try:
704
865
            return [l.rstrip('\r\n') for l in
706
867
        finally:
707
868
            self.unlock()
708
869
 
709
 
 
710
870
    def common_ancestor(self, other, self_revno=None, other_revno=None):
711
871
        """
712
872
        >>> from bzrlib.commit import commit
761
921
        return len(self.revision_history())
762
922
 
763
923
 
764
 
    def last_patch(self):
 
924
    def last_revision(self):
765
925
        """Return last patch hash, or None if no history.
766
926
        """
767
927
        ph = self.revision_history()
772
932
 
773
933
 
774
934
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
775
 
        """
 
935
        """Return a list of new revisions that would perfectly fit.
 
936
        
776
937
        If self and other have not diverged, return a list of the revisions
777
938
        present in other, but missing from self.
778
939
 
798
959
        Traceback (most recent call last):
799
960
        DivergedBranches: These branches have diverged.
800
961
        """
 
962
        # FIXME: If the branches have diverged, but the latest
 
963
        # revision in this branch is completely merged into the other,
 
964
        # then we should still be able to pull.
801
965
        self_history = self.revision_history()
802
966
        self_len = len(self_history)
803
967
        other_history = other.revision_history()
809
973
 
810
974
        if stop_revision is None:
811
975
            stop_revision = other_len
812
 
        elif stop_revision > other_len:
813
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
814
 
        
 
976
        else:
 
977
            assert isinstance(stop_revision, int)
 
978
            if stop_revision > other_len:
 
979
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
815
980
        return other_history[self_len:stop_revision]
816
981
 
817
 
 
818
982
    def update_revisions(self, other, stop_revision=None):
819
 
        """Pull in all new revisions from other branch.
820
 
        """
 
983
        """Pull in new perfect-fit revisions."""
821
984
        from bzrlib.fetch import greedy_fetch
822
985
        from bzrlib.revision import get_intervening_revisions
823
 
 
824
 
        pb = bzrlib.ui.ui_factory.progress_bar()
825
 
        pb.update('comparing histories')
826
986
        if stop_revision is None:
827
 
            other_revision = other.last_patch()
828
 
        else:
829
 
            other_revision = other.get_rev_id(stop_revision)
830
 
        count = greedy_fetch(self, other, other_revision, pb)[0]
831
 
        try:
832
 
            revision_ids = self.missing_revisions(other, stop_revision)
833
 
        except DivergedBranches, e:
834
 
            try:
835
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
836
 
                                                         other_revision, self)
837
 
                assert self.last_patch() not in revision_ids
838
 
            except bzrlib.errors.NotAncestor:
839
 
                if is_ancestor(self.last_patch(), other_revision, self):
840
 
                    revision_ids = []
841
 
                else:
842
 
                    raise e
843
 
        self.append_revision(*revision_ids)
844
 
        pb.clear()
845
 
 
846
 
    def install_revisions(self, other, revision_ids, pb):
847
 
        if hasattr(other.revision_store, "prefetch"):
848
 
            other.revision_store.prefetch(revision_ids)
849
 
        if hasattr(other.inventory_store, "prefetch"):
850
 
            inventory_ids = []
851
 
            for rev_id in revision_ids:
852
 
                try:
853
 
                    revision = other.get_revision(rev_id).inventory_id
854
 
                    inventory_ids.append(revision)
855
 
                except bzrlib.errors.NoSuchRevision:
856
 
                    pass
857
 
            other.inventory_store.prefetch(inventory_ids)
858
 
 
859
 
        if pb is None:
860
 
            pb = bzrlib.ui.ui_factory.progress_bar()
861
 
                
862
 
        revisions = []
863
 
        needed_texts = set()
864
 
        i = 0
865
 
 
866
 
        failures = set()
867
 
        for i, rev_id in enumerate(revision_ids):
868
 
            pb.update('fetching revision', i+1, len(revision_ids))
869
 
            try:
870
 
                rev = other.get_revision(rev_id)
871
 
            except bzrlib.errors.NoSuchRevision:
872
 
                failures.add(rev_id)
873
 
                continue
874
 
 
875
 
            revisions.append(rev)
876
 
            inv = other.get_inventory(str(rev.inventory_id))
877
 
            for key, entry in inv.iter_entries():
878
 
                if entry.text_id is None:
879
 
                    continue
880
 
                if entry.text_id not in self.text_store:
881
 
                    needed_texts.add(entry.text_id)
882
 
 
883
 
        pb.clear()
884
 
                    
885
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
886
 
                                                    needed_texts)
887
 
        #print "Added %d texts." % count 
888
 
        inventory_ids = [ f.inventory_id for f in revisions ]
889
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
890
 
                                                         inventory_ids)
891
 
        #print "Added %d inventories." % count 
892
 
        revision_ids = [ f.revision_id for f in revisions]
893
 
 
894
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
895
 
                                                          revision_ids,
896
 
                                                          permit_failure=True)
897
 
        assert len(cp_fail) == 0 
898
 
        return count, failures
899
 
       
 
987
            stop_revision = other.last_revision()
 
988
        greedy_fetch(to_branch=self, from_branch=other,
 
989
                     revision=stop_revision)
 
990
        pullable_revs = self.missing_revisions(
 
991
            other, other.revision_id_to_revno(stop_revision))
 
992
        if pullable_revs:
 
993
            greedy_fetch(to_branch=self,
 
994
                         from_branch=other,
 
995
                         revision=pullable_revs[-1])
 
996
            self.append_revision(*pullable_revs)
 
997
    
900
998
 
901
999
    def commit(self, *args, **kw):
902
 
        from bzrlib.commit import commit
903
 
        commit(self, *args, **kw)
904
 
        
 
1000
        from bzrlib.commit import Commit
 
1001
        Commit().commit(self, *args, **kw)
 
1002
    
905
1003
    def revision_id_to_revno(self, revision_id):
906
1004
        """Given a revision id, return its revno"""
 
1005
        if revision_id is None:
 
1006
            return 0
907
1007
        history = self.revision_history()
908
1008
        try:
909
1009
            return history.index(revision_id) + 1
920
1020
            raise bzrlib.errors.NoSuchRevision(self, revno)
921
1021
        return history[revno - 1]
922
1022
 
923
 
 
924
1023
    def revision_tree(self, revision_id):
925
1024
        """Return Tree for a revision on this branch.
926
1025
 
932
1031
            return EmptyTree()
933
1032
        else:
934
1033
            inv = self.get_revision_inventory(revision_id)
935
 
            return RevisionTree(self.text_store, inv)
 
1034
            return RevisionTree(self.weave_store, inv, revision_id)
936
1035
 
937
1036
 
938
1037
    def working_tree(self):
939
1038
        """Return a `Tree` for the working copy."""
940
1039
        from bzrlib.workingtree import WorkingTree
941
 
        return WorkingTree(self.base, self.read_working_inventory())
 
1040
        # TODO: In the future, WorkingTree should utilize Transport
 
1041
        # RobertCollins 20051003 - I don't think it should - working trees are
 
1042
        # much more complex to keep consistent than our careful .bzr subset.
 
1043
        # instead, we should say that working trees are local only, and optimise
 
1044
        # for that.
 
1045
        return WorkingTree(self._transport.base, self.read_working_inventory())
942
1046
 
943
1047
 
944
1048
    def basis_tree(self):
946
1050
 
947
1051
        If there are no revisions yet, return an `EmptyTree`.
948
1052
        """
949
 
        r = self.last_patch()
950
 
        if r == None:
951
 
            return EmptyTree()
952
 
        else:
953
 
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
954
 
 
 
1053
        return self.revision_tree(self.last_revision())
955
1054
 
956
1055
 
957
1056
    def rename_one(self, from_rel, to_rel):
1123
1222
        These are revisions that have been merged into the working
1124
1223
        directory but not yet committed.
1125
1224
        """
1126
 
        cfn = self.controlfilename('pending-merges')
1127
 
        if not os.path.exists(cfn):
 
1225
        cfn = self._rel_controlfilename('pending-merges')
 
1226
        if not self._transport.has(cfn):
1128
1227
            return []
1129
1228
        p = []
1130
1229
        for l in self.controlfile('pending-merges', 'r').readlines():
1132
1231
        return p
1133
1232
 
1134
1233
 
1135
 
    def add_pending_merge(self, revision_id):
1136
 
        from bzrlib.revision import validate_revision_id
1137
 
 
1138
 
        validate_revision_id(revision_id)
1139
 
 
 
1234
    def add_pending_merge(self, *revision_ids):
 
1235
        # TODO: Perhaps should check at this point that the
 
1236
        # history of the revision is actually present?
1140
1237
        p = self.pending_merges()
1141
 
        if revision_id in p:
1142
 
            return
1143
 
        p.append(revision_id)
1144
 
        self.set_pending_merges(p)
1145
 
 
 
1238
        updated = False
 
1239
        for rev_id in revision_ids:
 
1240
            if rev_id in p:
 
1241
                continue
 
1242
            p.append(rev_id)
 
1243
            updated = True
 
1244
        if updated:
 
1245
            self.set_pending_merges(p)
1146
1246
 
1147
1247
    def set_pending_merges(self, rev_list):
1148
 
        from bzrlib.atomicfile import AtomicFile
1149
1248
        self.lock_write()
1150
1249
        try:
1151
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1152
 
            try:
1153
 
                for l in rev_list:
1154
 
                    print >>f, l
1155
 
                f.commit()
1156
 
            finally:
1157
 
                f.close()
 
1250
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1158
1251
        finally:
1159
1252
            self.unlock()
1160
1253
 
1211
1304
        
1212
1305
 
1213
1306
 
1214
 
class ScratchBranch(LocalBranch):
 
1307
class ScratchBranch(_Branch):
1215
1308
    """Special test class: a branch that cleans up after itself.
1216
1309
 
1217
1310
    >>> b = ScratchBranch()
1234
1327
        if base is None:
1235
1328
            base = mkdtemp()
1236
1329
            init = True
1237
 
        LocalBranch.__init__(self, base, init=init)
 
1330
        if isinstance(base, basestring):
 
1331
            base = get_transport(base)
 
1332
        _Branch.__init__(self, base, init=init)
1238
1333
        for d in dirs:
1239
 
            os.mkdir(self.abspath(d))
 
1334
            self._transport.mkdir(d)
1240
1335
            
1241
1336
        for f in files:
1242
 
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
1337
            self._transport.put(f, 'content of %s' % f)
1243
1338
 
1244
1339
 
1245
1340
    def clone(self):
1262
1357
        copytree(self.base, base, symlinks=True)
1263
1358
        return ScratchBranch(base=base)
1264
1359
 
1265
 
 
1266
 
        
1267
1360
    def __del__(self):
1268
1361
        self.destroy()
1269
1362
 
1282
1375
                for name in files:
1283
1376
                    os.chmod(os.path.join(root, name), 0700)
1284
1377
            rmtree(self.base)
1285
 
        self.base = None
 
1378
        self._transport = None
1286
1379
 
1287
1380
    
1288
1381
 
1339
1432
    return gen_file_id('TREE_ROOT')
1340
1433
 
1341
1434
 
1342
 
def copy_branch(branch_from, to_location, revno=None, basis_branch=None):
1343
 
    """Copy branch_from into the existing directory to_location.
1344
 
 
1345
 
    revision
1346
 
        If not None, only revisions up to this point will be copied.
1347
 
        The head of the new branch will be that revision.
1348
 
 
1349
 
    to_location
1350
 
        The name of a local directory that exists but is empty.
1351
 
 
1352
 
    revno
1353
 
        The revision to copy up to
1354
 
 
1355
 
    basis_branch
1356
 
        A local branch to copy revisions from, related to branch_from
1357
 
    """
1358
 
    from bzrlib.merge import merge
1359
 
 
1360
 
    assert isinstance(branch_from, Branch)
1361
 
    assert isinstance(to_location, basestring)
1362
 
    
1363
 
    br_to = Branch.initialize(to_location)
1364
 
    if basis_branch is not None:
1365
 
        copy_stores(basis_branch, br_to)
1366
 
    br_to.set_root_id(branch_from.get_root_id())
1367
 
    if revno is None:
1368
 
        revno = branch_from.revno()
1369
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1370
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1371
 
          check_clean=False, ignore_zero=True)
1372
 
    br_to.set_parent(branch_from.base)
1373
 
    return br_to
1374
 
 
1375
 
def copy_stores(branch_from, branch_to):
1376
 
    """Copies all entries from branch stores to another branch's stores.
1377
 
    """
1378
 
    store_pairs = ((branch_from.text_store,      branch_to.text_store),
1379
 
                   (branch_from.inventory_store, branch_to.inventory_store),
1380
 
                   (branch_from.revision_store,  branch_to.revision_store))
1381
 
    try:
1382
 
        for from_store, to_store in store_pairs: 
1383
 
            copy_all(from_store, to_store)
1384
 
    except UnlistableStore:
1385
 
        raise UnlistableBranch(from_store)