~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: aaron.bentley at utoronto
  • Date: 2005-08-26 06:34:07 UTC
  • mto: (1185.3.4)
  • mto: This revision was merged to the branch mainline in revision 1178.
  • Revision ID: aaron.bentley@utoronto.ca-20050826063406-84d09e206c6c5e73
Shortened conflict markers to 7 characters, to please smerge

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
24
     splitpath, \
25
25
     sha_file, appendpath, file_kind
26
 
 
27
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
28
27
import bzrlib.errors
29
28
from bzrlib.textui import show_status
31
30
from bzrlib.xml import unpack_xml
32
31
from bzrlib.delta import compare_trees
33
32
from bzrlib.tree import EmptyTree, RevisionTree
34
 
import bzrlib.ui
35
 
 
36
 
 
37
 
 
 
33
from bzrlib.progress import ProgressBar
 
34
 
 
35
       
38
36
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
37
## TODO: Maybe include checks for common corruption of newlines, etc?
40
38
 
108
106
    It is not necessary that f exists.
109
107
 
110
108
    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
 
    """
 
109
    run into the root."""
113
110
    if f == None:
114
111
        f = os.getcwd()
115
112
    elif hasattr(os.path, 'realpath'):
128
125
        head, tail = os.path.split(f)
129
126
        if head == f:
130
127
            # reached the root, whatever that may be
131
 
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
 
128
            raise BzrError('%r is not in a branch' % orig_f)
132
129
        f = head
133
 
 
134
 
 
135
 
 
136
 
# XXX: move into bzrlib.errors; subclass BzrError    
 
130
    
137
131
class DivergedBranches(Exception):
138
132
    def __init__(self, branch1, branch2):
139
133
        self.branch1 = branch1
219
213
            self._lock.unlock()
220
214
 
221
215
 
 
216
 
222
217
    def lock_write(self):
223
218
        if self._lock_mode:
224
219
            if self._lock_mode != 'w':
234
229
            self._lock_count = 1
235
230
 
236
231
 
 
232
 
237
233
    def lock_read(self):
238
234
        if self._lock_mode:
239
235
            assert self._lock_mode in ('r', 'w'), \
246
242
            self._lock_mode = 'r'
247
243
            self._lock_count = 1
248
244
                        
 
245
 
 
246
            
249
247
    def unlock(self):
250
248
        if not self._lock_mode:
251
249
            from errors import LockError
258
256
            self._lock = None
259
257
            self._lock_mode = self._lock_count = None
260
258
 
 
259
 
261
260
    def abspath(self, name):
262
261
        """Return absolute filename for something in the branch"""
263
262
        return os.path.join(self.base, name)
264
263
 
 
264
 
265
265
    def relpath(self, path):
266
266
        """Return path relative to this branch of something inside it.
267
267
 
268
268
        Raises an error if path is not in this branch."""
269
269
        return _relpath(self.base, path)
270
270
 
 
271
 
271
272
    def controlfilename(self, file_or_path):
272
273
        """Return location relative to branch."""
273
274
        if isinstance(file_or_path, basestring):
300
301
        else:
301
302
            raise BzrError("invalid controlfile mode %r" % mode)
302
303
 
 
304
 
 
305
 
303
306
    def _make_control(self):
304
307
        from bzrlib.inventory import Inventory
305
308
        from bzrlib.xml import pack_xml
323
326
        # simplicity.
324
327
        pack_xml(Inventory(), self.controlfile('inventory','w'))
325
328
 
 
329
 
326
330
    def _check_format(self):
327
331
        """Check this branch format is supported.
328
332
 
404
408
                         """Inventory for the working copy.""")
405
409
 
406
410
 
407
 
    def add(self, files, ids=None):
 
411
    def add(self, files, verbose=False, ids=None):
408
412
        """Make files versioned.
409
413
 
410
 
        Note that the command line normally calls smart_add instead,
411
 
        which can automatically recurse.
 
414
        Note that the command line normally calls smart_add instead.
412
415
 
413
416
        This puts the files in the Added state, so that they will be
414
417
        recorded by the next commit.
424
427
        TODO: Perhaps have an option to add the ids even if the files do
425
428
              not (yet) exist.
426
429
 
427
 
        TODO: Perhaps yield the ids and paths as they're added.
 
430
        TODO: Perhaps return the ids of the files?  But then again it
 
