~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Robert Collins
  • Date: 2005-09-06 15:39:59 UTC
  • mto: (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1397.
  • Revision ID: robertc@robertcollins.net-20050906153959-c79d671a510ca2fc
move RemoteStore to store.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
173
173
    def __init__(self, base, init=False, find_root=True):
174
174
        """Create new branch object at a particular location.
175
175
 
176
 
        base -- Base directory for the branch.
 
176
        base -- Base directory for the branch. May be a file:// url.
177
177
        
178
178
        init -- If True, create new control files in a previously
179
179
             unversioned directory.  If False, the branch must already
192
192
        elif find_root:
193
193
            self.base = find_branch_root(base)
194
194
        else:
 
195
            if base.startswith("file://"):
 
196
                base = base[7:]
195
197
            self.base = os.path.realpath(base)
196
198
            if not isdir(self.controlfilename('.')):
197
199
                from errors import NotBranchError
218
220
            warn("branch %r was not explicitly unlocked" % self)
219
221
            self._lock.unlock()
220
222
 
221
 
 
222
 
 
223
223
    def lock_write(self):
224
224
        if self._lock_mode:
225
225
            if self._lock_mode != 'w':
235
235
            self._lock_count = 1
236
236
 
237
237
 
238
 
 
239
238
    def lock_read(self):
240
239
        if self._lock_mode:
241
240
            assert self._lock_mode in ('r', 'w'), \
248
247
            self._lock_mode = 'r'
249
248
            self._lock_count = 1
250
249
                        
251
 
 
252
 
            
253
250
    def unlock(self):
254
251
        if not self._lock_mode:
255
252
            from errors import LockError
262
259
            self._lock = None
263
260
            self._lock_mode = self._lock_count = None
264
261
 
265
 
 
266
262
    def abspath(self, name):
267
263
        """Return absolute filename for something in the branch"""
268
264
        return os.path.join(self.base, name)
269
265
 
270
 
 
271
266
    def relpath(self, path):
272
267
        """Return path relative to this branch of something inside it.
273
268
 
274
269
        Raises an error if path is not in this branch."""
275
270
        return _relpath(self.base, path)
276
271
 
277
 
 
278
272
    def controlfilename(self, file_or_path):
279
273
        """Return location relative to branch."""
280
274
        if isinstance(file_or_path, basestring):
307
301
        else:
308
302
            raise BzrError("invalid controlfile mode %r" % mode)
309
303
 
310
 
 
311
 
 
312
304
    def _make_control(self):
313
305
        from bzrlib.inventory import Inventory
314
306
        from bzrlib.xml import pack_xml
332
324
        # simplicity.
333
325
        pack_xml(Inventory(), self.controlfile('inventory','w'))
334
326
 
335
 
 
336
327
    def _check_format(self):
337
328
        """Check this branch format is supported.
338
329
 
414
405
                         """Inventory for the working copy.""")
415
406
 
416
407
 
417
 
    def add(self, files, verbose=False, ids=None):
 
408
    def add(self, files, ids=None):
418
409
        """Make files versioned.
419
410
 
420
 
        Note that the command line normally calls smart_add instead.
 
411
        Note that the command line normally calls smart_add instead,
 
412
        which can automatically recurse.
421
413
 
422
414
        This puts the files in the Added state, so that they will be
423
415
        recorded by the next commit.
433
425
        TODO: Perhaps have an option to add the ids even if the files do
434
426
              not (yet) exist.
435
427
 
436
 
        TODO: Perhaps return the ids of the files?  But then again it
437
 
              is easy to retrieve them if they're needed.
438
 
 
439
 
        TODO: Adding a directory should optionally recurse down and
440
 
              add all non-ignored children.  Perhaps do that in a
441
 
              higher-level method.
 
428
        TODO: Perhaps yield the ids and paths as they're added.
442
429
        """
443
430
        # TODO: Re-adding a file that is removed in the working copy
444
431
        # should probably put it back with the previous ID.
480
467
                    file_id = gen_file_id(f)
481
468
                inv.add_path(f, kind=kind, file_id=file_id)
482
469
 
483
 
                if verbose:
484
 
                    print 'added', quotefn(f)
