~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

merge from aaron - fixes bare excepts, adds ancestor namespace

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
19
20
 
20
21
import bzrlib
21
22
from bzrlib.trace import mutter, note
22
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
 
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
 
24
     splitpath, \
23
25
     sha_file, appendpath, file_kind
24
 
from bzrlib.errors import BzrError
 
26
 
 
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
 
28
     DivergedBranches, NotBranchError
 
29
from bzrlib.textui import show_status
 
30
from bzrlib.revision import Revision
 
31
from bzrlib.delta import compare_trees
 
32
from bzrlib.tree import EmptyTree, RevisionTree
 
33
import bzrlib.xml
 
34
import bzrlib.ui
 
35
 
 
36
 
25
37
 
26
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
27
39
## TODO: Maybe include checks for common corruption of newlines, etc?
28
40
 
29
41
 
 
42
# TODO: Some operations like log might retrieve the same revisions
 
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
44
# cache in memory to make this faster.
 
45
 
 
46
# TODO: please move the revision-string syntax stuff out of the branch
 
47
# object; it's clutter
 
48
 
30
49
 
31
50
def find_branch(f, **args):
32
51
    if f and (f.startswith('http://') or f.startswith('https://')):
33
 
        import remotebranch 
34
 
        return remotebranch.RemoteBranch(f, **args)
 
52
        from bzrlib.remotebranch import RemoteBranch
 
53
        return RemoteBranch(f, **args)
35
54
    else:
36
55
        return Branch(f, **args)
37
56
 
38
57
 
39
58
def find_cached_branch(f, cache_root, **args):
40
 
    from remotebranch import RemoteBranch
 
59
    from bzrlib.remotebranch import RemoteBranch
41
60
    br = find_branch(f, **args)
42
61
    def cacheify(br, store_name):
43
 
        from meta_store import CachedStore
 
62
        from bzrlib.meta_store import CachedStore
44
63
        cache_path = os.path.join(cache_root, store_name)
45
64
        os.mkdir(cache_path)
46
65
        new_store = CachedStore(getattr(br, store_name), cache_path)
75
94
        if tail:
76
95
            s.insert(0, tail)
77
96
    else:
78
 
        from errors import NotBranchError
79
97
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
80
98
 
81
99
    return os.sep.join(s)
89
107
    It is not necessary that f exists.
90
108
 
91
109
    Basically we keep looking up until we find the control directory or
92
 
    run into the root."""
 
110
    run into the root.  If there isn't one, raises NotBranchError.
 
111
    """
93
112
    if f == None:
94
113
        f = os.getcwd()
95
114
    elif hasattr(os.path, 'realpath'):
108
127
        head, tail = os.path.split(f)
109
128
        if head == f:
110
129
            # reached the root, whatever that may be
111
 
            raise BzrError('%r is not in a branch' % orig_f)
 
130
            raise NotBranchError('%s is not in a branch' % orig_f)
112
131
        f = head
113
 
    
114
 
class DivergedBranches(Exception):
115
 
    def __init__(self, branch1, branch2):
116
 
        self.branch1 = branch1
117
 
        self.branch2 = branch2
118
 
        Exception.__init__(self, "These branches have diverged.")
119
 
 
120
 
 
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)
 
132
 
 
133
 
127
134
 
128
135
 
129
136
######################################################################
158
165
    def __init__(self, base, init=False, find_root=True):