431
              is easy to retrieve them if they're needed.
 
432
 
 
433
        TODO: Adding a directory should optionally recurse down and
 
434
              add all non-ignored children.  Perhaps do that in a
 
435
              higher-level method.
428
436
        """
429
437
        # TODO: Re-adding a file that is removed in the working copy
430
438
        # should probably put it back with the previous ID.
466
474
                    file_id = gen_file_id(f)
467
475
                inv.add_path(f, kind=kind, file_id=file_id)
468
476
 
 
477
                if verbose:
 
478
                    print 'added', quotefn(f)
 
479
 
469
480
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
470
481
 
471
482
            self._write_inventory(inv)
796
807
        if stop_revision is None:
797
808
            stop_revision = other_len
798
809
        elif stop_revision > other_len:
799
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
810
            raise NoSuchRevision(self, stop_revision)
800
811
        
801
812
        return other_history[self_len:stop_revision]
802
813
 
803
814
 
804
815
    def update_revisions(self, other, stop_revision=None):
805
816
        """Pull in all new revisions from other branch.
 
817
        
 
818
        >>> from bzrlib.commit import commit
 
819
        >>> bzrlib.trace.silent = True
 
820
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
 
821
        >>> br1.add('foo')
 
822
        >>> br1.add('bar')
 
823
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
 
824
        >>> br2 = ScratchBranch()
 
825
        >>> br2.update_revisions(br1)
 
826
        Added 2 texts.
 
827
        Added 1 inventories.
 
828
        Added 1 revisions.
 
829
        >>> br2.revision_history()
 
830
        [u'REVISION-ID-1']
 
831
        >>> br2.update_revisions(br1)
 
832
        Added 0 revisions.
 
833
        >>> br1.text_store.total_size() == br2.text_store.total_size()
 
834
        True
806
835
        """
807
836
        from bzrlib.fetch import greedy_fetch
808
 
 
809
 
        pb = bzrlib.ui.ui_factory.progress_bar()
 
837
        pb = ProgressBar()
810
838
        pb.update('comparing histories')
811
 
 
812
839
        revision_ids = self.missing_revisions(other, stop_revision)
813
 
 
814
840
        if len(revision_ids) > 0:
815
841
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
816
842
        else:
817
843
            count = 0
818
844
        self.append_revision(*revision_ids)
819
 
        ## note("Added %d revisions." % count)
820
 
        pb.clear()
821
 
 
822
 
    def install_revisions(self, other, revision_ids, pb):
 
845
        print "Added %d revisions." % count
 
846
                    
 
847
    def install_revisions(self, other, revision_ids, pb=None):
 
848
        if pb is None:
 
849
            pb = ProgressBar()
823
850
        if hasattr(other.revision_store, "prefetch"):
824
851
            other.revision_store.prefetch(revision_ids)
825
852
        if hasattr(other.inventory_store, "prefetch"):
826
853
            inventory_ids = [other.get_revision(r).inventory_id
827
854
                             for r in revision_ids]
828
855
            other.inventory_store.prefetch(inventory_ids)
829
 
 
830
 
        if pb is None:
831
 
            pb = bzrlib.ui.ui_factory.progress_bar()
832
856
                
833
857
        revisions = []
834
858
        needed_texts = set()
835
859
        i = 0
836
 
 
837
860
        failures = set()
838
861
        for i, rev_id in enumerate(revision_ids):
839
862
            pb.update('fetching revision', i+1, len(revision_ids))
842
865
            except bzrlib.errors.NoSuchRevision:
843
866
                failures.add(rev_id)
844
867
                continue
845
 
 
846
868
            revisions.append(rev)
847
869
            inv = other.get_inventory(str(rev.inventory_id))
848
870
            for key, entry in inv.iter_entries():
855
877
                    
856
878
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
857
879
                                                    needed_texts)
858
 
        #print "Added %d texts." % count 
 
880
        print "Added %d texts." % count 
859
881
        inventory_ids = [ f.inventory_id for f in revisions ]
860
882
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
861
883
                                                         inventory_ids)
862
 
        #print "Added %d inventories." % count 
 
884
        print "Added %d inventories." % count 
863
885
        revision_ids = [ f.revision_id for f in revisions]
864
 
 
865
886
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
866
887
                                                          revision_ids,
867
888
                                                          permit_failure=True)
868
889
        assert len(cp_fail) == 0 
869
890
        return count, failures
870
891
       
871
 
 
872
892
    def commit(self, *args, **kw):
873
893
        from bzrlib.commit import commit
874
894
        commit(self, *args, **kw)
876
896
 
877
897
    def lookup_revision(self, revision):
878
898
        """Return the revision identifier for a given revision information."""
879
 
        revno, info = self.get_revision_info(revision)
 
899
        revno, info = self._get_revision_info(revision)
880
900
        return info
881
901
 
882
 
 
883
 
    def revision_id_to_revno(self, revision_id):
884
 
        """Given a revision id, return its revno"""
885
 
        history = self.revision_history()
886
 
        try:
887
 
            return history.index(revision_id) + 1
888
 
        except ValueError:
889
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
890
 
 
891
 
 
892
902
    def get_revision_info(self, revision):
893
903
        """Return (revno, revision id) for revision identifier.
