~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Merge from integration

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 
25
25
 
26
26
import bzrlib
27
 
from bzrlib.inventory import InventoryEntry
28
27
import bzrlib.inventory as inventory
29
28
from bzrlib.trace import mutter, note
30
 
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
 
29
from bzrlib.osutils import (isdir, quotefn,
31
30
                            rename, splitpath, sha_file, appendpath, 
32
31
                            file_kind, abspath)
33
32
import bzrlib.errors as errors
46
45
from bzrlib.lockablefiles import LockableFiles
47
46
from bzrlib.revstorage import RevisionStorage
48
47
from bzrlib.store import copy_all
 
48
from bzrlib.store.text import TextStore
 
49
from bzrlib.store.weave import WeaveStore
 
50
from bzrlib.testament import Testament
49
51
import bzrlib.transactions as transactions
50
52
from bzrlib.transport import Transport, get_transport
51
53
import bzrlib.ui
109
111
        """Open a branch which may be of an old format.
110
112
        
111
113
        Only local branches are supported."""
112
 
        return _Branch(get_transport(base), relax_version_check=True)
 
114
        return BzrBranch(get_transport(base), relax_version_check=True)
113
115
        
114
116
    @staticmethod
115
117
    def open(base):
116
118
        """Open an existing branch, rooted at 'base' (url)"""
117
119
        t = get_transport(base)
118
120
        mutter("trying to open %r with transport %r", base, t)
119
 
        return _Branch(t)
 
121
        return BzrBranch(t)
120
122
 
121
123
    @staticmethod
122
124
    def open_containing(url):
131
133
        t = get_transport(url)
132
134
        while True:
133
135
            try:
134
 
                return _Branch(t), t.relpath(url)
 
136
                return BzrBranch(t), t.relpath(url)
135
137
            except NotBranchError:
136
138
                pass
137
139
            new_t = t.clone('..')
144
146
    def initialize(base):
145
147
        """Create a new branch, rooted at 'base' (url)"""
146
148
        t = get_transport(base)
147
 
        return _Branch(t, init=True)
 
149
        return BzrBranch(t, init=True)
148
150
 
149
151
    def setup_caching(self, cache_root):
150
152
        """Subclasses that care about caching should override this, and set
163
165
 
164
166
    nick = property(_get_nick, _set_nick)
165
167
        
166
 
 
167
 
class _Branch(Branch):
 
168
    def push_stores(self, branch_to):
 
169
        """Copy the content of this branches store to branch_to."""
 
170
        raise NotImplementedError('push_stores is abstract')
 
171
 
 
172
    def get_transaction(self):
 
173
        """Return the current active transaction.
 
174
 
 
175
        If no transaction is active, this returns a passthrough object
 
176
        for which all data is immediately flushed and no caching happens.
 
177
        """
 
178
        raise NotImplementedError('get_transaction is abstract')
 
179
 
 
180
    def lock_write(self):
 
181
        raise NotImplementedError('lock_write is abstract')
 
182
        
 
183
    def lock_read(self):
 
184
        raise NotImplementedError('lock_read is abstract')
 
185
 
 
186
    def unlock(self):
 
187
        raise NotImplementedError('unlock is abstract')
 
188
 
 
189
    def abspath(self, name):
 
190
        """Return absolute filename for something in the branch
 
191
        
 
192
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
193
        method and not a tree method.
 
194
        """
 
195
        raise NotImplementedError('abspath is abstract')
 
196
 
 
197
    def get_root_id(self):
 
198
        """Return the id of this branches root"""
 
199
        raise NotImplementedError('get_root_id is abstract')
 
200
 
 
201
    def print_file(self, file, revno):
 
202
        """Print `file` to stdout."""
 
203
        raise NotImplementedError('print_file is abstract')
 
204
 
 
205
    def append_revision(self, *revision_ids):
 
206
        raise NotImplementedError('append_revision is abstract')
 
207
 
 
208
    def set_revision_history(self, rev_history):
 
209
        raise NotImplementedError('set_revision_history is abstract')
 
210
 
 
211
    def get_revision_delta(self, revno):
 
212
        """Return the delta for one revision.
 
213
 
 
214
        The delta is relative to its mainline predecessor, or the
 
215
        empty tree for revision 1.
 
216
        """
 
217
        assert isinstance(revno, int)
 
218
        rh = self.revision_history()
 
219
        if not (1 <= revno <= len(rh)):
 
220
            raise InvalidRevisionNumber(revno)
 
221
 
 
222
        # revno is 1-based; list is 0-based
 
223
 
 
224
        new_tree = self.storage.revision_tree(rh[revno-1])
 
225
        if revno == 1:
 
226
            old_tree = EmptyTree()
 
227
        else:
 
228
            old_tree = self.storage.revision_tree(rh[revno-2])
 
229
 
 
230
        return compare_trees(old_tree, new_tree)
 
231
 
 
232
    def get_ancestry(self, revision_id):
 
233
        """Return a list of revision-ids integrated by a revision.
 
234
        
 
235
        This currently returns a list, but the ordering is not guaranteed:
 
236
        treat it as a set.
 