159
166
        """Create new branch object at a particular location.
160
167
 
161
 
        base -- Base directory for the branch.
 
168
        base -- Base directory for the branch. May be a file:// url.
162
169
        
163
170
        init -- If True, create new control files in a previously
164
171
             unversioned directory.  If False, the branch must already
177
184
        elif find_root:
178
185
            self.base = find_branch_root(base)
179
186
        else:
 
187
            if base.startswith("file://"):
 
188
                base = base[7:]
180
189
            self.base = os.path.realpath(base)
181
190
            if not isdir(self.controlfilename('.')):
182
 
                from errors import NotBranchError
183
191
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
184
192
                                     ['use "bzr init" to initialize a new working tree',
185
193
                                      'current bzr can only operate from top-of-tree'])
199
207
 
200
208
    def __del__(self):
201
209
        if self._lock_mode or self._lock:
202
 
            from warnings import warn
 
210
            from bzrlib.warnings import warn
203
211
            warn("branch %r was not explicitly unlocked" % self)
204
212
            self._lock.unlock()
205
213
 
206
 
 
207
 
 
208
214
    def lock_write(self):
209
215
        if self._lock_mode:
210
216
            if self._lock_mode != 'w':
211
 
                from errors import LockError
 
217
                from bzrlib.errors import LockError
212
218
                raise LockError("can't upgrade to a write lock from %r" %
213
219
                                self._lock_mode)
214
220
            self._lock_count += 1
220
226
            self._lock_count = 1
221
227
 
222
228
 
223
 
 
224
229
    def lock_read(self):
225
230
        if self._lock_mode:
226
231
            assert self._lock_mode in ('r', 'w'), \
233
238
            self._lock_mode = 'r'
234
239
            self._lock_count = 1
235
240
                        
236
 
 
237
 
            
238
241
    def unlock(self):
239
242
        if not self._lock_mode:
240
 
            from errors import LockError
 
243
            from bzrlib.errors import LockError
241
244
            raise LockError('branch %r is not locked' % (self))
242
245
 
243
246
        if self._lock_count > 1:
247
250
            self._lock = None
248
251
            self._lock_mode = self._lock_count = None
249
252
 
250
 
 
251
253
    def abspath(self, name):
252
254
        """Return absolute filename for something in the branch"""
253
255
        return os.path.join(self.base, name)
254
256
 
255
 
 
256
257
    def relpath(self, path):
257
258
        """Return path relative to this branch of something inside it.
258
259
 
259
260
        Raises an error if path is not in this branch."""
260
261
        return _relpath(self.base, path)
261
262
 
262
 
 
263
263
    def controlfilename(self, file_or_path):
264
264
        """Return location relative to branch."""
265
265
        if isinstance(file_or_path, basestring):
292
292
        else:
293
293
            raise BzrError("invalid controlfile mode %r" % mode)
294
294
 
295
 
 
296
 
 
297
295
    def _make_control(self):
298
296
        from bzrlib.inventory import Inventory
299
 
        from bzrlib.xml import pack_xml
300
297
        
301
298
        os.mkdir(self.controlfilename([]))
302
299
        self.controlfile('README', 'w').write(
312
309
            self.controlfile(f, 'w').write('')
313
310
        mutter('created control directory in ' + self.base)
314
311
 
315
 
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
 
312
        # if we want per-tree root ids then this is the place to set
 
313
        # them; they're not needed for now and so ommitted for
 
314
        # simplicity.
 
315
        f = self.controlfile('inventory','w')
 
316
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
316
317
 
317
318
 
318
319
    def _check_format(self):
327
328
        # on Windows from Linux and so on.  I think it might be better
328
329
        # to always make all internal files in unix format.
329
330
        fmt = self.controlfile('branch-format', 'r').read()
330
 
        fmt.replace('\r\n', '')
 
331
        fmt = fmt.replace('\r\n', '\n')
331
332
        if fmt != BZR_BRANCH_FORMAT:
332
333
            raise BzrError('sorry, branch format %r not supported' % fmt,
333
334
                           ['use a different bzr version',
353
354
    def read_working_inventory(self):
354
355
        """Read the working inventory."""
355
356
        from bzrlib.inventory import Inventory
356
 
        from bzrlib.xml import unpack_xml
357
 
        from time import time
358
 
        before = time()
359
357
        self.lock_read()
360
358
        try:
361
359
            # ElementTree does its own conversion from UTF-8, so open in
362
360
            # 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
 
361
            f = self.controlfile('inventory', 'rb')
 
362
            return bzrlib.xml.serializer_v4.read_inventory(f)
368
363
        finally:
369
364
            self.unlock()
370
365
            
376
371
        will be committed to the next revision.
377
372
        """
