~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-07-22 22:36:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050722223654-93bf9d8cc0f0c128
todo

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
19
 
import os
 
18
import sys, os
20
19
 
21
20
import bzrlib
22
21
from bzrlib.trace import mutter, note
23
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
 
     splitpath, \
 
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
25
23
     sha_file, appendpath, file_kind
26
 
 
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
 
import bzrlib.errors
29
 
from bzrlib.textui import show_status
30
 
from bzrlib.revision import Revision
31
 
from bzrlib.xml import unpack_xml
32
 
from bzrlib.delta import compare_trees
33
 
from bzrlib.tree import EmptyTree, RevisionTree
34
 
import bzrlib.ui
35
 
 
36
 
 
 
24
from bzrlib.errors import BzrError
37
25
 
38
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
27
## TODO: Maybe include checks for common corruption of newlines, etc?
40
28
 
41
29
 
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
 
 
49
30
 
50
31
def find_branch(f, **args):
51
32
    if f and (f.startswith('http://') or f.startswith('https://')):
108
89
    It is not necessary that f exists.
109
90
 
110
91
    Basically we keep looking up until we find the control directory or
111
 
    run into the root.  If there isn't one, raises NotBranchError.
112
 
    """
 
92
    run into the root."""
113
93
    if f == None:
114
94
        f = os.getcwd()
115
95
    elif hasattr(os.path, 'realpath'):
128
108
        head, tail = os.path.split(f)
129
109
        if head == f:
130
110
            # reached the root, whatever that may be
131
 
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
 
111
            raise BzrError('%r is not in a branch' % orig_f)
132
112
        f = head
133
 
 
134
 
 
135
 
 
136
 
# XXX: move into bzrlib.errors; subclass BzrError    
 
113
    
137
114
class DivergedBranches(Exception):
138
115
    def __init__(self, branch1, branch2):
139
116
        self.branch1 = branch1
141
118
        Exception.__init__(self, "These branches have diverged.")
142
119
 
143
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)
 
127
 
 
128
 
144
129
######################################################################
145
130
# branch objects
146
131
 
327
312
            self.controlfile(f, 'w').write('')
328
313
        mutter('created control directory in ' + self.base)
329
314
 
330
 
        # if we want per-tree root ids then this is the place to set
331
 
        # them; they're not needed for now and so ommitted for
332
 
        # simplicity.
333
 
        pack_xml(Inventory(), self.controlfile('inventory','w'))
 
315
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
334
316
 
335
317
 
336
318
    def _check_format(self):
414
396
                         """Inventory for the working copy.""")
415
397
 
416
398
 
417
 
    def add(self, files, ids=None):
 
399
    def add(self, files, verbose=False, ids=None):
418
400
        """Make files versioned.
419
401
 
420
 
        Note that the command line normally calls smart_add instead,
421
 
        which can automatically recurse.
 
402
        Note that the command line normally calls smart_add instead.
422
403
 
423
404
        This puts the files in the Added state, so that they will be
424
405
        recorded by the next commit.
434
415
        TODO: Perhaps have an option to add the ids even if the files do
435
416
              not (yet) exist.
436
417
 
437
 
        TODO: Perhaps yield the ids and paths as they're added.
 
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.
438
424
        """
 
425
        from bzrlib.textui import show_status
439
426
        # TODO: Re-adding a file that is removed in the working copy
440
427
        # should probably put it back with the previous ID.
441
428
        if isinstance(files, basestring):
476
463
                    file_id = gen_file_id(f)
477
464
                inv.add_path(f, kind=kind, file_id=file_id)
478
465
 
 
466
                if verbose:
 
467
                    print 'added', quotefn(f)
 
468
 
479
469
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
480
470
 
481
471
            self._write_inventory(inv)
511
501
        is the opposite of add.  Removing it is consistent with most
512
502
        other tools.  Maybe an option.
513
503
        """
 
504
        from bzrlib.textui import show_status
514
505
        ## TODO: Normalize names
515
506
        ## TODO: Remove nested loops; better scalability
516
507
        if isinstance(files, basestring):
591
582
            f.close()
592
583
 
593
584
 
594
 
    def get_revision_xml(self, revision_id):
595
 
        """Return XML file object for revision object."""
596
 
        if not revision_id or not isinstance(revision_id, basestring):
597
 
            raise InvalidRevisionId(revision_id)
598
 
 
599
 
        self.lock_read()
600
 
        try:
601
 
            try:
602
 
                return self.revision_store[revision_id]
603
 
            except IndexError:
604
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
605
 
        finally:
606
 
            self.unlock()
607
 
 
608
 
 
609
585
    def get_revision(self, revision_id):
610
586
        """Return the Revision object for a named revision"""
611
 
        xml_file = self.get_revision_xml(revision_id)
 
587
        from bzrlib.revision import Revision
 
588
        from bzrlib.xml import unpack_xml
612
589
 
 
590
        self.lock_read()
613
591
        try:
614
 
            r = unpack_xml(Revision, xml_file)
615
 
        except SyntaxError, e:
616
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
617
 
                                         [revision_id,
618
 
                                          str(e)])
 
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()
619
597
            
620
598
        assert r.revision_id == revision_id
621
599
        return r
622
 
 
623
 
 
624
 
    def get_revision_delta(self, revno):
625
 
        """Return the delta for one revision.
