~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-09-22 06:19:33 UTC
  • Revision ID: mbp@sourcefrog.net-20050922061933-4b71d0f1e205b153
- keep track of number of checked revisions

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
import sys, os
 
18
import sys
 
19
import os
 
20
from cStringIO import StringIO
19
21
 
20
22
import bzrlib
21
23
from bzrlib.trace import mutter, note
22
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
 
24
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
 
25
     splitpath, \
23
26
     sha_file, appendpath, file_kind
24
 
from bzrlib.errors import BzrError
25
 
 
26
 
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
 
27
 
 
28
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
29
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
30
                           LockError)
 
31
from bzrlib.textui import show_status
 
32
from bzrlib.revision import Revision, validate_revision_id
 
33
from bzrlib.delta import compare_trees
 
34
from bzrlib.tree import EmptyTree, RevisionTree
 
35
from bzrlib.inventory import Inventory
 
36
from bzrlib.weavestore import WeaveStore
 
37
from bzrlib.store import ImmutableStore
 
38
import bzrlib.xml5
 
39
import bzrlib.ui
 
40
 
 
41
 
 
42
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
43
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
27
44
## TODO: Maybe include checks for common corruption of newlines, etc?
28
45
 
29
46
 
 
47
# TODO: Some operations like log might retrieve the same revisions
 
48
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
49
# cache in memory to make this faster.  In general anything can be
 
50
# cached in memory between lock and unlock operations.
 
51
 
 
52
# TODO: please move the revision-string syntax stuff out of the branch
 
53
# object; it's clutter
 
54
 
30
55
 
31
56
def find_branch(f, **args):
32
57
    if f and (f.startswith('http://') or f.startswith('https://')):
75
100
        if tail:
76
101
            s.insert(0, tail)
77
102
    else:
78
 
        from errors import NotBranchError
79
103
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
80
104
 
81
105
    return os.sep.join(s)
89
113
    It is not necessary that f exists.
90
114
 
91
115
    Basically we keep looking up until we find the control directory or
92
 
    run into the root."""
 
116
    run into the root.  If there isn't one, raises NotBranchError.
 
117
    """
93
118
    if f == None:
94
119
        f = os.getcwd()
95
120
    elif hasattr(os.path, 'realpath'):
108
133
        head, tail = os.path.split(f)
109
134
        if head == f:
110
135
            # reached the root, whatever that may be
111
 
            raise BzrError('%r is not in a branch' % orig_f)
 
136
            raise NotBranchError('%s is not in a branch' % orig_f)
112
137
        f = head
113
 
    
 
138
 
 
139
 
 
140
 
 
141
# XXX: move into bzrlib.errors; subclass BzrError    
114
142
class DivergedBranches(Exception):
115
143
    def __init__(self, branch1, branch2):
116
144
        self.branch1 = branch1
118
146
        Exception.__init__(self, "These branches have diverged.")
119
147
 
120
148
 
121
 
class NoSuchRevision(BzrError):
122
 
    def __init__(self, branch, revision):
123
 
        self.branch = branch
124
 
        self.revision = revision
125
 
        msg = "Branch %s has no revision %d" % (branch, revision)
126
 
        BzrError.__init__(self, msg)
127
 
 
128
 
 
129
149
######################################################################
130
150
# branch objects
131
151
 
149
169
    _lock_mode = None
150
170
    _lock_count = None
151
171
    _lock = None
 
172
    _inventory_weave = None
152
173
    
153
174
    # Map some sort of prefix into a namespace
154
175
    # stuff like "revno:10", "revid:", etc.
155
176
    # This should match a prefix with a function which accepts
156
177
    REVISION_NAMESPACES = {}
157
178
 
158
 
    def __init__(self, base, init=False, find_root=True):
 
179
    def __init__(self, base, init=False, find_root=True,
 
180
                 relax_version_check=False):
159
181
        """Create new branch object at a particular location.
160
182
 
161
183
        base -- Base directory for the branch.
167
189
        find_root -- If true and init is false, find the root of the
168
190
             existing branch containing base.
169
191
 
 
192
        relax_version_check -- If true, the usual check for the branch
 
193
            version is not applied.  This is intended only for
 
194
            upgrade/recovery type use; it's not guaranteed that
 
195
            all operations will work on old format branches.
 
196
 
170
197
        In the test suite, creation of new trees is tested using the
171
198
        `ScratchBranch` class.
172
199
        """
173
 
        from bzrlib.store import ImmutableStore
174
200
        if init:
175
201
            self.base = os.path.realpath(base)
176
202
            self._make_control()
179
205
        else:
180
206
            self.base = os.path.realpath(base)
181
207
            if not isdir(self.controlfilename('.')):
182
 
                from errors import NotBranchError
183
 
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
184
 
                                     ['use "bzr init" to initialize a new working tree',
185
 
                                      'current bzr can only operate from top-of-tree'])
186
 
        self._check_format()
187
 
 
188
 
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
189
 
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
190
 
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
 
208
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
 
