~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-08-25 05:58:05 UTC
  • mfrom: (974.1.36)
  • Revision ID: mbp@sourcefrog.net-20050825055805-8c892bc3c2d75131
- merge aaron's merge improvements:

  * When merging, pull in all missing revisions from the source
    branch. 

  * Detect common ancestors by looking at the whole ancestry graph, 
    rather than just mainline history.

  Some changes to reconcile this with parallel updates to the test and
  trace code.

aaron.bentley@utoronto.ca-20050823052551-f3401a8b57d9126f

Show diffs side-by-side

added added

removed removed

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