894
904
 
897
907
        revision can also be a string, in which case it is parsed for something like
898
908
            'date:' or 'revid:' etc.
899
909
        """
 
910
        revno, rev_id = self._get_revision_info(revision)
 
911
        if revno is None:
 
912
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
913
        return revno, rev_id
 
914
 
 
915
    def get_rev_id(self, revno, history=None):
 
916
        """Find the revision id of the specified revno."""
 
917
        if revno == 0:
 
918
            return None
 
919
        if history is None:
 
920
            history = self.revision_history()
 
921
        elif revno <= 0 or revno > len(history):
 
922
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
923
        return history[revno - 1]
 
924
 
 
925
    def _get_revision_info(self, revision):
 
926
        """Return (revno, revision id) for revision specifier.
 
927
 
 
928
        revision can be an integer, in which case it is assumed to be revno
 
929
        (though this will translate negative values into positive ones)
 
930
        revision can also be a string, in which case it is parsed for something
 
931
        like 'date:' or 'revid:' etc.
 
932
 
 
933
        A revid is always returned.  If it is None, the specifier referred to
 
934
        the null revision.  If the revid does not occur in the revision
 
935
        history, revno will be None.
 
936
        """
 
937
        
900
938
        if revision is None:
901
939
            return 0, None
902
940
        revno = None
906
944
            pass
907
945
        revs = self.revision_history()
908
946
        if isinstance(revision, int):
909
 
            if revision == 0:
910
 
                return 0, None
911
 
            # Mabye we should do this first, but we don't need it if revision == 0
912
947
            if revision < 0:
913
948
                revno = len(revs) + revision + 1
914
949
            else:
915
950
                revno = revision
 
951
            rev_id = self.get_rev_id(revno, revs)
916
952
        elif isinstance(revision, basestring):
917
953
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
918
954
                if revision.startswith(prefix):
919
 
                    revno = func(self, revs, revision)
 
955
                    result = func(self, revs, revision)
 
956
                    if len(result) > 1:
 
957
                        revno, rev_id = result
 
958
                    else:
 
959
                        revno = result[0]
 
960
                        rev_id = self.get_rev_id(revno, revs)
920
961
                    break
921
962
            else:
922
 
                raise BzrError('No namespace registered for string: %r' % revision)
 
963
                raise BzrError('No namespace registered for string: %r' %
 
964
                               revision)
 
965
        else:
 
966
            raise TypeError('Unhandled revision type %s' % revision)
923
967
 
924
 
        if revno is None or revno <= 0 or revno > len(revs):
925
 
            raise BzrError("no such revision %s" % revision)
926
 
        return revno, revs[revno-1]
 
968
        if revno is None:
 
969
            if rev_id is None:
 
970
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
971
        return revno, rev_id
927
972
 
928
973
    def _namespace_revno(self, revs, revision):
929
974
        """Lookup a revision by revision number"""
930
975
        assert revision.startswith('revno:')
931
976
        try:
932
 
            return int(revision[6:])
 
977
            return (int(revision[6:]),)
933
978
        except ValueError:
934
979
            return None
935
980
    REVISION_NAMESPACES['revno:'] = _namespace_revno
936
981
 
937
982
    def _namespace_revid(self, revs, revision):
938
983
        assert revision.startswith('revid:')
 
984
        rev_id = revision[len('revid:'):]
939
985
        try:
940
 
            return revs.index(revision[6:]) + 1
 
986
            return revs.index(rev_id) + 1, rev_id
941
987
        except ValueError:
942
 
            return None
 
988
            return None, rev_id
943
989
    REVISION_NAMESPACES['revid:'] = _namespace_revid
944
990
 
945
991
    def _namespace_last(self, revs, revision):
947
993
        try:
948
994
            offset = int(revision[5:])
949
995
        except ValueError:
950
 
            return None
 
996
            return (None,)
951
997
        else:
952
998
            if offset <= 0:
953
999
                raise BzrError('You must supply a positive value for --revision last:XXX')
954
 
            return len(revs) - offset + 1
 
1000
            return (len(revs) - offset + 1,)
955
1001
    REVISION_NAMESPACES['last:'] = _namespace_last
956
1002
 
957
1003
    def _namespace_tag(self, revs, revision):
1032
1078
                # TODO: Handle timezone.
1033
1079
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1034
1080
                if first >= dt and (last is None or dt >= last):
1035
 
                    return i+1
 
1081
                    return (i+1,)
1036
1082
        else:
1037
1083
            for i in range(len(revs)):
1038
1084
                r = self.get_revision(revs[i])
1039
1085
                # TODO: Handle timezone.
1040
1086
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1041
1087
                if first <= dt and (last is None or dt <= last):
1042
 
                    return i+1
 
1088
                    return (i+1,)
1043
1089
    REVISION_NAMESPACES['date:'] = _namespace_date
1044
1090
 
1045
1091
    def revision_tree(self, revision_id):
1110
1156
 
1111
1157
            inv.rename(file_id, to_dir_id, to_tail)
1112
1158
 
 
1159
            print "%s => %s" % (from_rel, to_rel)
 
1160
 
1113
1161
            from_abs = self.abspath(from_rel)
1114
1162
            to_abs = self.abspath(to_rel)
1115
1163
            try:
1134
1182
 
1135
1183
        Note that to_name is only the last component of the new name;
1136
1184
        this doesn't change the directory.
1137
 
 
1138
 
        This returns a list of (from_path, to_path) pairs for each
1139
 
        entry that is moved.
1140
1185
        """