209
                                     ['use "bzr init" to initialize a '
 
210
                                      'new working tree'])
 
211
        self._check_format(relax_version_check)
 
212
        cfn = self.controlfilename
 
213
        if self._branch_format == 4:
 
214
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
 
215
            self.text_store = ImmutableStore(cfn('text-store'))
 
216
        elif self._branch_format == 5:
 
217
            self.control_weaves = WeaveStore(cfn([]))
 
218
            self.weave_store = WeaveStore(cfn('weaves'))
 
219
        self.revision_store = ImmutableStore(cfn('revision-store'))
191
220
 
192
221
 
193
222
    def __str__(self):
204
233
            self._lock.unlock()
205
234
 
206
235
 
207
 
 
208
236
    def lock_write(self):
209
237
        if self._lock_mode:
210
238
            if self._lock_mode != 'w':
211
 
                from errors import LockError
212
239
                raise LockError("can't upgrade to a write lock from %r" %
213
240
                                self._lock_mode)
214
241
            self._lock_count += 1
220
247
            self._lock_count = 1
221
248
 
222
249
 
223
 
 
224
250
    def lock_read(self):
225
251
        if self._lock_mode:
226
252
            assert self._lock_mode in ('r', 'w'), \
233
259
            self._lock_mode = 'r'
234
260
            self._lock_count = 1
235
261
                        
236
 
 
237
 
            
238
262
    def unlock(self):
239
263
        if not self._lock_mode:
240
 
            from errors import LockError
241
264
            raise LockError('branch %r is not locked' % (self))
242
265
 
243
266
        if self._lock_count > 1:
247
270
            self._lock = None
248
271
            self._lock_mode = self._lock_count = None
249
272
 
250
 
 
251
273
    def abspath(self, name):
252
274
        """Return absolute filename for something in the branch"""
253
275
        return os.path.join(self.base, name)
254
276
 
255
 
 
256
277
    def relpath(self, path):
257
278
        """Return path relative to this branch of something inside it.
258
279
 
259
280
        Raises an error if path is not in this branch."""
260
281
        return _relpath(self.base, path)
261
282
 
262
 
 
263
283
    def controlfilename(self, file_or_path):
264
284
        """Return location relative to branch."""
265
285
        if isinstance(file_or_path, basestring):
292
312
        else:
293
313
            raise BzrError("invalid controlfile mode %r" % mode)
294
314
 
295
 
 
296
 
 
297
315
    def _make_control(self):
298
 
        from bzrlib.inventory import Inventory
299
 
        from bzrlib.xml import pack_xml
300
 
        
301
316
        os.mkdir(self.controlfilename([]))
302
317
        self.controlfile('README', 'w').write(
303
318
            "This is a Bazaar-NG control directory.\n"
304
319
            "Do not change any files in this directory.\n")
305
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
306
 
        for d in ('text-store', 'inventory-store', 'revision-store'):
 
320
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
 
321
        for d in ('text-store', 'revision-store',
 
322
                  'weaves'):
307
323
            os.mkdir(self.controlfilename(d))