237
        """
 
238
        raise NotImplementedError('get_ancestry is abstract')
 
239
 
 
240
    def get_inventory(self, revision_id):
 
241
        """Get Inventory object by hash."""
 
242
        raise NotImplementedError('get_inventory is abstract')
 
243
 
 
244
    def get_inventory_xml(self, revision_id):
 
245
        """Get inventory XML as a file object."""
 
246
        raise NotImplementedError('get_inventory_xml is abstract')
 
247
 
 
248
    def get_inventory_sha1(self, revision_id):
 
249
        """Return the sha1 hash of the inventory entry."""
 
250
        raise NotImplementedError('get_inventory_sha1 is abstract')
 
251
 
 
252
    def get_revision_inventory(self, revision_id):
 
253
        """Return inventory of a past revision."""
 
254
        raise NotImplementedError('get_revision_inventory is abstract')
 
255
 
 
256
    def revision_history(self):
 
257
        """Return sequence of revision hashes on to this branch."""
 
258
        raise NotImplementedError('revision_history is abstract')
 
259
 
 
260
    def revno(self):
 
261
        """Return current revision number for this branch.
 
262
 
 
263
        That is equivalent to the number of revisions committed to
 
264
        this branch.
 
265
        """
 
266
        return len(self.revision_history())
 
267
 
 
268
    def last_revision(self):
 
269
        """Return last patch hash, or None if no history."""
 
270
        ph = self.revision_history()
 
271
        if ph:
 
272
            return ph[-1]
 
273
        else:
 
274
            return None
 
275
 
 
276
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
277
        """Return a list of new revisions that would perfectly fit.
 
278
        
 
279
        If self and other have not diverged, return a list of the revisions
 
280
        present in other, but missing from self.
 
281
 
 
282
        >>> from bzrlib.commit import commit
 
283
        >>> bzrlib.trace.silent = True
 
284
        >>> br1 = ScratchBranch()
 
285
        >>> br2 = ScratchBranch()
 
286
        >>> br1.missing_revisions(br2)
 
287
        []
 
288
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
289
        >>> br1.missing_revisions(br2)
 
290
        [u'REVISION-ID-1']
 
291
        >>> br2.missing_revisions(br1)
 
292
        []
 
293
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
294
        >>> br1.missing_revisions(br2)
 
295
        []
 
296
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
297
        >>> br1.missing_revisions(br2)
 
298
        [u'REVISION-ID-2A']
 
299
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
300
        >>> br1.missing_revisions(br2)
 
301
        Traceback (most recent call last):
 
302
        DivergedBranches: These branches have diverged.
 
303
        """
 
304
        self_history = self.revision_history()
 
305
        self_len = len(self_history)
 
306
        other_history = other.revision_history()
 
307
        other_len = len(other_history)
 
308
        common_index = min(self_len, other_len) -1
 
309
        if common_index >= 0 and \
 
310
            self_history[common_index] != other_history[common_index]:
 
311
            raise DivergedBranches(self, other)
 
312
 
 
313
        if stop_revision is None:
 
314
            stop_revision = other_len
 
315
        else:
 
316
            assert isinstance(stop_revision, int)
 
317
            if stop_revision > other_len:
 
318
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
319
        return other_history[self_len:stop_revision]
 
320
    
 
321
    def update_revisions(self, other, stop_revision=None):
 
322
        """Pull in new perfect-fit revisions."""
 
323
        raise NotImplementedError('update_revisions is abstract')
 
324
 
 
325
    def pullable_revisions(self, other, stop_revision):
 
326
        raise NotImplementedError('pullable_revisions is abstract')
 
327
        
 
328
    def revision_id_to_revno(self, revision_id):
 
329
        """Given a revision id, return its revno"""
 
330
        if revision_id is None:
 
331
            return 0
 
332
        history = self.revision_history()
 
333
        try:
 
334
            return history.index(revision_id) + 1
 
335
        except ValueError:
 
336
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
337
 
 
338
    def get_rev_id(self, revno, history=None):
 
339
        """Find the revision id of the specified revno."""
 
340
        if revno == 0:
 
341
            return None
 
342
        if history is None:
 
343
            history = self.revision_history()
 
344
        elif revno <= 0 or revno > len(history):
 
345
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
346
        return history[revno - 1]
 
347
 
 
348
    def working_tree(self):
 
349
        """Return a `Tree` for the working copy if this is a local branch."""
 
350
        raise NotImplementedError('working_tree is abstract')
 
351
 
 
352
    def pull(self, source, overwrite=False):
 
353
        raise NotImplementedError('pull is abstract')
 
354
 
 
355
    def basis_tree(self):
 
356
        """Return `Tree` object for last revision.
 
357
 
 
358
        If there are no revisions yet, return an `EmptyTree`.
 
359
        """
 
360
        return self.storage.revision_tree(self.last_revision())
 
361
 
 
362
    def rename_one(self, from_rel, to_rel):
 
363
        """Rename one file.
 
364
 
 
365
        This can change the directory or the filename or both.
 
366
        """
 
367
        raise NotImplementedError('rename_one is abstract')
 
368
 
 
369
    def move(self, from_paths, to_name):
 
370
        """Rename files.
 
371
 
 
372
        to_name must exist as a versioned directory.
 
373
 
 
374
        If to_name exists and is a directory, the files are moved into
 
375
        it, keeping their old names.  If it is a directory, 
 