485
 
 
486
470
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
487
471
 
488
472
            self._write_inventory(inv)
597
581
        finally:
598
582
            f.close()
599
583
 
600
 
 
601
584
    def get_revision_xml(self, revision_id):
602
585
        """Return XML file object for revision object."""
603
586
        if not revision_id or not isinstance(revision_id, basestring):
612
595
        finally:
613
596
            self.unlock()
614
597
 
615
 
 
616
598
    def get_revision(self, revision_id):
617
599
        """Return the Revision object for a named revision"""
618
600
        xml_file = self.get_revision_xml(revision_id)
627
609
        assert r.revision_id == revision_id
628
610
        return r
629
611
 
630
 
 
631
612
    def get_revision_delta(self, revno):
632
613
        """Return the delta for one revision.
633
614
 
834
815
            count = 0
835
816
        self.append_revision(*revision_ids)
836
817
        ## note("Added %d revisions." % count)
 
818
        pb.clear()
837
819
 
838
 
        
839
820
    def install_revisions(self, other, revision_ids, pb):
840
821
        if hasattr(other.revision_store, "prefetch"):
841
822
            other.revision_store.prefetch(revision_ids)
872
853
                    
873
854
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
874
855
                                                    needed_texts)
875
 
        print "Added %d texts." % count 
 
856
        #print "Added %d texts." % count 
876
857
        inventory_ids = [ f.inventory_id for f in revisions ]
877
858
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
878
859
                                                         inventory_ids)
879
 
        print "Added %d inventories." % count 
 
860
        #print "Added %d inventories." % count 
880
861
        revision_ids = [ f.revision_id for f in revisions]
881
862
 
882
863
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
893
874
 
894
875
    def lookup_revision(self, revision):
895
876
        """Return the revision identifier for a given revision information."""
896
 
        revno, info = self.get_revision_info(revision)
 
877
        revno, info = self._get_revision_info(revision)
897
878
        return info
898
879
 
899
880
 
914
895
        revision can also be a string, in which case it is parsed for something like
915
896
            'date:' or 'revid:' etc.
916
897
        """
 
898
        revno, rev_id = self._get_revision_info(revision)
 
899
        if revno is None:
 
900
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
901
        return revno, rev_id
 
902
 
 
903
    def get_rev_id(self, revno, history=None):
 
904
        """Find the revision id of the specified revno."""
 
905
        if revno == 0:
 
906
            return None
 
907
        if history is None:
 
908
            history = self.revision_history()
 
909
        elif revno <= 0 or revno > len(history):
 
910
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
911
        return history[revno - 1]
 
912
 
 
913
    def _get_revision_info(self, revision):
 
914
        """Return (revno, revision id) for revision specifier.
 
915
 
 
916
        revision can be an integer, in which case it is assumed to be revno
 
917
        (though this will translate negative values into positive ones)
 
918
        revision can also be a string, in which case it is parsed for something
 
919
        like 'date:' or 'revid:' etc.
 
920
 
 
921
        A revid is always returned.  If it is None, the specifier referred to
 
922
        the null revision.  If the revid does not occur in the revision
 
923
        history, revno will be None.
 
924
        """
 
925
        
917
926
        if revision is None:
918
927
            return 0, None
919
928
        revno = None
923
932
            pass
924
933
        revs = self.revision_history()
925
934
        if isinstance(revision, int):
926
 
            if revision == 0:
927
 
                return 0, None
928
 
            # Mabye we should do this first, but we don't need it if revision == 0
929
935
            if revision < 0:
930
936
                revno = len(revs) + revision + 1
931
937
            else:
932
938
                revno = revision
 
939
            rev_id = self.get_rev_id(revno, revs)
933
940
        elif isinstance(revision, basestring):
934
941
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
935
942
                if revision.startswith(prefix):
936
 
                    revno = func(self, revs, revision)
 
943
                    result = func(self, revs, revision)
 
944
                    if len(result) > 1:
 
945
                        revno, rev_id = result
 
946
                    else:
 
947
                        revno = result[0]
 
948
                        rev_id = self.get_rev_id(revno, revs)
937
949
                    break