378
373
        from bzrlib.atomicfile import AtomicFile
379
 
        from bzrlib.xml import pack_xml
380
374
        
381
375
        self.lock_write()
382
376
        try:
383
377
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
384
378
            try:
385
 
                pack_xml(inv, f)
 
379
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
386
380
                f.commit()
387
381
            finally:
388
382
                f.close()
396
390
                         """Inventory for the working copy.""")
397
391
 
398
392
 
399
 
    def add(self, files, verbose=False, ids=None):
 
393
    def add(self, files, ids=None):
400
394
        """Make files versioned.
401
395
 
402
 
        Note that the command line normally calls smart_add instead.
 
396
        Note that the command line normally calls smart_add instead,
 
397
        which can automatically recurse.
403
398
 
404
399
        This puts the files in the Added state, so that they will be
405
400
        recorded by the next commit.
415
410
        TODO: Perhaps have an option to add the ids even if the files do
416
411
              not (yet) exist.
417
412
 
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.
 
413
        TODO: Perhaps yield the ids and paths as they're added.
424
414
        """
425
 
        from bzrlib.textui import show_status
426
415
        # TODO: Re-adding a file that is removed in the working copy
427
416
        # should probably put it back with the previous ID.
428
417
        if isinstance(files, basestring):
463
452
                    file_id = gen_file_id(f)
464
453
                inv.add_path(f, kind=kind, file_id=file_id)
465
454
 
466
 
                if verbose:
467
 
                    print 'added', quotefn(f)
468
 
 
469
455
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
470
456
 
471
457
            self._write_inventory(inv)
501
487
        is the opposite of add.  Removing it is consistent with most
502
488
        other tools.  Maybe an option.
503
489
        """
504
 
        from bzrlib.textui import show_status
505
490
        ## TODO: Normalize names
506
491
        ## TODO: Remove nested loops; better scalability
507
492
        if isinstance(files, basestring):
582
567
            f.close()
583
568
 
584
569
 
 
570
    def get_revision_xml_file(self, revision_id):
 
571
        """Return XML file object for revision object."""
 
572
        if not revision_id or not isinstance(revision_id, basestring):
 
573
            raise InvalidRevisionId(revision_id)
 
574
 
 
575
        self.lock_read()
 
576
        try:
 
577
            try:
 
578
                return self.revision_store[revision_id]
 
579
            except KeyError:
 
580
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
581
        finally:
 
582
            self.unlock()
 
583
 
 
584
 
 
585
    #deprecated
 
586
    get_revision_xml = get_revision_xml_file
 
587
 
 
588
 
585
589
    def get_revision(self, revision_id):
586
590
        """Return the Revision object for a named revision"""
587
 
        from bzrlib.revision import Revision
588
 
        from bzrlib.xml import unpack_xml
 
591
        xml_file = self.get_revision_xml_file(revision_id)
589
592
 
590
 
        self.lock_read()
591
593
        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()
 
594
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
 
595
        except SyntaxError, e:
 
596
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
597
                                         [revision_id,
 
598
                                          str(e)])
597
599
            
598
600
        assert r.revision_id == revision_id
599
601
        return r
 
602
 
 
603
 
 
604
    def get_revision_delta(self, revno):
 
605
        """Return the delta for one revision.
 
606
 
 
607
        The delta is relative to its mainline predecessor, or the
 
608
        empty tree for revision 1.
 
609
        """
 
610
        assert isinstance(revno, int)
 
611
        rh = self.revision_history()
 
612
        if not (1 <= revno <= len(rh)):
 
613
            raise InvalidRevisionNumber(revno)
 
614
 
 
615
        # revno is 1-based; list is 0-based
 
616
 
 
617
        new_tree = self.revision_tree(rh[revno-1])
 
618
        if revno == 1:
 
619
            old_tree = EmptyTree()
 
620
        else:
 
621
            old_tree = self.revision_tree(rh[revno-2])
 
622
 
 
623
        return compare_trees(old_tree, new_tree)
 
624
 
600
625
        
601
626
 
602
627
    def get_revision_sha1(self, revision_id):
607
632
        # the revision, (add signatures/remove signatures) and still
608
633
        # have all hash pointers stay consistent.
609
634
        # But for now, just hash the contents.
610
 
        return sha_file(self.revision_store[revision_id])
 
635
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
611
636
 
612
637
 
613
638
    def get_inventory(self, inventory_id):
617
642
               parameter which can be either an integer revno or a
618
643
               string hash."""