626
 
 
627
 
        The delta is relative to its mainline predecessor, or the
628
 
        empty tree for revision 1.
629
 
        """
630
 
        assert isinstance(revno, int)
631
 
        rh = self.revision_history()
632
 
        if not (1 <= revno <= len(rh)):
633
 
            raise InvalidRevisionNumber(revno)
634
 
 
635
 
        # revno is 1-based; list is 0-based
636
 
 
637
 
        new_tree = self.revision_tree(rh[revno-1])
638
 
        if revno == 1:
639
 
            old_tree = EmptyTree()
640
 
        else:
641
 
            old_tree = self.revision_tree(rh[revno-2])
642
 
 
643
 
        return compare_trees(old_tree, new_tree)
644
 
 
645
600
        
646
601
 
647
602
    def get_revision_sha1(self, revision_id):
652
607
        # the revision, (add signatures/remove signatures) and still
653
608
        # have all hash pointers stay consistent.
654
609
        # But for now, just hash the contents.
655
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
 
610
        return sha_file(self.revision_store[revision_id])
656
611
 
657
612
 
658
613
    def get_inventory(self, inventory_id):
664
619
        from bzrlib.inventory import Inventory
665
620
        from bzrlib.xml import unpack_xml
666
621
 
667
 
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
668
 
 
669
 
 
670
 
    def get_inventory_xml(self, inventory_id):
671
 
        """Get inventory XML as a file object."""
672
 
        return self.inventory_store[inventory_id]
 
622
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
673
623
            
674
624
 
675
625
    def get_inventory_sha1(self, inventory_id):
676
626
        """Return the sha1 hash of the inventory entry
677
627
        """
678
 
        return sha_file(self.get_inventory_xml(inventory_id))
 
628
        return sha_file(self.inventory_store[inventory_id])
679
629
 
680
630
 
681
631
    def get_revision_inventory(self, revision_id):
747
697
                return r+1, my_history[r]
748
698
        return None, None
749
699
 
 
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
 
750
721
 
751
722
    def revno(self):
752
723
        """Return current revision number for this branch.
767
738
            return None
768
739
 
769
740
 
770
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
741
    def missing_revisions(self, other, stop_revision=None):
771
742
        """
772
743
        If self and other have not diverged, return a list of the revisions
773
744
        present in other, but missing from self.
806
777
        if stop_revision is None:
807
778
            stop_revision = other_len
808
779
        elif stop_revision > other_len:
809
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
780
            raise NoSuchRevision(self, stop_revision)
810
781
        
811
782
        return other_history[self_len:stop_revision]
812
783
 
813
784
 
814
785
    def update_revisions(self, other, stop_revision=None):
815
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
816
807
        """
817
 
        from bzrlib.fetch import greedy_fetch
818
 
 
819
 
        pb = bzrlib.ui.ui_factory.progress_bar()
 
808
        from bzrlib.progress import ProgressBar
 
809
 
 
810
        pb = ProgressBar()
 
811
 
820
812
        pb.update('comparing histories')
821
 
 
822
813
        revision_ids = self.missing_revisions(other, stop_revision)
823
814
 
824
 
        if len(revision_ids) > 0:
825
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
826
 
        else:
827
 
            count = 0
828
 
        self.append_revision(*revision_ids)
829
 
        ## note("Added %d revisions." % count)
830
 
        pb.clear()
831
 
 
832
 
        
833
 
        
834
 
    def install_revisions(self, other, revision_ids, pb):
835
815
        if hasattr(other.revision_store, "prefetch"):
836
816
            other.revision_store.prefetch(revision_ids)
837
817
        if hasattr(other.inventory_store, "prefetch"):
838
818
            inventory_ids = [other.get_revision(r).inventory_id
839
819
                             for r in revision_ids]
840
820
            other.inventory_store.prefetch(inventory_ids)
841
 
 
842
 
        if pb is None:
843
 
            pb = bzrlib.ui.ui_factory.progress_bar()
844
821
                
845
822
        revisions = []