376
 
 
377
        Note that to_name is only the last component of the new name;
 
378
        this doesn't change the directory.
 
379
 
 
380
        This returns a list of (from_path, to_path) pairs for each
 
381
        entry that is moved.
 
382
        """
 
383
        raise NotImplementedError('move is abstract')
 
384
 
 
385
    def get_parent(self):
 
386
        """Return the parent location of the branch.
 
387
 
 
388
        This is the default location for push/pull/missing.  The usual
 
389
        pattern is that the user can override it by specifying a
 
390
        location.
 
391
        """
 
392
        raise NotImplementedError('get_parent is abstract')
 
393
 
 
394
    def get_push_location(self):
 
395
        """Return the None or the location to push this branch to."""
 
396
        raise NotImplementedError('get_push_location is abstract')
 
397
 
 
398
    def set_push_location(self, location):
 
399
        """Set a new push location for this branch."""
 
400
        raise NotImplementedError('set_push_location is abstract')
 
401
 
 
402
    def set_parent(self, url):
 
403
        raise NotImplementedError('set_parent is abstract')
 
404
 
 
405
    def check_revno(self, revno):
 
406
        """\
 
407
        Check whether a revno corresponds to any revision.
 
408
        Zero (the NULL revision) is considered valid.
 
409
        """
 
410
        if revno != 0:
 
411
            self.check_real_revno(revno)
 
412
            
 
413
    def check_real_revno(self, revno):
 
414
        """\
 
415
        Check whether a revno corresponds to a real revision.
 
416
        Zero (the NULL revision) is considered invalid
 
417
        """
 
418
        if revno < 1 or revno > self.revno():
 
419
            raise InvalidRevisionNumber(revno)
 
420
        
 
421
    def sign_revision(self, revision_id, gpg_strategy):
 
422
        raise NotImplementedError('sign_revision is abstract')
 
423
 
 
424
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
425
        raise NotImplementedError('store_revision_signature is abstract')
 
426
 
 
427
class BzrBranch(Branch):
168
428
    """A branch stored in the actual filesystem.
169
429
 
170
430
    Note that it's "local" in the context of the filesystem; it doesn't
171
431
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
172
432
    it's writable, and can be accessed via the normal filesystem API.
173
433
 
 
434
    _lock_mode
 
435
        None, or 'r' or 'w'
 
436
 
 
437
    _lock_count
 
438
        If _lock_mode is true, a positive count of the number of times the
 
439
        lock has been taken.
 
440
 
 
441
    _lock
 
442
        Lock object from bzrlib.lock.
174
443
    """
175
444
    # We actually expect this class to be somewhat short-lived; part of its
176
445
    # purpose is to try to isolate what bits of the branch logic are tied to
177
446
    # filesystem access, so that in a later step, we can extricate them to
178
447
    # a separarte ("storage") class.
 
448
    _lock_mode = None
 
449
    _lock_count = None
 
450
    _lock = None
179
451
    _inventory_weave = None
180
452
    
181
453
    # Map some sort of prefix into a namespace
184
456
    REVISION_NAMESPACES = {}
185
457
 
186
458
    def push_stores(self, branch_to):
187
 
        """Copy the content of this branches store to branch_to."""
 
459
        """See Branch.push_stores."""
188
460
        if (self._branch_format != branch_to._branch_format
189
461
            or self._branch_format != 4):
190
462
            from bzrlib.fetch import greedy_fetch
255
527
 
256
528
    base = property(_get_base, doc="The URL for the root of this branch.")
257
529
 
 
530
    def _finish_transaction(self):
 
531
        """Exit the current transaction."""
 
532
        return self.control_files._finish_transaction()
 
533
 
 
534
    def get_transaction(self):
 
535
        """Return the current active transaction.
 
536
 
 
537
        If no transaction is active, this returns a passthrough object
 
538
        for which all data is immediately flushed and no caching happens.
 
539
        """
 
540
        # this is an explicit function so that we can do tricky stuff
 
541
        # when the storage in rev_storage is elsewhere.
 
542
        # we probably need to hook the two 'lock a location' and 
 
543
        # 'have a transaction' together more delicately, so that
 
544
        # we can have two locks (branch and storage) and one transaction
 
545
        # ... and finishing the transaction unlocks both, but unlocking
 
546
        # does not. - RBC 20051121
 
547
        return self.control_files.get_transaction()
 
548
 
 
549
    def _set_transaction(self, transaction):
 
550
        """Set a new active transaction."""
 
551
        return self.control_files._set_transaction(transaction)
 
552
 
258
553
    def abspath(self, name):
259
 
        """Return absolute filename for something in the branch
260
 
        
261
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
262
 
        method and not a tree method?