619
644
        from bzrlib.inventory import Inventory
620
 
        from bzrlib.xml import unpack_xml
621
 
 
622
 
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
 
645
 
 
646
        f = self.get_inventory_xml_file(inventory_id)
 
647
        return bzrlib.xml.serializer_v4.read_inventory(f)
 
648
 
 
649
 
 
650
    def get_inventory_xml(self, inventory_id):
 
651
        """Get inventory XML as a file object."""
 
652
        return self.inventory_store[inventory_id]
 
653
 
 
654
    get_inventory_xml_file = get_inventory_xml
623
655
            
624
656
 
625
657
    def get_inventory_sha1(self, inventory_id):
626
658
        """Return the sha1 hash of the inventory entry
627
659
        """
628
 
        return sha_file(self.inventory_store[inventory_id])
 
660
        return sha_file(self.get_inventory_xml(inventory_id))
629
661
 
630
662
 
631
663
    def get_revision_inventory(self, revision_id):
655
687
 
656
688
    def common_ancestor(self, other, self_revno=None, other_revno=None):
657
689
        """
658
 
        >>> import commit
 
690
        >>> from bzrlib.commit import commit
659
691
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
660
692
        >>> sb.common_ancestor(sb) == (None, None)
661
693
        True
662
 
        >>> commit.commit(sb, "Committing first revision", verbose=False)
 
694
        >>> commit(sb, "Committing first revision", verbose=False)
663
695
        >>> sb.common_ancestor(sb)[0]
664
696
        1
665
697
        >>> clone = sb.clone()
666
 
        >>> commit.commit(sb, "Committing second revision", verbose=False)
 
698
        >>> commit(sb, "Committing second revision", verbose=False)
667
699
        >>> sb.common_ancestor(sb)[0]
668
700
        2
669
701
        >>> sb.common_ancestor(clone)[0]
670
702
        1
671
 
        >>> commit.commit(clone, "Committing divergent second revision", 
 
703
        >>> commit(clone, "Committing divergent second revision", 
672
704
        ...               verbose=False)
673
705
        >>> sb.common_ancestor(clone)[0]
674
706
        1
697
729
                return r+1, my_history[r]
698
730
        return None, None
699
731
 
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
732
 
722
733
    def revno(self):
723
734
        """Return current revision number for this branch.
738
749
            return None
739
750
 
740
751
 
741
 
    def missing_revisions(self, other, stop_revision=None):
 
752
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
742
753
        """
743
754
        If self and other have not diverged, return a list of the revisions
744
755
        present in other, but missing from self.
777
788
        if stop_revision is None:
778
789
            stop_revision = other_len
779
790
        elif stop_revision > other_len:
780
 
            raise NoSuchRevision(self, stop_revision)
 
791
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
781
792
        
782
793
        return other_history[self_len:stop_revision]
783
794
 
784
795
 
785
796
    def update_revisions(self, other, stop_revision=None):
786
797
        """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
