~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:
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. May be a file:// url.
 
176
        base -- Base directory for the branch.
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:]
197
195
            self.base = os.path.realpath(base)
198
196
            if not isdir(self.controlfilename('.')):
199
197
                from errors import NotBranchError
220
218
            warn("branch %r was not explicitly unlocked" % self)
221
219
            self._lock.unlock()
222
220
 
 
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
 
238
239
    def lock_read(self):
239
240
        if self._lock_mode:
240
241
            assert self._lock_mode in ('r', 'w'), \
247
248
            self._lock_mode = 'r'
248
249
            self._lock_count = 1
249
250
                        
 
251
 
 
252
            
250
253
    def unlock(self):
251
254
        if not self._lock_mode:
252
255
            from errors import LockError
259
262
            self._lock = None
260
263
            self._lock_mode = self._lock_count = None
261
264
 
 
265
 
262
266
    def abspath(self, name):
263
267
        """Return absolute filename for something in the branch"""
264
268
        return os.path.join(self.base, name)
265
269
 
 
270
 
266
271
    def relpath(self, path):
267
272
        """Return path relative to this branch of something inside it.
268
273
 
269
274
        Raises an error if path is not in this branch."""
270
275
        return _relpath(self.base, path)
271
276
 
 
277
 
272
278
    def controlfilename(self, file_or_path):
273
279
        """Return location relative to branch."""
274
280
        if isinstance(file_or_path, basestring):
301
307
        else:
302
308
            raise BzrError("invalid controlfile mode %r" % mode)
303
309
 
 
310
 
 
311
 
304
312
    def _make_control(self):
305
313
        from bzrlib.inventory import Inventory
306
314
        from bzrlib.xml import pack_xml
324
332
        # simplicity.
325
333
        pack_xml(Inventory(), self.controlfile('inventory','w'))
326
334
 
 
335
 
327
336
    def _check_format(self):
328
337
        """Check this branch format is supported.
329
338
 
405
414
                         """Inventory for the working copy.""")
406
415
 
407
416
 
408
 
    def add(self, files, ids=None):
 
417
    def add(self, files, verbose=False, ids=None):
409
418
        """Make files versioned.
410
419
 
411
 
        Note that the command line normally calls smart_add instead,
412
 
        which can automatically recurse.
 
420
        Note that the command line normally calls smart_add instead.
413
421
 
414
422
        This puts the files in the Added state, so that they will be
415
423
        recorded by the next commit.
425
433
        TODO: Perhaps have an option to add the ids even if the files do
426
434
              not (yet) exist.
427
435
 
428
 
        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.
429
442
        """
430
443
        # TODO: Re-adding a file that is removed in the working copy
431
444
        # should probably put it back with the previous ID.
467
480
                    file_id = gen_file_id(f)
468
481
                inv.add_path(f, kind=kind, file_id=file_id)
469
482
 
 
483
                if verbose:
 
484
                    print 'added', quotefn(f)
 
485
 
470
486
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
471
487
 
472
488
            self._write_inventory(inv)
581
597
        finally:
582
598
            f.close()
583
599
 
 
600
 
584
601
    def get_revision_xml(self, revision_id):
585
602
        """Return XML file object for revision object."""
586
603
        if not revision_id or not isinstance(revision_id, basestring):
595
612
        finally:
596
613
            self.unlock()
597
614
 
 
615
 
598
616
    def get_revision(self, revision_id):
599
617
        """Return the Revision object for a named revision"""
600
618
        xml_file = self.get_revision_xml(revision_id)
609
627
        assert r.revision_id == revision_id
610
628
        return r
611
629
 
 
630
 
612
631
    def get_revision_delta(self, revno):
613
632
        """Return the delta for one revision.
614
633
 
815
834
            count = 0
816
835
        self.append_revision(*revision_ids)
817
836
        ## note("Added %d revisions." % count)
818
 
        pb.clear()
819
837
 
 
838
        
820
839
    def install_revisions(self, other, revision_ids, pb):
821
840
        if hasattr(other.revision_store, "prefetch"):
822
841
            other.revision_store.prefetch(revision_ids)
853
872
                    
854
873
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
855
874
                                                    needed_texts)
856
 
        #print "Added %d texts." % count 
 
875
        print "Added %d texts." % count 
857
876
        inventory_ids = [ f.inventory_id for f in revisions ]
858
877
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
859
878
                                                         inventory_ids)
860
 
        #print "Added %d inventories." % count 
 
879
        print "Added %d inventories." % count 
861
880
        revision_ids = [ f.revision_id for f in revisions]
862
881
 
863
882
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
874
893
 
875
894
    def lookup_revision(self, revision):
876
895
        """Return the revision identifier for a given revision information."""
877
 
        revno, info = self._get_revision_info(revision)
 
896
        revno, info = self.get_revision_info(revision)
878
897
        return info
879
898
 
880
899
 
895
914
        revision can also be a string, in which case it is parsed for something like
896
915
            'date:' or 'revid:' etc.