1141
 
        result = []
1142
1186
        self.lock_write()
1143
1187
        try:
1144
1188
            ## TODO: Option to move IDs only
1179
1223
            for f in from_paths:
1180
1224
                name_tail = splitpath(f)[-1]
1181
1225
                dest_path = appendpath(to_name, name_tail)
1182
 
                result.append((f, dest_path))
 
1226
                print "%s => %s" % (f, dest_path)
1183
1227
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1184
1228
                try:
1185
1229
                    os.rename(self.abspath(f), self.abspath(dest_path))
1191
1235
        finally:
1192
1236
            self.unlock()
1193
1237
 
1194
 
        return result
1195
 
 
1196
1238
 
1197
1239
    def revert(self, filenames, old_tree=None, backups=True):
1198
1240
        """Restore selected files to the versions from a previous tree.
1402
1444
    """Return a new tree-root file id."""
1403
1445
    return gen_file_id('TREE_ROOT')
1404
1446
 
1405
 
 
1406
 
def pull_loc(branch):
1407
 
    # TODO: Should perhaps just make attribute be 'base' in
1408
 
    # RemoteBranch and Branch?
1409
 
    if hasattr(branch, "baseurl"):
1410
 
        return branch.baseurl
1411
 
    else:
1412
 
        return branch.base
1413
 
 
1414
 
 
1415
 
def copy_branch(branch_from, to_location, revision=None):
1416
 
    """Copy branch_from into the existing directory to_location.
1417
 
 
1418
 
    If revision is not None, the head of the new branch will be revision.
1419
 
    """
1420
 
    from bzrlib.merge import merge
1421
 
    from bzrlib.branch import Branch
1422
 
    br_to = Branch(to_location, init=True)
1423
 
    br_to.set_root_id(branch_from.get_root_id())
1424
 
    if revision is None:
1425
 
        revno = branch_from.revno()
1426
 
    else:
1427
 
        revno, rev_id = branch_from.get_revision_info(revision)
1428
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1429
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1430
 
          check_clean=False, ignore_zero=True)
1431
 
    from_location = pull_loc(branch_from)
1432
 
    br_to.controlfile("x-pull", "wb").write(from_location + "\n")
1433