807
798
        """
808
 
        from bzrlib.progress import ProgressBar
809
 
 
810
 
        pb = ProgressBar()
811
 
 
 
799
        from bzrlib.fetch import greedy_fetch
 
800
        from bzrlib.revision import get_intervening_revisions
 
801
 
 
802
        pb = bzrlib.ui.ui_factory.progress_bar()
812
803
        pb.update('comparing histories')
813
 
        revision_ids = self.missing_revisions(other, stop_revision)
814
 
 
 
804
        if stop_revision is None:
 
805
            other_revision = other.last_patch()
 
806
        else:
 
807
            other_revision = other.lookup_revision(stop_revision)
 
808
        count = greedy_fetch(self, other, other_revision, pb)[0]
 
809
        try:
 
810
            revision_ids = self.missing_revisions(other, stop_revision)
 
811
        except DivergedBranches, e:
 
812
            try:
 
813
                revision_ids = get_intervening_revisions(self.last_patch(), 
 
814
                                                         other_revision, self)
 
815
                assert self.last_patch() not in revision_ids
 
816
            except bzrlib.errors.NotAncestor:
 
817
                raise e
 
818
 
 
819
        self.append_revision(*revision_ids)
 
820
        pb.clear()
 
821
 
 
822
    def install_revisions(self, other, revision_ids, pb):
815
823
        if hasattr(other.revision_store, "prefetch"):
816
824
            other.revision_store.prefetch(revision_ids)
817
825
        if hasattr(other.inventory_store, "prefetch"):
818
 
            inventory_ids = [other.get_revision(r).inventory_id
819
 
                             for r in revision_ids]
 
826
            inventory_ids = []
 
827
            for rev_id in revision_ids:
 
828
                try:
 
829
                    revision = other.get_revision(rev_id).inventory_id
 
830
                    inventory_ids.append(revision)
 
831
                except bzrlib.errors.NoSuchRevision:
 
832
                    pass
820
833
            other.inventory_store.prefetch(inventory_ids)
 
834
 
 
835
        if pb is None:
 
836
            pb = bzrlib.ui.ui_factory.progress_bar()
821
837
                
822
838
        revisions = []
823
839
        needed_texts = set()
824
840
        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)
 
841
 
 
842
        failures = set()
 
843
        for i, rev_id in enumerate(revision_ids):
 
844
            pb.update('fetching revision', i+1, len(revision_ids))
 
845
            try:
 
846
                rev = other.get_revision(rev_id)
 
847
            except bzrlib.errors.NoSuchRevision:
 
848
                failures.add(rev_id)
 
849
                continue
 
850
 
829
851
            revisions.append(rev)
830
852
            inv = other.get_inventory(str(rev.inventory_id))
831
853
            for key, entry in inv.iter_entries():
836
858
 
837
859
        pb.clear()
838
860
                    
839
 
        count = self.text_store.copy_multi(other.text_store, needed_texts)
840
 
        print "Added %d texts." % count 
 
861
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
862
                                                    needed_texts)
 
863
        #print "Added %d texts." % count 
841
864
        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 
 
865
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
866
                                                         inventory_ids)
 
867
        #print "Added %d inventories." % count 
845
868
        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
 
        
 
869
 
 
870
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
871
                                                          revision_ids,
 
872
                                                          permit_failure=True)
 
873
        assert len(cp_fail) == 0 
 
874
        return count, failures
 
875
       
 
876
 
853
877
    def commit(self, *args, **kw):
854
878
        from bzrlib.commit import commit
855
879
        commit(self, *args, **kw)
857
881
 
858
882
    def lookup_revision(self, revision):
859
883
        """Return the revision identifier for a given revision information."""
860
 
        revno, info = self.get_revision_info(revision)
 
884
        revno, info = self._get_revision_info(revision)
861
885
        return info
862
886
 
 
887
 
 
888
    def revision_id_to_revno(self, revision_id):
 
889
        """Given a revision id, return its revno"""
 
890
        history = self.revision_history()
 
891
        try:
 
892
            return history.index(revision_id) + 1
 
893
        except ValueError:
 
894
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
895
 
 
896
 
863
897
    def get_revision_info(self, revision):
864
898
        """Return (revno, revision id) for revision identifier.
865
899
 
868
902
        revision can also be a string, in which case it is parsed for something like
869
903
            'date:' or 'revid:' etc.