897
916
        """
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
 
        
926
917
        if revision is None:
927
918
            return 0, None
928
919
        revno = None
932
923
            pass
933
924
        revs = self.revision_history()
934
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
935
929
            if revision < 0:
936
930
                revno = len(revs) + revision + 1
937
931
            else:
938
932
                revno = revision
939
 
            rev_id = self.get_rev_id(revno, revs)
940
933
        elif isinstance(revision, basestring):
941
934
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
942
935
                if revision.startswith(prefix):
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)
 
936
                    revno = func(self, revs, revision)
949
937
                    break
950
938
            else:
951
 
                raise BzrError('No namespace registered for string: %r' %
952
 
                               revision)
953
 
        else:
954
 
            raise TypeError('Unhandled revision type %s' % revision)
 
939
                raise BzrError('No namespace registered for string: %r' % revision)
955
940
 
956
 
        if revno is None:
957
 
            if rev_id is None:
958
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
959
 
        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]
960
944
 
961
945
    def _namespace_revno(self, revs, revision):
962
946
        """Lookup a revision by revision number"""
963
947
        assert revision.startswith('revno:')
964
948
        try:
965
 
            return (int(revision[6:]),)
 
949
            return int(revision[6:])
966
950
        except ValueError:
967
951
            return None
968
952
    REVISION_NAMESPACES['revno:'] = _namespace_revno
969
953
 
970
954
    def _namespace_revid(self, revs, revision):
971
955
        assert revision.startswith('revid:')
972
 
        rev_id = revision[len('revid:'):]
973
956
        try:
974
 
            return revs.index(rev_id) + 1, rev_id
 
957
            return revs.index(revision[6:]) + 1
975
958
        except ValueError:
976
 
            return None, rev_id
 
959
            return None
977
960
    REVISION_NAMESPACES['revid:'] = _namespace_revid
978
961
 
979
962
    def _namespace_last(self, revs, revision):
981
964
        try:
982
965
            offset = int(revision[5:])
983
966
        except ValueError:
984
 
            return (None,)
 
967
            return None
985
968
        else:
986
969
            if offset <= 0:
987
970
                raise BzrError('You must supply a positive value for --revision last:XXX')
988
 
            return (len(revs) - offset + 1,)
 
971
            return len(revs) - offset + 1
989
972
    REVISION_NAMESPACES['last:'] = _namespace_last
990
973
 
991
974
    def _namespace_tag(self, revs, revision):
1066
1049
                # TODO: Handle timezone.
1067
1050
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1068
1051
                if first >= dt and (last is None or dt >= last):
1069
 
                    return (i+1,)
 
1052
                    return i+1
1070
1053
        else:
1071
1054
            for i in range(len(revs)):
1072
1055
                r = self.get_revision(revs[i])
1073
1056
                # TODO: Handle timezone.
1074
1057
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1075
1058
                if first <= dt and (last is None or dt <= last):
1076
 
                    return (i+1,)
 
1059
                    return i+1
1077
1060
    REVISION_NAMESPACES['date:'] = _namespace_date
1078
1061
 
1079
1062
    def revision_tree(self, revision_id):
1144
1127
 
1145
1128
            inv.rename(file_id, to_dir_id, to_tail)
1146
1129
 
 
1130
            print "%s => %s" % (from_rel, to_rel)
 
1131
 
1147
1132
            from_abs = self.abspath(from_rel)
1148
1133
            to_abs = self.abspath(to_rel)
1149
1134
            try:
1168
1153
 
1169
1154
        Note that to_name is only the last component of the new name;
1170
1155
        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.
1174
1156
        """
1175
 
        result = []
1176
1157
        self.lock_write()
1177
1158
        try:
1178
1159
            ## TODO: Option to move IDs only
1213
1194
            for f in from_paths:
1214
1195
                name_tail = splitpath(f)[-1]
1215
1196
                dest_path = appendpath(to_name, name_tail)
1216
 
                result.append((f, dest_path))
 
1197
                print "%s => %s" % (f, dest_path)
1217
1198
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1218
1199
                try:
1219
1200
                    os.rename(self.abspath(f), self.abspath(dest_path))
1225
1206
        finally:
1226
1207
            self.unlock()
1227
1208
 
1228
 
        return result
1229
 
 
1230
1209
 
1231
1210
    def revert(self, filenames, old_tree=None, backups=True):
1232
1211
        """Restore selected files to the versions from a previous tree.
1314
1293
            self.unlock()
1315
1294
 
1316
1295
 
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
 
 
1367
1296
 
1368
1297
class ScratchBranch(Branch):
1369
1298
    """Special test class: a branch that cleans up after itself.
1411
1340
        os.rmdir(base)
1412
1341
        copytree(self.base, base, symlinks=True)
1413
1342
        return ScratchBranch(base=base)
1414
 
 
1415
 
 
1416
1343
        
1417
1344
    def __del__(self):
1418
1345
        self.destroy()
1488
1415
    """Return a new tree-root file id."""
1489
1416
    return gen_file_id('TREE_ROOT')
1490
1417
 
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))