263
 
        """
 
554
        """See Branch.abspath."""
264
555
        return self.control_files._transport.abspath(name)
265
556
 
266
557
    def _make_control(self):
327
618
                            'or remove the .bzr directory'
328
619
                            ' and "bzr init" again'])
329
620
 
 
621
    @needs_read_lock
330
622
    def get_root_id(self):
331
 
        """Return the id of this branches root"""
 
623
        """See Branch.get_root_id."""
332
624
        inv = self.storage.get_inventory(self.last_revision())
333
625
        return inv.root.file_id
334
626
 
347
639
        self.storage.unlock()
348
640
        self.control_files.unlock()
349
641
 
350
 
    @needs_write_lock
351
 
    def set_root_id(self, file_id):
352
 
        inv = self.working_tree().read_working_inventory()
353
 
        orig_root_id = inv.root.file_id
354
 
        del inv._byid[inv.root.file_id]
355
 
        inv.root.file_id = file_id
356
 
        inv._byid[inv.root.file_id] = inv.root
357
 
        for fid in inv:
358
 
            entry = inv[fid]
359
 
            if entry.parent_id in (None, orig_root_id):
360
 
                entry.parent_id = inv.root.file_id
361
 
        self._write_inventory(inv)
362
 
 
363
 
    @needs_write_lock
364
 
    def _write_inventory(self, inv):
365
 
        """Update the working inventory.
366
 
 
367
 
        That is to say, the inventory describing changes underway, that
368
 
        will be committed to the next revision.
369
 
        """
370
 
        from cStringIO import StringIO
371
 
        sio = StringIO()
372
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
373
 
        sio.seek(0)
374
 
        # Transport handles atomicity
375
 
        self.control_files.put_utf8('inventory', sio)
376
 
        
377
 
        mutter('wrote working inventory')
378
 
            
379
 
    @needs_write_lock
380
 
    def add(self, files, ids=None):
381
 
        """Make files versioned.
382
 
 
383
 
        Note that the command line normally calls smart_add instead,
384
 
        which can automatically recurse.
385
 
 
386
 
        This puts the files in the Added state, so that they will be
387
 
        recorded by the next commit.
388
 
 
389
 
        files
390
 
            List of paths to add, relative to the base of the tree.
391
 
 
392
 
        ids
393
 
            If set, use these instead of automatically generated ids.
394
 
            Must be the same length as the list of files, but may
395
 
            contain None for ids that are to be autogenerated.
396
 
 
397
 
        TODO: Perhaps have an option to add the ids even if the files do
398
 
              not (yet) exist.
399
 
 
400
 
        TODO: Perhaps yield the ids and paths as they're added.
401
 
        """
402
 
        # TODO: Re-adding a file that is removed in the working copy
403
 
        # should probably put it back with the previous ID.
404
 
        if isinstance(files, basestring):
405
 
            assert(ids is None or isinstance(ids, basestring))
406
 
            files = [files]
407
 
            if ids is not None:
408
 
                ids = [ids]
409
 
 
410
 
        if ids is None:
411
 
            ids = [None] * len(files)
412
 
        else:
413
 
            assert(len(ids) == len(files))
414
 
 
415
 
        inv = self.working_tree().read_working_inventory()
416
 
        for f,file_id in zip(files, ids):
417
 
            if is_control_file(f):
418
 
                raise BzrError("cannot add control file %s" % quotefn(f))
419
 
 
420
 
            fp = splitpath(f)
421
 
 
422
 
            if len(fp) == 0:
423
 
                raise BzrError("cannot add top-level %r" % f)
424
 
 
425
 
            fullpath = os.path.normpath(self.abspath(f))
426
 
 
427
 
            try:
428
 
                kind = file_kind(fullpath)
429
 
            except OSError:
430
 
                # maybe something better?
431
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
432
 
 
433
 
            if not InventoryEntry.versionable_kind(kind):
434
 
                raise BzrError('cannot add: not a versionable file ('
435
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
436
 
 
437
 
            if file_id is None:
438
 
                file_id = gen_file_id(f)
439
 
            inv.add_path(f, kind=kind, file_id=file_id)
440
 
 
441
 
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
442
 
 
443
 
        self._write_inventory(inv)
444
 
 
445
642
    @needs_read_lock
446
643
    def print_file(self, file, revno):
447
 
        """Print `file` to stdout."""
 
644
        """See Branch.print_file."""
448
645
        return self.storage.print_file(file, self.get_rev_id(revno))
449
646
 
450
 
    def unknowns(self):
451
 
        """Return all unknown files.
452
 
 
453
 
        These are files in the working directory that are not versioned or
454
 
        control files or ignored.
455
 
        
456
 
        >>> from bzrlib.workingtree import WorkingTree
457
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
458
 
        >>> map(str, b.unknowns())
459
 
        ['foo']
460
 
        >>> b.add('foo')
461
 
        >>> list(b.unknowns())
462
 
        []
463
 
        >>> WorkingTree(b.base, b).remove('foo')
464
 
        >>> list(b.unknowns())
465
 
        [u'foo']
466
 
        """
467
 
        return self.working_tree().unknowns()
468
 
 
469
647
    @needs_write_lock
470
648
    def append_revision(self, *revision_ids):
 
649
        """See Branch.append_revision."""
471
650
        for revision_id in revision_ids:
472
651
            mutter("add {%s} to revision-history" % revision_id)
473
652
        rev_history = self.revision_history()
476
655
 
477
656
    @needs_write_lock
478
657
    def set_revision_history(self, rev_history):
 
658
        """See Branch.set_revision_history."""
479
659
        self.control_files.put_utf8(
480
660
            'revision-history', '\n'.join(rev_history))
481
661
 
482
 
    def get_revision_delta(self, revno):
483
 
        """Return the delta for one revision.