870
904
        """
 
905
        revno, rev_id = self._get_revision_info(revision)
 
906
        if revno is None:
 
907
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
908
        return revno, rev_id
 
909
 
 
910
    def get_rev_id(self, revno, history=None):
 
911
        """Find the revision id of the specified revno."""
 
912
        if revno == 0:
 
913
            return None
 
914
        if history is None:
 
915
            history = self.revision_history()
 
916
        elif revno <= 0 or revno > len(history):
 
917
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
918
        return history[revno - 1]
 
919
 
 
920
    def _get_revision_info(self, revision):
 
921
        """Return (revno, revision id) for revision specifier.
 
922
 
 
923
        revision can be an integer, in which case it is assumed to be revno
 
924
        (though this will translate negative values into positive ones)
 
925
        revision can also be a string, in which case it is parsed for something
 
926
        like 'date:' or 'revid:' etc.
 
927
 
 
928
        A revid is always returned.  If it is None, the specifier referred to
 
929
        the null revision.  If the revid does not occur in the revision
 
930
        history, revno will be None.
 
931
        """
 
932
        
871
933
        if revision is None:
872
934
            return 0, None
873
935
        revno = None
877
939
            pass
878
940
        revs = self.revision_history()
879
941
        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
942
            if revision < 0:
884
943
                revno = len(revs) + revision + 1
885
944
            else:
886
945
                revno = revision
 
946
            rev_id = self.get_rev_id(revno, revs)
887
947
        elif isinstance(revision, basestring):
888
948
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
889
949
                if revision.startswith(prefix):
890
 
                    revno = func(self, revs, revision)
 
950
                    result = func(self, revs, revision)
 
951
                    if len(result) > 1:
 
952
                        revno, rev_id = result
 
953
                    else:
 
954
                        revno = result[0]
 
955
                        rev_id = self.get_rev_id(revno, revs)
891
956
                    break
892
957
            else:
893
 
                raise BzrError('No namespace registered for string: %r' % revision)
 
958
                raise BzrError('No namespace registered for string: %r' %
 
959
                               revision)
 
960
        else:
 
961
            raise TypeError('Unhandled revision type %s' % revision)
894
962
 
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]
 
963
        if revno is None:
 
964
            if rev_id is None:
 
965
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
966
        return revno, rev_id
898
967
 
899
968
    def _namespace_revno(self, revs, revision):
900
969
        """Lookup a revision by revision number"""
901
970
        assert revision.startswith('revno:')
902
971
        try:
903
 
            return int(revision[6:])
 
972
            return (int(revision[6:]),)
904
973
        except ValueError:
905
974
            return None
906
975
    REVISION_NAMESPACES['revno:'] = _namespace_revno
907
976
 
908
977
    def _namespace_revid(self, revs, revision):
909
978
        assert revision.startswith('revid:')
 
979
        rev_id = revision[len('revid:'):]
910
980
        try:
911
 
            return revs.index(revision[6:]) + 1
 
981
            return revs.index(rev_id) + 1, rev_id
912
982
        except ValueError:
913
 
            return None
 
983
            return None, rev_id
914
984
    REVISION_NAMESPACES['revid:'] = _namespace_revid
915
985
 
916
986
    def _namespace_last(self, revs, revision):
918
988
        try:
919
989
            offset = int(revision[5:])
920
990
        except ValueError:
921
 
            return None
 
991
            return (None,)
922
992
        else:
923
993
            if offset <= 0:
924
994
                raise BzrError('You must supply a positive value for --revision last:XXX')
925
 
            return len(revs) - offset + 1
 
995
            return (len(revs) - offset + 1,)
926
996
    REVISION_NAMESPACES['last:'] = _namespace_last
927
997
 
928
998
    def _namespace_tag(self, revs, revision):
1003
1073
                # TODO: Handle timezone.
1004
1074
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1005
1075
                if first >= dt and (last is None or dt >= last):
1006
 
                    return i+1
 
1076
                    return (i+1,)
1007
1077
        else:
1008
1078
            for i in range(len(revs)):
1009
1079
                r = self.get_revision(revs[i])
1010
1080
                # TODO: Handle timezone.
1011
1081
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1012
1082
                if first <= dt and (last is None or dt <= last):
1013
 
                    return i+1
 
1083
                    return (i+1,)
1014
1084
    REVISION_NAMESPACES['date:'] = _namespace_date
1015
1085
 
 
1086
 
 
1087
    def _namespace_ancestor(self, revs, revision):
 
1088
        from revision import common_ancestor, MultipleRevisionSources
 
1089
        other_branch = find_branch(_trim_namespace('ancestor', revision))
 
1090
        revision_a = self.last_patch()
 
1091
        revision_b = other_branch.last_patch()
 
1092
        for r, b in ((revision_a, self), (revision_b, other_branch)):
 
1093
            if r is None:
 
1094
                raise bzrlib.errors.NoCommits(b)
 
1095
        revision_source = MultipleRevisionSources(self, other_branch)
 
1096
        result = common_ancestor(revision_a, revision_b, revision_source)
 
1097
        try:
 
1098
            revno = self.revision_id_to_revno(result)
 
1099
        except bzrlib.errors.NoSuchRevision:
 
1100
            revno = None
 
1101
        return revno,result
 
1102
        
 
1103
 
 
1104
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
 
1105
 
1016
1106
    def revision_tree(self, revision_id):
1017
1107
        """Return Tree for a revision on this branch.