938
950
            else:
939
 
                raise BzrError('No namespace registered for string: %r' % revision)
 
951
                raise BzrError('No namespace registered for string: %r' %
 
952
                               revision)
 
953
        else:
 
954
            raise TypeError('Unhandled revision type %s' % revision)
940
955
 
941
 
        if revno is None or revno <= 0 or revno > len(revs):
942
 
            raise BzrError("no such revision %s" % revision)
943
 
        return revno, revs[revno-1]
 
956
        if revno is None:
 
957
            if rev_id is None:
 
958
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
959
        return revno, rev_id
944
960
 
945
961
    def _namespace_revno(self, revs, revision):
946
962
        """Lookup a revision by revision number"""
947
963
        assert revision.startswith('revno:')
948
964
        try:
949
 
            return int(revision[6:])
 
965
            return (int(revision[6:]),)
950
966
        except ValueError:
951
967
            return None
952
968
    REVISION_NAMESPACES['revno:'] = _namespace_revno
953
969
 
954
970
    def _namespace_revid(self, revs, revision):
955
971
        assert revision.startswith('revid:')
 
972
        rev_id = revision[len('revid:'):]
956
973
        try:
957
 
            return revs.index(revision[6:]) + 1
 
974
            return revs.index(rev_id) + 1, rev_id
958
975
        except ValueError:
959
 
            return None
 
976
            return None, rev_id
960
977
    REVISION_NAMESPACES['revid:'] = _namespace_revid
961
978
 
962
979
    def _namespace_last(self, revs, revision):
964
981
        try:
965
982
            offset = int(revision[5:])
966
983
        except ValueError:
967
 
            return None
 
984
            return (None,)
968
985
        else:
969
986
            if offset <= 0:
970
987
                raise BzrError('You must supply a positive value for --revision last:XXX')
971
 
            return len(revs) - offset + 1
 
988
            return (len(revs) - offset + 1,)
972
989
    REVISION_NAMESPACES['last:'] = _namespace_last
973
990
 
974
991
    def _namespace_tag(self, revs, revision):
1049
1066
                # TODO: Handle timezone.
1050
1067
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1051
1068
                if first >= dt and (last is None or dt >= last):
1052
 
                    return i+1
 
1069
                    return (i+1,)
1053
1070
        else:
1054
1071
            for i in range(len(revs)):
1055
1072
                r = self.get_revision(revs[i])
1056
1073
                # TODO: Handle timezone.
1057
1074
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1058
1075
                if first <= dt and (last is None or dt <= last):
1059
 
                    return i+1
 
1076
                    return (i+1,)
1060
1077
    REVISION_NAMESPACES['date:'] = _namespace_date
1061
1078
 
1062
1079
    def revision_tree(self, revision_id):
1127
1144
 
1128
1145
            inv.rename(file_id, to_dir_id, to_tail)
1129
1146
 
1130
 
            print "%s => %s" % (from_rel, to_rel)
1131
 
 
1132
1147
            from_abs = self.abspath(from_rel)
1133
1148
            to_abs = self.abspath(to_rel)
1134
1149
            try:
1153
1168
 
1154
1169
        Note that to_name is only the last component of the new name;
1155
1170
        this doesn't change the directory.
 
1171
 
 
1172
        This returns a list of (from_path, to_path) pairs for each
 
1173
        entry that is moved.
1156
1174
        """
 
1175
        result = []
1157
1176
        self.lock_write()
1158
1177
        try:
1159
1178
            ## TODO: Option to move IDs only
1194
1213
            for f in from_paths:
1195
1214
                name_tail = splitpath(f)[-1]
1196
1215
                dest_path = appendpath(to_name, name_tail)
1197
 
                print "%s => %s" % (f, dest_path)
 
1216
                result.append((f, dest_path))
1198
1217
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1199
1218
                try:
1200
1219
                    os.rename(self.abspath(f), self.abspath(dest_path))
1206
1225
        finally:
1207
1226
            self.unlock()
1208
1227
 
 
1228
        return result
 
1229
 
1209
1230
 
1210
1231
    def revert(self, filenames, old_tree=None, backups=True):
1211
1232
        """Restore selected files to the versions from a previous tree.