484
 
 
485
 
        The delta is relative to its mainline predecessor, or the
486
 
        empty tree for revision 1.
487
 
        """
488
 
        assert isinstance(revno, int)
489
 
        rh = self.revision_history()
490
 
        if not (1 <= revno <= len(rh)):
491
 
            raise InvalidRevisionNumber(revno)
492
 
 
493
 
        # revno is 1-based; list is 0-based
494
 
 
495
 
        new_tree = self.storage.revision_tree(rh[revno-1])
496
 
        if revno == 1:
497
 
            old_tree = EmptyTree()
498
 
        else:
499
 
            old_tree = self.storage.revision_tree(rh[revno-2])
500
 
 
501
 
        return compare_trees(old_tree, new_tree)
502
 
 
503
662
    def get_ancestry(self, revision_id):
504
 
        """Return a list of revision-ids integrated by a revision.
505
 
        
506
 
        This currently returns a list, but the ordering is not guaranteed:
507
 
        treat it as a set.
508
 
        """
 
663
        """See Branch.get_ancestry."""
509
664
        if revision_id is None:
510
665
            return [None]
511
666
        w = self.storage.get_inventory_weave()
512
667
        return [None] + map(w.idx_to_name,
513
668
                            w.inclusions([w.lookup(revision_id)]))
514
669
 
 
670
    def _get_inventory_weave(self):
 
671
        return self.storage.control_weaves.get_weave('inventory',
 
672
                                             self.get_transaction())
 
673
 
 
674
    def get_inventory(self, revision_id):
 
675
        """See Branch.get_inventory."""
 
676
        xml = self.get_inventory_xml(revision_id)
 
677
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
678
 
 
679
    def get_inventory_xml(self, revision_id):
 
680
        """See Branch.get_inventory_xml."""
 
681
        try:
 
682
            assert isinstance(revision_id, basestring), type(revision_id)
 
683
            iw = self._get_inventory_weave()
 
684
            return iw.get_text(iw.lookup(revision_id))
 
685
        except IndexError:
 
686
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
687
 
 
688
    def get_inventory_sha1(self, revision_id):
 
689
        """See Branch.get_inventory_sha1."""
 
690
        return self.get_revision(revision_id).inventory_sha1
 
691
 
 
692
    def get_revision_inventory(self, revision_id):
 
693
        """See Branch.get_revision_inventory."""
 
694
        # TODO: Unify this with get_inventory()
 
695
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
696
        # must be the same as its revision, so this is trivial.
 
697
        if revision_id == None:
 
698
            # This does not make sense: if there is no revision,
 
699
            # then it is the current tree inventory surely ?!
 
700
            # and thus get_root_id() is something that looks at the last
 
701
            # commit on the branch, and the get_root_id is an inventory check.
 
702
            raise NotImplementedError
 
703
            # return Inventory(self.get_root_id())
 
704
        else:
 
705
            return self.get_inventory(revision_id)
 
706
 
515
707
    @needs_read_lock
516
708
    def revision_history(self):
517
 
        """Return sequence of revision hashes on to this branch."""
 
709
        """See Branch.revision_history."""
518
710
        # FIXME are transactions bound to control files ? RBC 20051121
519
711
        transaction = self.get_transaction()
520
712
        history = transaction.map.find_revision_history()
529
721
        # transaction.register_clean(history, precious=True)
530
722
        return list(history)
531
723
 
532
 
    def revno(self):
533
 
        """Return current revision number for this branch.
534
 
 
535
 
        That is equivalent to the number of revisions committed to
536
 
        this branch.
537
 
        """
538
 
        return len(self.revision_history())
539
 
 
540
 
    def last_revision(self):
541
 
        """Return last patch hash, or None if no history.
542
 
        """
543
 
        ph = self.revision_history()
544
 
        if ph:
545
 
            return ph[-1]
546
 
        else:
547
 
            return None
548
 
 
549
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
550
 
        """Return a list of new revisions that would perfectly fit.
551
 
        
552
 
        If self and other have not diverged, return a list of the revisions
553
 
        present in other, but missing from self.
554
 
 
555
 
        >>> from bzrlib.commit import commit
556
 
        >>> bzrlib.trace.silent = True
557
 
        >>> br1 = ScratchBranch()
558
 
        >>> br2 = ScratchBranch()
559
 
        >>> br1.missing_revisions(br2)
560
 
        []
561
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
562
 
        >>> br1.missing_revisions(br2)
563
 
        [u'REVISION-ID-1']
564
 
        >>> br2.missing_revisions(br1)
565
 
        []
566
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
567
 
        >>> br1.missing_revisions(br2)
568
 
        []
569
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
570
 
        >>> br1.missing_revisions(br2)
571
 
        [u'REVISION-ID-2A']
572
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
573
 
        >>> br1.missing_revisions(br2)
574
 
        Traceback (most recent call last):
575
 
        DivergedBranches: These branches have diverged.