1018
1108
 
1019
1109
        `revision_id` may be None for the null revision, in which case
1020
1110
        an `EmptyTree` is returned."""
1021
 
        from bzrlib.tree import EmptyTree, RevisionTree
1022
1111
        # TODO: refactor this to use an existing revision object
1023
1112
        # so we don't need to read it in twice.
1024
1113
        if revision_id == None:
1025
 
            return EmptyTree(self.get_root_id())
 
1114
            return EmptyTree()
1026
1115
        else:
1027
1116
            inv = self.get_revision_inventory(revision_id)
1028
1117
            return RevisionTree(self.text_store, inv)
1030
1119
 
1031
1120
    def working_tree(self):
1032
1121
        """Return a `Tree` for the working copy."""
1033
 
        from workingtree import WorkingTree
 
1122
        from bzrlib.workingtree import WorkingTree
1034
1123
        return WorkingTree(self.base, self.read_working_inventory())
1035
1124
 
1036
1125
 
1039
1128
 
1040
1129
        If there are no revisions yet, return an `EmptyTree`.
1041
1130
        """
1042
 
        from bzrlib.tree import EmptyTree, RevisionTree
1043
1131
        r = self.last_patch()
1044
1132
        if r == None:
1045
 
            return EmptyTree(self.get_root_id())
 
1133
            return EmptyTree()
1046
1134
        else:
1047
1135
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1048
1136
 
1083
1171
 
1084
1172
            inv.rename(file_id, to_dir_id, to_tail)
1085
1173
 
1086
 
            print "%s => %s" % (from_rel, to_rel)
1087
 
 
1088
1174
            from_abs = self.abspath(from_rel)
1089
1175
            to_abs = self.abspath(to_rel)
1090
1176
            try:
1109
1195
 
1110
1196
        Note that to_name is only the last component of the new name;
1111
1197
        this doesn't change the directory.
 
1198
 
 
1199
        This returns a list of (from_path, to_path) pairs for each
 
1200
        entry that is moved.
1112
1201
        """
 
1202
        result = []
1113
1203
        self.lock_write()
1114
1204
        try:
1115
1205
            ## TODO: Option to move IDs only
1150
1240
            for f in from_paths:
1151
1241
                name_tail = splitpath(f)[-1]
1152
1242
                dest_path = appendpath(to_name, name_tail)
1153
 
                print "%s => %s" % (f, dest_path)
 
1243
                result.append((f, dest_path))
1154
1244
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1155
1245
                try:
1156
1246
                    os.rename(self.abspath(f), self.abspath(dest_path))
1162
1252
        finally:
1163
1253
            self.unlock()
1164
1254
 
 
1255
        return result
 
1256
 
1165
1257
 
1166
1258
    def revert(self, filenames, old_tree=None, backups=True):
1167
1259
        """Restore selected files to the versions from a previous tree.