1293
1314
            self.unlock()
1294
1315
 
1295
1316
 
 
1317
    def get_parent(self):
 
1318
        """Return the parent location of the branch.
 
1319
 
 
1320
        This is the default location for push/pull/missing.  The usual
 
1321
        pattern is that the user can override it by specifying a
 
1322
        location.
 
1323
        """
 
1324
        import errno
 
1325
        _locs = ['parent', 'pull', 'x-pull']
 
1326
        for l in _locs:
 
1327
            try:
 
1328
                return self.controlfile(l, 'r').read().strip('\n')
 
1329
            except IOError, e:
 
1330
                if e.errno != errno.ENOENT:
 
1331
                    raise
 
1332
        return None
 
1333
 
 
1334
 
 
1335
    def set_parent(self, url):
 
1336
        # TODO: Maybe delete old location files?
 
1337
        from bzrlib.atomicfile import AtomicFile
 
1338
        self.lock_write()
 
1339
        try:
 
1340
            f = AtomicFile(self.controlfilename('parent'))
 
1341
            try:
 
1342
                f.write(url + '\n')
 
1343
                f.commit()
 
1344
            finally:
 
1345
                f.close()
 
1346
        finally:
 
1347
            self.unlock()
 
1348
 
 
1349
    def check_revno(self, revno):
 
1350
        """\
 
1351
        Check whether a revno corresponds to any revision.
 
1352
        Zero (the NULL revision) is considered valid.
 
1353
        """
 
1354
        if revno != 0:
 
1355
            self.check_real_revno(revno)
 
1356
            
 
1357
    def check_real_revno(self, revno):
 
1358
        """\
 
1359
        Check whether a revno corresponds to a real revision.
 
1360
        Zero (the NULL revision) is considered invalid
 
1361
        """
 
1362
        if revno < 1 or revno > self.revno():
 
1363
            raise InvalidRevisionNumber(revno)
 
1364
        
 
1365
        
 
1366
 
1296
1367
 
1297
1368
class ScratchBranch(Branch):
1298
1369
    """Special test class: a branch that cleans up after itself.
1340
1411
        os.rmdir(base)
1341
1412
        copytree(self.base, base, symlinks=True)
1342
1413
        return ScratchBranch(base=base)
 
1414
 
 
1415
 
1343
1416
        
1344
1417
    def __del__(self):
1345
1418
        self.destroy()
1415
1488
    """Return a new tree-root file id."""
1416
1489
    return gen_file_id('TREE_ROOT')
1417
1490
 
 
1491
 
 
1492
def pull_loc(branch):
 
1493
    # TODO: Should perhaps just make attribute be 'base' in
 
1494
    # RemoteBranch and Branch?
 
1495
    if hasattr(branch, "baseurl"):
 
1496
        return branch.baseurl
 
1497
    else:
 
1498
        return branch.base
 
1499
 
 
1500
 
 
1501
def copy_branch(branch_from, to_location, revision=None):
 
1502
    """Copy branch_from into the existing directory to_location.
 
1503
 
 
1504
    revision
 
1505
        If not None, only revisions up to this point will be copied.
 
1506
        The head of the new branch will be that revision.
 
1507
 
 
1508
    to_location
 
1509
        The name of a local directory that exists but is empty.
 
1510
    """
 
1511
    from bzrlib.merge import merge
 
1512
    from bzrlib.branch import Branch
 
1513
 
 
1514
    assert isinstance(branch_from, Branch)
 
1515
    assert isinstance(to_location, basestring)
 
1516
    
 
1517
    br_to = Branch(to_location, init=True)
 
1518
    br_to.set_root_id(branch_from.get_root_id())
 
1519
    if revision is None:
 
1520
        revno = branch_from.revno()
 
1521
    else:
 
1522
        revno, rev_id = branch_from.get_revision_info(revision)
 
1523
    br_to.update_revisions(branch_from, stop_revision=revno)
 
1524
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
1525
          check_clean=False, ignore_zero=True)
 
1526
    
 
1527
    from_location = pull_loc(branch_from)
 
1528
    br_to.set_parent(pull_loc(branch_from))