576
 
        """
577
 
        self_history = self.revision_history()
578
 
        self_len = len(self_history)
579
 
        other_history = other.revision_history()
580
 
        other_len = len(other_history)
581
 
        common_index = min(self_len, other_len) -1
582
 
        if common_index >= 0 and \
583
 
            self_history[common_index] != other_history[common_index]:
584
 
            raise DivergedBranches(self, other)
585
 
 
586
 
        if stop_revision is None:
587
 
            stop_revision = other_len
588
 
        else:
589
 
            assert isinstance(stop_revision, int)
590
 
            if stop_revision > other_len:
591
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
592
 
        return other_history[self_len:stop_revision]
593
 
 
594
724
    def update_revisions(self, other, stop_revision=None):
595
 
        """Pull in new perfect-fit revisions."""
 
725
        """See Branch.update_revisions."""
596
726
        from bzrlib.fetch import greedy_fetch
597
727
        if stop_revision is None:
598
728
            stop_revision = other.last_revision()
607
737
            self.append_revision(*pullable_revs)
608
738
 
609
739
    def pullable_revisions(self, other, stop_revision):
 
740
        """See Branch.pullable_revisions."""
610
741
        other_revno = other.revision_id_to_revno(stop_revision)
611
742
        try:
612
743
            return self.missing_revisions(other, other_revno)
623
754
                else:
624
755
                    raise e
625
756
        
626
 
    def commit(self, *args, **kw):
627
 
        from bzrlib.commit import Commit
628
 
        Commit().commit(self, *args, **kw)
629
 
    
630
 
    def revision_id_to_revno(self, revision_id):
631
 
        """Given a revision id, return its revno"""
632
 
        if revision_id is None:
633
 
            return 0
634
 
        history = self.revision_history()
635
 
        try:
636
 
            return history.index(revision_id) + 1
637
 
        except ValueError:
638
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
639
 
 
640
 
    def get_rev_id(self, revno, history=None):
641
 
        """Find the revision id of the specified revno."""
642
 
        if revno == 0:
643
 
            return None
644
 
        if history is None:
645
 
            history = self.revision_history()
646
 
        elif revno <= 0 or revno > len(history):
647
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
648
 
        return history[revno - 1]
649
 
 
650
757
    def working_tree(self):
651
 
        """Return a `Tree` for the working copy."""
 
758
        """See Branch.working_tree."""
652
759
        from bzrlib.workingtree import WorkingTree
653
 
        # TODO: In the future, perhaps WorkingTree should utilize Transport
654
 
        # RobertCollins 20051003 - I don't think it should - working trees are
655
 
        # much more complex to keep consistent than our careful .bzr subset.
656
 
        # instead, we should say that working trees are local only, and optimise
657
 
        # for that.
658
760
        if self.base.find('://') != -1:
659
761
            raise NoWorkingTree(self.base)
660
762
        return WorkingTree(self.base, branch=self)
661
763
 
662
764
    @needs_write_lock
663
765
    def pull(self, source, overwrite=False):
 
766
        """See Branch.pull."""
664
767
        source.lock_read()
665
768
        try:
 
769
            old_count = len(self.revision_history())
666
770
            try:
667
771
                self.update_revisions(source)
668
772
            except DivergedBranches:
669
773
                if not overwrite:
670
774
                    raise
671
775
                self.set_revision_history(source.revision_history())
 
776
            new_count = len(self.revision_history())
 
777
            return new_count - old_count
672
778
        finally:
673
779
            source.unlock()
674
780
 
675
 
    def basis_tree(self):
676
 
        """Return `Tree` object for last revision.
677
 
 
678
 
        If there are no revisions yet, return an `EmptyTree`.
679
 
        """
680
 
        return self.storage.revision_tree(self.last_revision())
681
 
 
682
 
    @needs_write_lock
683
 
    def rename_one(self, from_rel, to_rel):
684
 
        """Rename one file.
685
 
 
686
 
        This can change the directory or the filename or both.
687
 
        """
688
 
        tree = self.working_tree()
689
 
        inv = tree.inventory
690
 
        if not tree.has_filename(from_rel):
691
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
692
 
        if tree.has_filename(to_rel):
693
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
694
 
 
695
 
        file_id = inv.path2id(from_rel)
696
 
        if file_id == None:
697
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
698
 
 
699
 
        if inv.path2id(to_rel):
700
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
701
 
 
702
 
        to_dir, to_tail = os.path.split(to_rel)
703
 
        to_dir_id = inv.path2id(to_dir)
704
 
        if to_dir_id == None and to_dir != '':
705
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
706
 
 
707
 
        mutter("rename_one:")
708
 
        mutter("  file_id    {%s}" % file_id)
709
 
        mutter("  from_rel   %r" % from_rel)
710
 
        mutter("  to_rel     %r" % to_rel)
711
 
        mutter("  to_dir     %r" % to_dir)
712
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
713
 
 
714
 
        inv.rename(file_id, to_dir_id, to_tail)
715
 
 
716
 
        from_abs = self.abspath(from_rel)
717
 
        to_abs = self.abspath(to_rel)
718
 
        try:
719
 
            rename(from_abs, to_abs)
720
 
        except OSError, e:
721
 
            raise BzrError("failed to rename %r to %r: %s"
722
 
                    % (from_abs, to_abs, e[1]),
723
 
                    ["rename rolled back"])
724
 
 
725
 
        self._write_inventory(inv)
726
 
 
727
 
    @needs_write_lock
728
 
    def move(self, from_paths, to_name):
729
 
        """Rename files.