308
 
        for f in ('revision-history', 'merged-patches',
309
 
                  'pending-merged-patches', 'branch-name',
 
324
        for f in ('revision-history',
 
325
                  'branch-name',
310
326
                  'branch-lock',
311
327
                  'pending-merges'):
312
328
            self.controlfile(f, 'w').write('')
313
329
        mutter('created control directory in ' + self.base)
314
330
 
315
 
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
316
 
 
317
 
 
318
 
    def _check_format(self):
 
331
        # if we want per-tree root ids then this is the place to set
 
332
        # them; they're not needed for now and so ommitted for
 
333
        # simplicity.
 
334
        f = self.controlfile('inventory','w')
 
335
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
 
336
        
 
337
 
 
338
 
 
339
    def _check_format(self, relax_version_check):
319
340
        """Check this branch format is supported.
320
341
 
321
 
        The current tool only supports the current unstable format.
 
342
        The format level is stored, as an integer, in
 
343
        self._branch_format for code that needs to check it later.
322
344
 
323
345
        In the future, we might need different in-memory Branch
324
346
        classes to support downlevel branches.  But not yet.
325
347
        """
326
 
        # This ignores newlines so that we can open branches created
327
 
        # on Windows from Linux and so on.  I think it might be better
328
 
        # to always make all internal files in unix format.
329
348
        fmt = self.controlfile('branch-format', 'r').read()
330
 
        fmt.replace('\r\n', '')
331
 
        if fmt != BZR_BRANCH_FORMAT:
332
 
            raise BzrError('sorry, branch format %r not supported' % fmt,
333
 
                           ['use a different bzr version',
334
 
                            'or remove the .bzr directory and "bzr init" again'])
 
349
        if fmt == BZR_BRANCH_FORMAT_5:
 
350
            self._branch_format = 5
 
351
        elif fmt == BZR_BRANCH_FORMAT_4:
 
352
            self._branch_format = 4
 
353
 
 
354
        if (not relax_version_check
 
355
            and self._branch_format != 5):
 
356
            raise BzrError('sorry, branch format "%s" not supported; ' 
 
357
                           'use a different bzr version, '
 
358
                           'or run "bzr upgrade"'
 
359
                           % fmt.rstrip('\n\r'))
 
360
        
335
361
 
336
362
    def get_root_id(self):
337
363
        """Return the id of this branches root"""
352
378
 
353
379
    def read_working_inventory(self):
354
380
        """Read the working inventory."""
355
 
        from bzrlib.inventory import Inventory
356
 
        from bzrlib.xml import unpack_xml
357
 
        from time import time
358
 
        before = time()
359
381
        self.lock_read()
360
382
        try:
361
383
            # ElementTree does its own conversion from UTF-8, so open in
362
384
            # binary.
363
 
            inv = unpack_xml(Inventory,
364
 
                             self.controlfile('inventory', 'rb'))
365
 
            mutter("loaded inventory of %d items in %f"
366
 
                   % (len(inv), time() - before))
367
 
            return inv
 
385
            f = self.controlfile('inventory', 'rb')
 
386
            return bzrlib.xml5.serializer_v5.read_inventory(f)
368
387
        finally:
369
388
            self.unlock()
370
389
            
376
395
        will be committed to the next revision.
377
396
        """
378
397
        from bzrlib.atomicfile import AtomicFile
379
 
        from bzrlib.xml import pack_xml
380
398
        
381
399
        self.lock_write()
382
400
        try:
383
401
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
384
402
            try:
385
 
                pack_xml(inv, f)
 
403
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
386
404
                f.commit()
387
405
            finally:
388
406
                f.close()
396
414
                         """Inventory for the working copy.""")
397
415
 
398
416
 
399
 
    def add(self, files, verbose=False, ids=None):
 
417
    def add(self, files, ids=None):
400
418
        """Make files versioned.
401
419
 
402
 
        Note that the command line normally calls smart_add instead.
 
420
        Note that the command line normally calls smart_add instead,
 
421
        which can automatically recurse.
403
422
 
404
423
        This puts the files in the Added state, so that they will be
405
424
        recorded by the next commit.
415
434
        TODO: Perhaps have an option to add the ids even if the files do
416
435
              not (yet) exist.
417
436
 
418
 
        TODO: Perhaps return the ids of the files?  But then again it
419
 
              is easy to retrieve them if they're needed.
420
 
 
421
 
        TODO: Adding a directory should optionally recurse down and
422
 
              add all non-ignored children.  Perhaps do that in a
423
 
              higher-level method.
 
437
        TODO: Perhaps yield the ids and paths as they're added.
424
438
        """
425
 
        from bzrlib.textui import show_status
426
439
        # TODO: Re-adding a file that is removed in the working copy
427
440
        # should probably put it back with the previous ID.
428
441
        if isinstance(files, basestring):
463
476
                    file_id = gen_file_id(f)
464
477
                inv.add_path(f, kind=kind, file_id=file_id)
465
478
 
466
 
                if verbose:
467
 
                    print 'added', quotefn(f)
468
 
 
469
479
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
470
480
 
471
481
            self._write_inventory(inv)
501
511
        is the opposite of add.  Removing it is consistent with most
502
512
        other tools.  Maybe an option.
503
513
        """
504
 
        from bzrlib.textui import show_status
505
514
        ## TODO: Normalize names
506
515
        ## TODO: Remove nested loops; better scalability
507
516
        if isinstance(files, basestring):
582
591
            f.close()
583
592
 
584
593
 
 
594
    def has_revision(self, revision_id):
 
595
        """True if this branch has a copy of the revision.
 
596
 
 
597
        This does not necessarily imply the revision is merge
 
598
        or on the mainline."""
 
599
        return revision_id in self.revision_store
 
600
 
 
601
 
 
602
    def get_revision_xml_file(self, revision_id):
 
603
        """Return XML file object for revision object."""
 
604
        if not revision_id or not isinstance(revision_id, basestring):
 
605
            raise InvalidRevisionId(revision_id)
 
606
 
 
607
        self.lock_read()
 
608
        try:
 
609
            try:
 
610
                return self.revision_store[revision_id]
 
611
            except IndexError:
 
612
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
613
        finally:
 
614
            self.unlock()
 
615
 
 
616
 
 
617
    def get_revision_xml(self, revision_id):
 
618
        return self.get_revision_xml_file(revision_id).read()
 
619
 
 
620
 
585
621
    def get_revision(self, revision_id):
586
622
        """Return the Revision object for a named revision"""
587
 
        from bzrlib.revision import Revision
588
 
        from bzrlib.xml import unpack_xml
 
623
        xml_file = self.get_revision_xml_file(revision_id)
589
624
 
590
 
        self.lock_read()
591
625
        try:
592
 
            if not revision_id or not isinstance(revision_id, basestring):
593
 
                raise ValueError('invalid revision-id: %r' % revision_id)
594
 
            r = unpack_xml(Revision, self.revision_store[revision_id])
595
 
        finally:
596
 
            self.unlock()
 
626
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
627
        except SyntaxError, e:
 
628
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
629
                                         [revision_id,
 
630
                                          str(e)])