846
823
        needed_texts = set()
847
824
        i = 0
848
 
 
849
 
        failures = set()
850
 
        for i, rev_id in enumerate(revision_ids):
851
 
            pb.update('fetching revision', i+1, len(revision_ids))
852
 
            try:
853
 
                rev = other.get_revision(rev_id)
854
 
            except bzrlib.errors.NoSuchRevision:
855
 
                failures.add(rev_id)
856
 
                continue
857
 
 
 
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)
858
829
            revisions.append(rev)
859
830
            inv = other.get_inventory(str(rev.inventory_id))
860
831
            for key, entry in inv.iter_entries():
865
836
 
866
837
        pb.clear()
867
838
                    
868
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
869
 
                                                    needed_texts)
870
 
        #print "Added %d texts." % count 
 
839
        count = self.text_store.copy_multi(other.text_store, needed_texts)
 
840
        print "Added %d texts." % count 
871
841
        inventory_ids = [ f.inventory_id for f in revisions ]
872
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
873
 
                                                         inventory_ids)
874
 
        #print "Added %d inventories." % count 
 
842
        count = self.inventory_store.copy_multi(other.inventory_store, 
 
843
                                                inventory_ids)
 
844
        print "Added %d inventories." % count 
875
845
        revision_ids = [ f.revision_id for f in revisions]
876
 
 
877
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
878
 
                                                          revision_ids,
879
 
                                                          permit_failure=True)
880
 
        assert len(cp_fail) == 0 
881
 
        return count, failures
882
 
       
883
 
 
 
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
        
884
853
    def commit(self, *args, **kw):
885
854
        from bzrlib.commit import commit
886
855
        commit(self, *args, **kw)
891
860
        revno, info = self.get_revision_info(revision)
892
861
        return info
893
862
 
894
 
 
895
 
    def revision_id_to_revno(self, revision_id):
896
 
        """Given a revision id, return its revno"""
897
 
        history = self.revision_history()
898
 
        try:
899
 
            return history.index(revision_id) + 1
900
 
        except ValueError:
901
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
902
 
 
903
 
 
904
863
    def get_revision_info(self, revision):
905
864
        """Return (revno, revision id) for revision identifier.
906
865
 
1059
1018
 
1060
1019
        `revision_id` may be None for the null revision, in which case
1061
1020
        an `EmptyTree` is returned."""
 
1021
        from bzrlib.tree import EmptyTree, RevisionTree
1062
1022
        # TODO: refactor this to use an existing revision object
1063
1023
        # so we don't need to read it in twice.
1064
1024
        if revision_id == None:
1065
 
            return EmptyTree()
 
1025
            return EmptyTree(self.get_root_id())
1066
1026
        else:
1067
1027
            inv = self.get_revision_inventory(revision_id)
1068
1028
            return RevisionTree(self.text_store, inv)
1079
1039
 
1080
1040
        If there are no revisions yet, return an `EmptyTree`.
1081
1041
        """
 
1042
        from bzrlib.tree import EmptyTree, RevisionTree
1082
1043
        r = self.last_patch()
1083
1044
        if r == None:
1084
 
            return EmptyTree()
 
1045
            return EmptyTree(self.get_root_id())
1085
1046
        else:
1086
1047
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1087
1048
 
1122
1083
 
1123
1084
            inv.rename(file_id, to_dir_id, to_tail)
1124
1085
 
 
1086
            print "%s => %s" % (from_rel, to_rel)
 
1087
 
1125
1088
            from_abs = self.abspath(from_rel)
1126
1089
            to_abs = self.abspath(to_rel)
1127
1090
            try:
1146
1109
 
1147
1110
        Note that to_name is only the last component of the new name;
1148
1111
        this doesn't change the directory.
1149
 
 
1150
 
        This returns a list of (from_path, to_path) pairs for each
1151
 
        entry that is moved.
1152
1112
        """
1153
 
        result = []
1154
1113
        self.lock_write()
1155
1114
        try:
1156
1115
            ## TODO: Option to move IDs only
1191
1150
            for f in from_paths:
1192
1151
                name_tail = splitpath(f)[-1]
1193
1152
                dest_path = appendpath(to_name, name_tail)
1194
 
                result.append((f, dest_path))
 
1153
                print "%s => %s" % (f, dest_path)
1195
1154
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1196
1155
                try:
1197
1156
                    os.rename(self.abspath(f), self.abspath(dest_path))
1203
1162
        finally:
1204
1163
            self.unlock()
1205
1164
 
1206
 
        return result
1207
 
 
1208
1165
 
1209
1166
    def revert(self, filenames, old_tree=None, backups=True):
1210
1167
        """Restore selected files to the versions from a previous tree.