730
 
 
731
 
        to_name must exist as a versioned directory.
732
 
 
733
 
        If to_name exists and is a directory, the files are moved into
734
 
        it, keeping their old names.  If it is a directory, 
735
 
 
736
 
        Note that to_name is only the last component of the new name;
737
 
        this doesn't change the directory.
738
 
 
739
 
        This returns a list of (from_path, to_path) pairs for each
740
 
        entry that is moved.
741
 
        """
742
 
        result = []
743
 
        ## TODO: Option to move IDs only
744
 
        assert not isinstance(from_paths, basestring)
745
 
        tree = self.working_tree()
746
 
        inv = tree.inventory
747
 
        to_abs = self.abspath(to_name)
748
 
        if not isdir(to_abs):
749
 
            raise BzrError("destination %r is not a directory" % to_abs)
750
 
        if not tree.has_filename(to_name):
751
 
            raise BzrError("destination %r not in working directory" % to_abs)
752
 
        to_dir_id = inv.path2id(to_name)
753
 
        if to_dir_id == None and to_name != '':
754
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
755
 
        to_dir_ie = inv[to_dir_id]
756
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
757
 
            raise BzrError("destination %r is not a directory" % to_abs)
758
 
 
759
 
        to_idpath = inv.get_idpath(to_dir_id)
760
 
 
761
 
        for f in from_paths:
762
 
            if not tree.has_filename(f):
763
 
                raise BzrError("%r does not exist in working tree" % f)
764
 
            f_id = inv.path2id(f)
765
 
            if f_id == None:
766
 
                raise BzrError("%r is not versioned" % f)
767
 
            name_tail = splitpath(f)[-1]
768
 
            dest_path = appendpath(to_name, name_tail)
769
 
            if tree.has_filename(dest_path):
770
 
                raise BzrError("destination %r already exists" % dest_path)
771
 
            if f_id in to_idpath:
772
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
773
 
 
774
 
        # OK, so there's a race here, it's possible that someone will
775
 
        # create a file in this interval and then the rename might be
776
 
        # left half-done.  But we should have caught most problems.
777
 
 
778
 
        for f in from_paths:
779
 
            name_tail = splitpath(f)[-1]
780
 
            dest_path = appendpath(to_name, name_tail)
781
 
            result.append((f, dest_path))
782
 
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
783
 
            try:
784
 
                rename(self.abspath(f), self.abspath(dest_path))
785
 
            except OSError, e:
786
 
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
787
 
                        ["rename rolled back"])
788
 
 
789
 
        self._write_inventory(inv)
790
 
        return result
791
 
 
792
 
 
793
 
    def revert(self, filenames, old_tree=None, backups=True):
794
 
        """Restore selected files to the versions from a previous tree.
795
 
 
796
 
        backups
797
 
            If true (default) backups are made of files before
798
 
            they're renamed.
799
 
        """
800
 
        from bzrlib.atomicfile import AtomicFile
801
 
        from bzrlib.osutils import backup_file
802
 
        
803
 
        inv = self.working_tree().read_working_inventory()
804
 
        if old_tree is None:
805
 
            old_tree = self.basis_tree()
806
 
        old_inv = old_tree.inventory
807
 
 
808
 
        nids = []
809
 
        for fn in filenames:
810
 
            file_id = inv.path2id(fn)
811
 
            if not file_id:
812
 
                raise NotVersionedError(path=fn)
813
 
            if not old_inv.has_id(file_id):
814
 
                raise BzrError("file not present in old tree", fn, file_id)
815
 
            nids.append((fn, file_id))
816
 
            
817
 
        # TODO: Rename back if it was previously at a different location
818
 
 
819
 
        # TODO: If given a directory, restore the entire contents from
820
 
        # the previous version.
821
 
 
822
 
        # TODO: Make a backup to a temporary file.
823
 
 
824
 
        # TODO: If the file previously didn't exist, delete it?
825
 
        for fn, file_id in nids:
826
 
            backup_file(fn)
827
 
            
828
 
            f = AtomicFile(fn, 'wb')
829
 
            try:
830
 
                f.write(old_tree.get_file(file_id).read())
831
 
                f.commit()
832
 
            finally:
833
 
                f.close()
834
 
 
835
 
 
836
 
    def pending_merges(self):
837
 
        """Return a list of pending merges.
838
 
 
839
 
        These are revisions that have been merged into the working
840
 
        directory but not yet committed.
841
 
        """
842
 
        cfn = self.control_files._rel_controlfilename('pending-merges')
843
 
        if not self.control_files._transport.has(cfn):
844
 
            return []
845
 
        p = []
846
 
        for l in self.control_files.controlfile(
847
 
                'pending-merges', 'r').readlines():
848
 
            p.append(l.rstrip('\n'))
849
 
        return p
850
 
 
851
 
 
852
 
    def add_pending_merge(self, *revision_ids):
853
 
        # TODO: Perhaps should check at this point that the
854
 
        # history of the revision is actually present?
855
 
        p = self.pending_merges()
856
 
        updated = False
857
 
        for rev_id in revision_ids:
858
 
            if rev_id in p:
859
 
                continue
860
 
            p.append(rev_id)
861
 
            updated = True
862
 
        if updated:
863
 
            self.set_pending_merges(p)
864
 
 
865
 
    @needs_write_lock
866
 
    def set_pending_merges(self, rev_list):
867
 
        self.control_files.put_utf8(
868
 
            'pending-merges', '\n'.join(rev_list))
869
 
 
870
781
    def get_parent(self):
871
 
        """Return the parent location of the branch.