1249
1341
            self.unlock()
1250
1342
 
1251
1343
 
 
1344
    def get_parent(self):
 
1345
        """Return the parent location of the branch.
 
1346
 
 
1347
        This is the default location for push/pull/missing.  The usual
 
1348
        pattern is that the user can override it by specifying a
 
1349
        location.
 
1350
        """
 
1351
        import errno
 
1352
        _locs = ['parent', 'pull', 'x-pull']
 
1353
        for l in _locs:
 
1354
            try:
 
1355
                return self.controlfile(l, 'r').read().strip('\n')
 
1356
            except IOError, e:
 
1357
                if e.errno != errno.ENOENT:
 
1358
                    raise
 
1359
        return None
 
1360
 
 
1361
 
 
1362
    def set_parent(self, url):
 
1363
        # TODO: Maybe delete old location files?
 
1364
        from bzrlib.atomicfile import AtomicFile
 
1365
        self.lock_write()
 
1366
        try:
 
1367
            f = AtomicFile(self.controlfilename('parent'))
 
1368
            try:
 
1369
                f.write(url + '\n')
 
1370
                f.commit()
 
1371
            finally:
 
1372
                f.close()
 
1373
        finally:
 
1374
            self.unlock()
 
1375
 
 
1376
    def check_revno(self, revno):
 
1377
        """\
 
1378
        Check whether a revno corresponds to any revision.
 
1379
        Zero (the NULL revision) is considered valid.
 
1380
        """
 
1381
        if revno != 0:
 
1382
            self.check_real_revno(revno)
 
1383
            
 
1384
    def check_real_revno(self, revno):
 
1385
        """\
 
1386
        Check whether a revno corresponds to a real revision.
 
1387
        Zero (the NULL revision) is considered invalid
 
1388
        """
 
1389
        if revno < 1 or revno > self.revno():
 
1390
            raise InvalidRevisionNumber(revno)
 
1391
        
 
1392
        
 
1393
 
1252
1394
 
1253
1395
class ScratchBranch(Branch):
1254
1396
    """Special test class: a branch that cleans up after itself.
1296
1438
        os.rmdir(base)
1297
1439
        copytree(self.base, base, symlinks=True)
1298
1440
        return ScratchBranch(base=base)
 
1441
 
 
1442
 
1299
1443
        
1300
1444
    def __del__(self):
1301
1445
        self.destroy()
1371
1515
    """Return a new tree-root file id."""
1372
1516
    return gen_file_id('TREE_ROOT')
1373
1517
 
 
1518
 
 
1519
def copy_branch(branch_from, to_location, revision=None):
 
1520
    """Copy branch_from into the existing directory to_location.
 
1521
 
 
1522
    revision
 
1523
        If not None, only revisions up to this point will be copied.
 
1524
        The head of the new branch will be that revision.
 
1525
 
 
1526
    to_location
 
1527
        The name of a local directory that exists but is empty.
 
1528
    """
 
1529
    from bzrlib.merge import merge
 
1530
 
 
1531
    assert isinstance(branch_from, Branch)
 
1532
    assert isinstance(to_location, basestring)
 
1533
    
 
1534
    br_to = Branch(to_location, init=True)
 
1535
    br_to.set_root_id(branch_from.get_root_id())
 
1536
    if revision is None:
 
1537
        revno = branch_from.revno()
 
1538
    else:
 
1539
        revno, rev_id = branch_from.get_revision_info(revision)
 
1540
    br_to.update_revisions(branch_from, stop_revision=revno)
 
1541
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
1542
          check_clean=False, ignore_zero=True)
 
1543
    br_to.set_parent(branch_from.base)
 
1544
    return br_to
 
1545
 
 
1546
def _trim_namespace(namespace, spec):
 
1547
    full_namespace = namespace + ':'
 
1548
    assert spec.startswith(full_namespace)
 
1549
    return spec[len(full_namespace):]