597
631
            
598
632
        assert r.revision_id == revision_id
599
633
        return r
 
634
 
 
635
 
 
636
    def get_revision_delta(self, revno):
 
637
        """Return the delta for one revision.
 
638
 
 
639
        The delta is relative to its mainline predecessor, or the
 
640
        empty tree for revision 1.
 
641
        """
 
642
        assert isinstance(revno, int)
 
643
        rh = self.revision_history()
 
644
        if not (1 <= revno <= len(rh)):
 
645
            raise InvalidRevisionNumber(revno)
 
646
 
 
647
        # revno is 1-based; list is 0-based
 
648
 
 
649
        new_tree = self.revision_tree(rh[revno-1])
 
650
        if revno == 1:
 
651
            old_tree = EmptyTree()
 
652
        else:
 
653
            old_tree = self.revision_tree(rh[revno-2])
 
654
 
 
655
        return compare_trees(old_tree, new_tree)
 
656
 
600
657
        
601
658
 
602
659
    def get_revision_sha1(self, revision_id):
603
660
        """Hash the stored value of a revision, and return it."""
604
 
        # In the future, revision entries will be signed. At that
605
 
        # point, it is probably best *not* to include the signature
606
 
        # in the revision hash. Because that lets you re-sign
607
 
        # the revision, (add signatures/remove signatures) and still
608
 
        # have all hash pointers stay consistent.
609
 
        # But for now, just hash the contents.
610
 
        return sha_file(self.revision_store[revision_id])
611
 
 
612
 
 
613
 
    def get_inventory(self, inventory_id):
614
 
        """Get Inventory object by hash.
615
 
 
616
 
        TODO: Perhaps for this and similar methods, take a revision
617
 
               parameter which can be either an integer revno or a
618
 
               string hash."""
619
 
        from bzrlib.inventory import Inventory
620
 
        from bzrlib.xml import unpack_xml
621
 
 
622
 
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
623
 
            
624
 
 
625
 
    def get_inventory_sha1(self, inventory_id):
 
661
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
662
 
 
663
 
 
664
    def _get_ancestry_weave(self):
 
665
        return self.control_weaves.get_weave('ancestry')
 
666
        
 
667
 
 
668
    def get_ancestry(self, revision_id):
 
669
        """Return a list of revision-ids integrated by a revision.
 
670
        """
 
671
        # strip newlines
 
672
        w = self._get_ancestry_weave()
 
673
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
 
674
 
 
675
 
 
676
    def get_inventory_weave(self):
 
677
        return self.control_weaves.get_weave('inventory')
 
678
 
 
679
 
 
680
    def get_inventory(self, revision_id):
 
681
        """Get Inventory object by hash."""
 
682
        # FIXME: The text gets passed around a lot coming from the weave.
 
683
        f = StringIO(self.get_inventory_xml(revision_id))
 
684
        return bzrlib.xml5.serializer_v5.read_inventory(f)
 
685
 
 
686
 
 
687
    def get_inventory_xml(self, revision_id):
 
688
        """Get inventory XML as a file object."""
 
689
        try:
 
690
            assert isinstance(revision_id, basestring), type(revision_id)
 
691
            iw = self.get_inventory_weave()
 
692
            return iw.get_text(iw.lookup(revision_id))
 
693
        except IndexError:
 
694
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
695
 
 
696
 
 
697
    def get_inventory_sha1(self, revision_id):
626
698
        """Return the sha1 hash of the inventory entry
627
699
        """
628
 
        return sha_file(self.inventory_store[inventory_id])
 
700
        return self.get_revision(revision_id).inventory_sha1
629
701
 
630
702
 
631
703
    def get_revision_inventory(self, revision_id):
632
704
        """Return inventory of a past revision."""
633
 
        # bzr 0.0.6 imposes the constraint that the inventory_id
 
705
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
634
706
        # must be the same as its revision, so this is trivial.
635
707
        if revision_id == None:
636
 
            from bzrlib.inventory import Inventory
637
708
            return Inventory(self.get_root_id())
638
709
        else:
639
710
            return self.get_inventory(revision_id)
640
711
 
641
712
 
642
713
    def revision_history(self):
643
 
        """Return sequence of revision hashes on to this branch.
644
 
 
645
 
        >>> ScratchBranch().revision_history()
646
 
        []
647
 
        """
 
714
        """Return sequence of revision hashes on to this branch."""
648
715
        self.lock_read()
649
716
        try:
650
717
            return [l.rstrip('\r\n') for l in
659
726
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
660
727
        >>> sb.common_ancestor(sb) == (None, None)
661
728
        True
662
 
        >>> commit.commit(sb, "Committing first revision", verbose=False)
 
729
        >>> commit.commit(sb, "Committing first revision")
663
730
        >>> sb.common_ancestor(sb)[0]
664
731
        1
665
732
        >>> clone = sb.clone()
666
 
        >>> commit.commit(sb, "Committing second revision", verbose=False)
 
733
        >>> commit.commit(sb, "Committing second revision")
667
734
        >>> sb.common_ancestor(sb)[0]
668
735
        2
669
736
        >>> sb.common_ancestor(clone)[0]
670
737
        1
671
 
        >>> commit.commit(clone, "Committing divergent second revision", 
672
 
        ...               verbose=False)
 
738
        >>> commit.commit(clone, "Committing divergent second revision")
673
739
        >>> sb.common_ancestor(clone)[0]
674
740
        1
675
741
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
697
763
                return r+1, my_history[r]
698
764
        return None, None
699
765
 
700
 
    def enum_history(self, direction):
701
 
        """Return (revno, revision_id) for history of branch.
702
 
 
703
 
        direction
704
 
            'forward' is from earliest to latest
705
 
            'reverse' is from latest to earliest
706
 
        """
707
 
        rh = self.revision_history()
708
 
        if direction == 'forward':
709
 
            i = 1
710
 
            for rid in rh:
711
 
                yield i, rid
712
 
                i += 1
713
 
        elif direction == 'reverse':
714
 
            i = len(rh)
715
 
            while i > 0:
716
 
                yield i, rh[i-1]
717
 
                i -= 1
718
 
        else:
719
 
            raise ValueError('invalid history direction', direction)
720
 
 
721
766
 
722
767
    def revno(self):
723
768
        """Return current revision number for this branch.
728
773
        return len(self.revision_history())
729
774
 
730
775
 
731
 
    def last_patch(self):
 
776
    def last_revision(self):
732
777
        """Return last patch hash, or None if no history.
733
778
        """
734
779
        ph = self.revision_history()
738
783
            return None
739
784
 
740
785
 
741
 
    def missing_revisions(self, other, stop_revision=None):
742
 
        """
 
786
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
787
        """Return a list of new revisions that would perfectly fit.
 
788
        
743
789
        If self and other have not diverged, return a list of the revisions
744
790
        present in other, but missing from self.
745
791
 
765
811
        Traceback (most recent call last):
766
812
        DivergedBranches: These branches have diverged.
767
813
        """
 
814
        # FIXME: If the branches have diverged, but the latest
 
815
        # revision in this branch is completely merged into the other,
 
816
        # then we should still be able to pull.
768
817
        self_history = self.revision_history()
769
818
        self_len = len(self_history)
770
819
        other_history = other.revision_history()
776
825
 
777
826
        if stop_revision is None:
778
827
            stop_revision = other_len
779
 
        elif stop_revision > other_len:
780
 
            raise NoSuchRevision(self, stop_revision)
 
828
        else:
 
829
            assert isinstance(stop_revision, int)
 
830
            if stop_revision > other_len:
 
831
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
781
832
        
782
833
        return other_history[self_len:stop_revision]
783
834
 
784
835
 
785
 
    def update_revisions(self, other, stop_revision=None):
786
 
        """Pull in all new revisions from other branch.
787
 
        
788
 
        >>> from bzrlib.commit import commit
789
 
        >>> bzrlib.trace.silent = True
790
 
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
791
 
        >>> br1.add('foo')
792
 
        >>> br1.add('bar')
793
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
794
 
        >>> br2 = ScratchBranch()
795
 
        >>> br2.update_revisions(br1)
796
 
        Added 2 texts.
797
 
        Added 1 inventories.
798
 
        Added 1 revisions.
799
 
        >>> br2.revision_history()
800
 
        [u'REVISION-ID-1']
801
 
        >>> br2.update_revisions(br1)
802
 
        Added 0 texts.
803
 
        Added 0 inventories.
804
 
        Added 0 revisions.
805
 
        >>> br1.text_store.total_size() == br2.text_store.total_size()
806
 
        True
 
836
    def update_revisions(self, other, stop_revno=None):
 
837
        """Pull in new perfect-fit revisions.
807
838
        """
808
 
        from bzrlib.progress import ProgressBar
809
 
 
810
 
        pb = ProgressBar()
811
 
 
812
 
        pb.update('comparing histories')
813
 
        revision_ids = self.missing_revisions(other, stop_revision)
814
 
 
815
 
        if hasattr(other.revision_store, "prefetch"):
816
 
            other.revision_store.prefetch(revision_ids)
817
 
        if hasattr(other.inventory_store, "prefetch"):
818
 
            inventory_ids = [other.get_revision(r).inventory_id
819
 
                             for r in revision_ids]
820
 
            other.inventory_store.prefetch(inventory_ids)
821
 
                
822
 
        revisions = []
823
 
        needed_texts = set()
824
 
        i = 0
825
 
        for rev_id in revision_ids:
826
 
            i += 1
827
 
            pb.update('fetching revision', i, len(revision_ids))
828
 
            rev = other.get_revision(rev_id)
829
 
            revisions.append(rev)
830
 
            inv = other.get_inventory(str(rev.inventory_id))
831
 
            for key, entry in inv.iter_entries():
832
 
                if entry.text_id is None:
833
 
                    continue
834
 
                if entry.text_id not in self.text_store:
835
 
                    needed_texts.add(entry.text_id)
836
 
 
837
 
        pb.clear()
838
 
                    
839
 
        count = self.text_store.copy_multi(other.text_store, needed_texts)
840
 
        print "Added %d texts." % count 
841
 
        inventory_ids = [ f.inventory_id for f in revisions ]
842
 
        count = self.inventory_store.copy_multi(other.inventory_store, 
843
 
                                                inventory_ids)
844
 
        print "Added %d inventories." % count 
845
 
        revision_ids = [ f.revision_id for f in revisions]
846
 
        count = self.revision_store.copy_multi(other.revision_store, 
847
 
                                               revision_ids)
848
 
        for revision_id in revision_ids:
849
 
            self.append_revision(revision_id)
850
 
        print "Added %d revisions." % count
851
 
                    
852
 
        
 
839
        from bzrlib.fetch import greedy_fetch
 
840
 
 
841
        if stop_revno:
 
842
            stop_revision = other.lookup_revision(stop_revno)
 
843
        else:
 
844
            stop_revision = None
 
845
        greedy_fetch(to_branch=self, from_branch=other,
 
846
                     revision=stop_revision)
 
847
 
 
848
        pullable_revs = self.missing_revisions(other, stop_revision)
 
849
 
 
850
        if pullable_revs:
 
851
            greedy_fetch(to_branch=self,
 
852
                         from_branch=other,
 
853
                         revision=pullable_revs[-1])
 
854
            self.append_revision(*pullable_revs)
 
855
 
 
856
 
853
857
    def commit(self, *args, **kw):
854
 
        from bzrlib.commit import commit
855
 
        commit(self, *args, **kw)
 
858
        from bzrlib.commit import Commit
 
859
        Commit().commit(self, *args, **kw)
856
860
        
857
861
 
858
862
    def lookup_revision(self, revision):
859
863
        """Return the revision identifier for a given revision information."""
860
 
        revno, info = self.get_revision_info(revision)
 
864
        revno, info = self._get_revision_info(revision)
861
865
        return info
862
866
 
 
867
 
 
868
    def revision_id_to_revno(self, revision_id):
 
869
        """Given a revision id, return its revno"""
 
870
        history = self.revision_history()
 
871
        try:
 
872
            return history.index(revision_id) + 1
 
873
        except ValueError:
 
874
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
875
 
 
876
 
863
877
    def get_revision_info(self, revision):
864
878
        """Return (revno, revision id) for revision identifier.
865
879
 
868
882
        revision can also be a string, in which case it is parsed for something like
869
883
            'date:' or 'revid:' etc.
870
884
        """
 
885
        revno, rev_id = self._get_revision_info(revision)
 
886
        if revno is None:
 
887
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
888
        return revno, rev_id
 
889
 
 
890
    def get_rev_id(self, revno, history=None):
 
891
        """Find the revision id of the specified revno."""
 
892
        if revno == 0:
 
893
            return None
 
894
        if history is None:
 
895
            history = self.revision_history()
 
896
        elif revno <= 0 or revno > len(history):
 
897
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
898
        return history[revno - 1]
 
899
 
 
900
    def _get_revision_info(self, revision):
 
901
        """Return (revno, revision id) for revision specifier.
 
902
 
 
903
        revision can be an integer, in which case it is assumed to be revno
 
904
        (though this will translate negative values into positive ones)
 
905
        revision can also be a string, in which case it is parsed for something
 
906
        like 'date:' or 'revid:' etc.
 
907
 
 
908
        A revid is always returned.  If it is None, the specifier referred to
 
909
        the null revision.  If the revid does not occur in the revision
 
910
        history, revno will be None.
 
911
        """
 
912
        
871
913
        if revision is None:
872
914
            return 0, None
873
915
        revno = None
877
919
            pass
878
920
        revs = self.revision_history()
879
921
        if isinstance(revision, int):
880
 
            if revision == 0:
881
 
                return 0, None
882
 
            # Mabye we should do this first, but we don't need it if revision == 0
883
922
            if revision < 0:
884
923
                revno = len(revs) + revision + 1
885
924
            else:
886
925
                revno = revision
 
926
            rev_id = self.get_rev_id(revno, revs)
887
927
        elif isinstance(revision, basestring):
888
928
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
889
929
                if revision.startswith(prefix):
890
 
                    revno = func(self, revs, revision)
 
930
                    result = func(self, revs, revision)
 
931
                    if len(result) > 1:
 
932
                        revno, rev_id = result
 
933
                    else:
 
934
                        revno = result[0]
 
935
                        rev_id = self.get_rev_id(revno, revs)
891
936
                    break
892
937
            else:
893
 
                raise BzrError('No namespace registered for string: %r' % revision)
 
938
                raise BzrError('No namespace registered for string: %r' %
 
939
                               revision)
 
940
        else:
 
941
            raise TypeError('Unhandled revision type %s' % revision)
894
942
 
895
 
        if revno is None or revno <= 0 or revno > len(revs):
896
 
            raise BzrError("no such revision %s" % revision)
897
 
        return revno, revs[revno-1]
 
943
        if revno is None:
 
944
            if rev_id is None:
 
945
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
946
        return revno, rev_id
898
947
 
899
948
    def _namespace_revno(self, revs, revision):
900
949
        """Lookup a revision by revision number"""
901
950
        assert revision.startswith('revno:')
902
951
        try:
903
 
            return int(revision[6:])
 
952
            return (int(revision[6:]),)
904
953
        except ValueError:
905
954
            return None
906
955
    REVISION_NAMESPACES['revno:'] = _namespace_revno
907
956
 
908
957
    def _namespace_revid(self, revs, revision):
909
958
        assert revision.startswith('revid:')
 
959
        rev_id = revision[len('revid:'):]
910
960
        try:
911
 
            return revs.index(revision[6:]) + 1
 
961
            return revs.index(rev_id) + 1, rev_id
912
962
        except ValueError:
913
 
            return None
 
963
            return None, rev_id
914
964
    REVISION_NAMESPACES['revid:'] = _namespace_revid
915
965
 
916
966
    def _namespace_last(self, revs, revision):
918
968
        try:
919
969
            offset = int(revision[5:])
920
970
        except ValueError:
921
 
            return None
 
971
            return (None,)
922
972
        else:
923
973
            if offset <= 0:
924
974
                raise BzrError('You must supply a positive value for --revision last:XXX')
925
 
            return len(revs) - offset + 1
 
975
            return (len(revs) - offset + 1,)
926
976
    REVISION_NAMESPACES['last:'] = _namespace_last
927
977
 
928
978
    def _namespace_tag(self, revs, revision):
1003
1053
                # TODO: Handle timezone.
1004
1054
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1005
1055
                if first >= dt and (last is None or dt >= last):
1006
 
                    return i+1
 
1056
                    return (i+1,)
1007
1057
        else:
1008
1058
            for i in range(len(revs)):
1009
1059
                r = self.get_revision(revs[i])
1010
1060
                # TODO: Handle timezone.
1011
1061
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1012
1062
                if first <= dt and (last is None or dt <= last):
1013
 
                    return i+1
 
1063
                    return (i+1,)
1014
1064
    REVISION_NAMESPACES['date:'] = _namespace_date
1015
1065
 
1016
1066
    def revision_tree(self, revision_id):
1018
1068
 
1019
1069
        `revision_id` may be None for the null revision, in which case
1020
1070
        an `EmptyTree` is returned."""
1021
 
        from bzrlib.tree import EmptyTree, RevisionTree
1022
1071
        # TODO: refactor this to use an existing revision object
1023
1072
        # so we don't need to read it in twice.
1024
1073
        if revision_id == None:
1025
 
            return EmptyTree(self.get_root_id())
 
1074
            return EmptyTree()
1026
1075
        else:
1027
1076
            inv = self.get_revision_inventory(revision_id)
1028
 
            return RevisionTree(self.text_store, inv)
 
1077
            return RevisionTree(self.weave_store, inv, revision_id)
1029
1078
 
1030
1079
 
1031
1080
    def working_tree(self):
1039
1088
 
1040
1089
        If there are no revisions yet, return an `EmptyTree`.
1041
1090
        """
1042
 
        from bzrlib.tree import EmptyTree, RevisionTree
1043
 
        r = self.last_patch()
1044
 
        if r == None:
1045
 
            return EmptyTree(self.get_root_id())
1046
 
        else:
1047
 
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1048
 
 
 
1091
        return self.revision_tree(self.last_revision())
1049
1092
 
1050
1093
 
1051
1094
    def rename_one(self, from_rel, to_rel):
1083
1126
 
1084
1127
            inv.rename(file_id, to_dir_id, to_tail)
1085
1128
 
1086
 
            print "%s => %s" % (from_rel, to_rel)
1087
 
 
1088
1129
            from_abs = self.abspath(from_rel)
1089
1130
            to_abs = self.abspath(to_rel)
1090
1131
            try:
1109
1150
 
1110
1151
        Note that to_name is only the last component of the new name;
1111
1152
        this doesn't change the directory.
 
1153
 
 
1154
        This returns a list of (from_path, to_path) pairs for each
 
1155
        entry that is moved.
1112
1156
        """
 
1157
        result = []
1113
1158
        self.lock_write()
1114
1159
        try:
1115
1160
            ## TODO: Option to move IDs only
1150
1195
            for f in from_paths:
1151
1196
                name_tail = splitpath(f)[-1]
1152
1197
                dest_path = appendpath(to_name, name_tail)
1153
 
                print "%s => %s" % (f, dest_path)
 
1198
                result.append((f, dest_path))
1154
1199
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1155
1200
                try:
1156
1201
                    os.rename(self.abspath(f), self.abspath(dest_path))
1162
1207
        finally:
1163
1208
            self.unlock()
1164
1209
 
 
1210
        return result
 
1211
 
1165
1212
 
1166
1213
    def revert(self, filenames, old_tree=None, backups=True):
1167
1214
        """Restore selected files to the versions from a previous tree.
1223
1270
 
1224
1271
 
1225
1272
    def add_pending_merge(self, revision_id):
1226
 
        from bzrlib.revision import validate_revision_id
1227
 
 
1228
1273
        validate_revision_id(revision_id)
1229
 
 
 
1274
        # TODO: Perhaps should check at this point that the
 
1275
        # history of the revision is actually present?
1230
1276
        p = self.pending_merges()
1231
1277
        if revision_id in p:
1232
1278
            return
1249
1295
            self.unlock()
1250
1296
 
1251
1297
 
 
1298
    def get_parent(self):
 
1299
        """Return the parent location of the branch.
 
1300
 
 
1301
        This is the default location for push/pull/missing.  The usual
 
1302
        pattern is that the user can override it by specifying a
 
1303
        location.
 
1304
        """
 
1305
        import errno
 
1306
        _locs = ['parent', 'pull', 'x-pull']
 
1307
        for l in _locs:
 
1308
            try:
 
1309
                return self.controlfile(l, 'r').read().strip('\n')
 
1310
            except IOError, e:
 
1311
                if e.errno != errno.ENOENT:
 
1312
                    raise
 
1313
        return None
 
1314
 
 
1315
 
 
1316
    def set_parent(self, url):
 
1317
        # TODO: Maybe delete old location files?
 
1318
        from bzrlib.atomicfile import AtomicFile
 
1319
        self.lock_write()
 
1320
        try:
 
1321
            f = AtomicFile(self.controlfilename('parent'))
 
1322
            try:
 
1323
                f.write(url + '\n')
 
1324
                f.commit()
 
1325
            finally:
 
1326
                f.close()
 
1327
        finally:
 
1328
            self.unlock()
 
1329
 
 
1330
    def check_revno(self, revno):
 
1331
        """\
 
1332
        Check whether a revno corresponds to any revision.
 
1333
        Zero (the NULL revision) is considered valid.
 
1334
        """
 
1335
        if revno != 0:
 
1336
            self.check_real_revno(revno)
 
1337
            
 
1338
    def check_real_revno(self, revno):
 
1339
        """\
 
1340
        Check whether a revno corresponds to a real revision.
 
1341
        Zero (the NULL revision) is considered invalid
 
1342
        """
 
1343
        if revno < 1 or revno > self.revno():
 
1344
            raise InvalidRevisionNumber(revno)
 
1345
        
 
1346
        
 
1347
 
1252
1348
 
1253
1349
class ScratchBranch(Branch):
1254
1350
    """Special test class: a branch that cleans up after itself.
1296
1392
        os.rmdir(base)
1297
1393
        copytree(self.base, base, symlinks=True)
1298
1394
        return ScratchBranch(base=base)
 
1395
 
 
1396
 
1299
1397
        
1300
1398
    def __del__(self):
1301
1399
        self.destroy()
1371
1469
    """Return a new tree-root file id."""
1372
1470
    return gen_file_id('TREE_ROOT')
1373
1471
 
 
1472
 
 
1473
def pull_loc(branch):
 
1474
    # TODO: Should perhaps just make attribute be 'base' in
 
1475
    # RemoteBranch and Branch?
 
1476
    if hasattr(branch, "baseurl"):
 
1477
        return branch.baseurl
 
1478
    else:
 
1479
        return branch.base
 
1480
 
 
1481
 
 
1482
def copy_branch(branch_from, to_location, revision=None):
 
1483
    """Copy branch_from into the existing directory to_location.
 
1484
 
 
1485
    revision
 
1486
        If not None, only revisions up to this point will be copied.
 
1487
        The head of the new branch will be that revision.  Can be a
 
1488
        revno or revid.
 
1489
 
 
1490
    to_location
 
1491
        The name of a local directory that exists but is empty.
 
1492
    """
 
1493
    # TODO: This could be done *much* more efficiently by just copying
 
1494
    # all the whole weaves and revisions, rather than getting one
 
1495
    # revision at a time.
 
1496
    from bzrlib.merge import merge
 
1497
    from bzrlib.branch import Branch
 
1498
 
 
1499
    assert isinstance(branch_from, Branch)
 
1500
    assert isinstance(to_location, basestring)
 
1501
    
 
1502
    br_to = Branch(to_location, init=True)
 
1503
    br_to.set_root_id(branch_from.get_root_id())
 
1504
    if revision is None:
 
1505
        revno = None
 
1506
    else:
 
1507
        revno, rev_id = branch_from.get_revision_info(revision)
 
1508
    br_to.update_revisions(branch_from, stop_revno=revno)
 
1509
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
1510
          check_clean=False, ignore_zero=True)
 
1511
    
 
1512
    from_location = pull_loc(branch_from)
 
1513
    br_to.set_parent(pull_loc(branch_from))
 
1514