872
 
 
873
 
        This is the default location for push/pull/missing.  The usual
874
 
        pattern is that the user can override it by specifying a
875
 
        location.
876
 
        """
 
782
        """See Branch.get_parent."""
877
783
        import errno
878
784
        _locs = ['parent', 'pull', 'x-pull']
879
785
        for l in _locs:
885
791
        return None
886
792
 
887
793
    def get_push_location(self):
888
 
        """Return the None or the location to push this branch to."""
 
794
        """See Branch.get_push_location."""
889
795
        config = bzrlib.config.BranchConfig(self)
890
796
        push_loc = config.get_user_option('push_location')
891
797
        return push_loc
892
798
 
893
799
    def set_push_location(self, location):
894
 
        """Set a new push location for this branch."""
 
800
        """See Branch.set_push_location."""
895
801
        config = bzrlib.config.LocationConfig(self.base)
896
802
        config.set_user_option('push_location', location)
897
803
 
898
804
    @needs_write_lock
899
805
    def set_parent(self, url):
 
806
        """See Branch.set_parent."""
900
807
        # TODO: Maybe delete old location files?
901
808
        from bzrlib.atomicfile import AtomicFile
902
809
        f = AtomicFile(self.control_files.controlfilename('parent'))
909
816
    def tree_config(self):
910
817
        return TreeConfig(self)
911
818
 
912
 
    def check_revno(self, revno):
913
 
        """\
914
 
        Check whether a revno corresponds to any revision.
915
 
        Zero (the NULL revision) is considered valid.
916
 
        """
917
 
        if revno != 0:
918
 
            self.check_real_revno(revno)
919
 
            
920
 
    def check_real_revno(self, revno):
921
 
        """\
922
 
        Check whether a revno corresponds to a real revision.
923
 
        Zero (the NULL revision) is considered invalid
924
 
        """
925
 
        if revno < 1 or revno > self.revno():
926
 
            raise InvalidRevisionNumber(revno)
927
 
        
928
 
    def _finish_transaction(self):
929
 
        """Exit the current transaction."""
930
 
        return self.control_files._finish_transaction()
931
 
 
932
 
    def get_transaction(self):
933
 
        """Return the current active transaction.
934
 
 
935
 
        If no transaction is active, this returns a passthrough object
936
 
        for which all data is immediately flushed and no caching happens.
937
 
        """
938
 
        # this is an explicit function so that we can do tricky stuff
939
 
        # when the storage in rev_storage is elsewhere.
940
 
        # we probably need to hook the two 'lock a location' and 
941
 
        # 'have a transaction' together more delicately, so that
942
 
        # we can have two locks (branch and storage) and one transaction
943
 
        # ... and finishing the transaction unlocks both, but unlocking
944
 
        # does not. - RBC 20051121
945
 
        return self.control_files.get_transaction()
946
 
 
947
 
    def _set_transaction(self, transaction):
948
 
        """Set a new active transaction."""
949
 
        return self.control_files._set_transaction(transaction)
950
 
 
951
 
class ScratchBranch(_Branch):
 
819
    def sign_revision(self, revision_id, gpg_strategy):
 
820
        """See Branch.sign_revision."""
 
821
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
822
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
823
 
 
824
    @needs_write_lock
 
825
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
826
        """See Branch.store_revision_signature."""
 
827
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
828
                                revision_id, "sig")
 
829
 
 
830
 
 
831
class ScratchBranch(BzrBranch):
952
832
    """Special test class: a branch that cleans up after itself.
953
833
 
954
834
    >>> b = ScratchBranch()
1017
897
            break
1018
898
        filename = head
1019
899
    return False
1020
 
 
1021
 
 
1022
 
def gen_file_id(name):
1023
 
    """Return new file id.
1024
 
 
1025
 
    This should probably generate proper UUIDs, but for the moment we
1026
 
    cope with just randomness because running uuidgen every time is
1027
 
    slow."""
1028
 
    import re
1029
 
    from binascii import hexlify
1030
 
    from time import time
1031
 
 
1032
 
    # get last component
1033
 
    idx = name.rfind('/')
1034
 
    if idx != -1:
1035
 
        name = name[idx+1 : ]
1036
 
    idx = name.rfind('\\')
1037
 
    if idx != -1:
1038
 
        name = name[idx+1 : ]
1039
 
 
1040
 
    # make it not a hidden file
1041
 
    name = name.lstrip('.')
1042
 
 
1043
 
    # remove any wierd characters; we don't escape them but rather
1044
 
    # just pull them out
1045
 
    name = re.sub(r'[^\w.]', '', name)
1046
 
 
1047
 
    s = hexlify(rand_bytes(8))
1048
 
    return '-'.join((name, compact_date(time()), s))
1049
 
 
1050
 
 
1051
 
def gen_root_id():
1052
 
    """Return a new tree-root file id."""
1053
 
    return gen_file_id('TREE_ROOT')
1054
 
 
1055