~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Optimize Tree._iter_changes with specific file_ids

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
from cStringIO import StringIO
 
19
 
 
20
from bzrlib.lazy_import import lazy_import
 
21
lazy_import(globals(), """
18
22
from copy import deepcopy
19
 
from cStringIO import StringIO
20
23
from unittest import TestSuite
21
24
from warnings import warn
22
25
 
23
26
import bzrlib
24
 
from bzrlib import bzrdir, errors, lockdir, osutils, revision, \
25
 
        tree, \
26
 
        ui, \
27
 
        urlutils
28
 
from bzrlib.config import TreeConfig
 
27
from bzrlib import (
 
28
        bzrdir,
 
29
        cache_utf8,
 
30
        config as _mod_config,
 
31
        errors,
 
32
        lockdir,
 
33
        lockable_files,
 
34
        osutils,
 
35
        revision as _mod_revision,
 
36
        transport,
 
37
        tree,
 
38
        ui,
 
39
        urlutils,
 
40
        )
 
41
from bzrlib.config import BranchConfig, TreeConfig
 
42
from bzrlib.lockable_files import LockableFiles, TransportLock
 
43
""")
 
44
 
29
45
from bzrlib.decorators import needs_read_lock, needs_write_lock
30
 
import bzrlib.errors as errors
31
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches, 
32
 
                           HistoryMissing, InvalidRevisionId, 
33
 
                           InvalidRevisionNumber, LockError, NoSuchFile, 
 
46
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
 
47
                           HistoryMissing, InvalidRevisionId,
 
48
                           InvalidRevisionNumber, LockError, NoSuchFile,
34
49
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
35
 
                           NotBranchError, UninitializableFormat, 
36
 
                           UnlistableStore, UnlistableBranch, 
 
50
                           NotBranchError, UninitializableFormat,
 
51
                           UnlistableStore, UnlistableBranch,
37
52
                           )
38
 
from bzrlib.lockable_files import LockableFiles, TransportLock
39
53
from bzrlib.symbol_versioning import (deprecated_function,
40
54
                                      deprecated_method,
41
55
                                      DEPRECATED_PARAMETER,
130
144
        """
131
145
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
132
146
 
 
147
    @deprecated_function(zero_eight)
133
148
    def setup_caching(self, cache_root):
134
149
        """Subclasses that care about caching should override this, and set
135
150
        up cached stores located under cache_root.
 
151
        
 
152
        NOTE: This is unused.
136
153
        """
137
 
        # seems to be unused, 2006-01-13 mbp
138
 
        warn('%s is deprecated' % self.setup_caching)
139
 
        self.cache_root = cache_root
 
154
        pass
140
155
 
141
156
    def get_config(self):
142
 
        return bzrlib.config.BranchConfig(self)
 
157
        return BranchConfig(self)
143
158
 
144
159
    def _get_nick(self):
145
160
        return self.get_config().get_nickname()
150
165
    nick = property(_get_nick, _set_nick)
151
166
 
152
167
    def is_locked(self):
153
 
        raise NotImplementedError('is_locked is abstract')
 
168
        raise NotImplementedError(self.is_locked)
154
169
 
155
170
    def lock_write(self):
156
 
        raise NotImplementedError('lock_write is abstract')
 
171
        raise NotImplementedError(self.lock_write)
157
172
 
158
173
    def lock_read(self):
159
 
        raise NotImplementedError('lock_read is abstract')
 
174
        raise NotImplementedError(self.lock_read)
160
175
 
161
176
    def unlock(self):
162
 
        raise NotImplementedError('unlock is abstract')
 
177
        raise NotImplementedError(self.unlock)
163
178
 
164
179
    def peek_lock_mode(self):
165
180
        """Return lock mode for the Branch: 'r', 'w' or None"""
166
181
        raise NotImplementedError(self.peek_lock_mode)
167
182
 
168
183
    def get_physical_lock_status(self):
169
 
        raise NotImplementedError('get_physical_lock_status is abstract')
 
184
        raise NotImplementedError(self.get_physical_lock_status)
170
185
 
171
186
    def abspath(self, name):
172
187
        """Return absolute filename for something in the branch
174
189
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
175
190
        method and not a tree method.
176
191
        """
177
 
        raise NotImplementedError('abspath is abstract')
 
192
        raise NotImplementedError(self.abspath)
178
193
 
179
194
    def bind(self, other):
180
195
        """Bind the local branch the other branch.
213
228
                    last_revision = from_history[-1]
214
229
                else:
215
230
                    # no history in the source branch
216
 
                    last_revision = revision.NULL_REVISION
 
231
                    last_revision = _mod_revision.NULL_REVISION
217
232
            return self.repository.fetch(from_branch.repository,
218
233
                                         revision_id=last_revision,
219
234
                                         pb=nested_pb)
271
286
 
272
287
    def get_root_id(self):
273
288
        """Return the id of this branches root"""
274
 
        raise NotImplementedError('get_root_id is abstract')
 
289
        raise NotImplementedError(self.get_root_id)
275
290
 
276
291
    def print_file(self, file, revision_id):
277
292
        """Print `file` to stdout."""
278
 
        raise NotImplementedError('print_file is abstract')
 
293
        raise NotImplementedError(self.print_file)
279
294
 
280
295
    def append_revision(self, *revision_ids):
281
 
        raise NotImplementedError('append_revision is abstract')
 
296
        raise NotImplementedError(self.append_revision)
282
297
 
283
298
    def set_revision_history(self, rev_history):
284
 
        raise NotImplementedError('set_revision_history is abstract')
 
299
        raise NotImplementedError(self.set_revision_history)
285
300
 
286
301
    def revision_history(self):
287
302
        """Return sequence of revision hashes on to this branch."""
288
 
        raise NotImplementedError('revision_history is abstract')
 
303
        raise NotImplementedError(self.revision_history)
289
304
 
290
305
    def revno(self):
291
306
        """Return current revision number for this branch.
300
315
        raise errors.UpgradeRequired(self.base)
301
316
 
302
317
    def last_revision(self):
303
 
        """Return last patch hash, or None if no history."""
 
318
        """Return last revision id, or None"""
304
319
        ph = self.revision_history()
305
320
        if ph:
306
321
            return ph[-1]
337
352
        :param stop_revision: Updated until the given revision
338
353
        :return: None
339
354
        """
340
 
        raise NotImplementedError('update_revisions is abstract')
 
355
        raise NotImplementedError(self.update_revisions)
341
356
 
342
357
    def revision_id_to_revno(self, revision_id):
343
358
        """Given a revision id, return its revno"""
355
370
            return None
356
371
        if history is None:
357
372
            history = self.revision_history()
358
 
        elif revno <= 0 or revno > len(history):
 
373
        if revno <= 0 or revno > len(history):
359
374
            raise bzrlib.errors.NoSuchRevision(self, revno)
360
375
        return history[revno - 1]
361
376
 
362
377
    def pull(self, source, overwrite=False, stop_revision=None):
363
 
        raise NotImplementedError('pull is abstract')
 
378
        raise NotImplementedError(self.pull)
364
379
 
365
380
    def basis_tree(self):
366
 
        """Return `Tree` object for last revision.
367
 
 
368
 
        If there are no revisions yet, return an `EmptyTree`.
369
 
        """
 
381
        """Return `Tree` object for last revision."""
370
382
        return self.repository.revision_tree(self.last_revision())
371
383
 
372
384
    def rename_one(self, from_rel, to_rel):
374
386
 
375
387
        This can change the directory or the filename or both.
376
388
        """
377
 
        raise NotImplementedError('rename_one is abstract')
 
389
        raise NotImplementedError(self.rename_one)
378
390
 
379
391
    def move(self, from_paths, to_name):
380
392
        """Rename files.
390
402
        This returns a list of (from_path, to_path) pairs for each
391
403
        entry that is moved.
392
404
        """
393
 
        raise NotImplementedError('move is abstract')
 
405
        raise NotImplementedError(self.move)
394
406
 
395
407
    def get_parent(self):
396
408
        """Return the parent location of the branch.
399
411
        pattern is that the user can override it by specifying a
400
412
        location.
401
413
        """
402
 
        raise NotImplementedError('get_parent is abstract')
 
414
        raise NotImplementedError(self.get_parent)
403
415
 
404
416
    def get_submit_branch(self):
405
417
        """Return the submit location of the branch.
421
433
 
422
434
    def get_push_location(self):
423
435
        """Return the None or the location to push this branch to."""
424
 
        raise NotImplementedError('get_push_location is abstract')
 
436
        raise NotImplementedError(self.get_push_location)
425
437
 
426
438
    def set_push_location(self, location):
427
439
        """Set a new push location for this branch."""
428
 
        raise NotImplementedError('set_push_location is abstract')
 
440
        raise NotImplementedError(self.set_push_location)
429
441
 
430
442
    def set_parent(self, url):
431
 
        raise NotImplementedError('set_parent is abstract')
 
443
        raise NotImplementedError(self.set_parent)
432
444
 
433
445
    @needs_write_lock
434
446
    def update(self):
537
549
                rev = self.repository.get_revision(revision_id)
538
550
                new_history = rev.get_history(self.repository)[1:]
539
551
        destination.set_revision_history(new_history)
540
 
        parent = self.get_parent()
541
 
        if parent:
542
 
            destination.set_parent(parent)
 
552
        try:
 
553
            parent = self.get_parent()
 
554
        except errors.InaccessibleParent, e:
 
555
            mutter('parent was not accessible to copy: %s', e)
 
556
        else:
 
557
            if parent:
 
558
                destination.set_parent(parent)
543
559
 
544
560
    @needs_read_lock
545
561
    def check(self):
571
587
            mainline_parent_id = revision_id
572
588
        return BranchCheckResult(self)
573
589
 
 
590
    def _get_checkout_format(self):
 
591
        """Return the most suitable metadir for a checkout of this branch.
 
592
        Weaves are used if this branch's repostory uses weaves.
 
593
        """
 
594
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
 
595
            from bzrlib import repository
 
596
            format = bzrdir.BzrDirMetaFormat1()
 
597
            format.repository_format = repository.RepositoryFormat7()
 
598
        else:
 
599
            format = self.repository.bzrdir.cloning_metadir()
 
600
        return format
 
601
 
 
602
    def create_checkout(self, to_location, revision_id=None, 
 
603
                        lightweight=False):
 
604
        """Create a checkout of a branch.
 
605
        
 
606
        :param to_location: The url to produce the checkout at
 
607
        :param revision_id: The revision to check out
 
608
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
609
        produce a bound branch (heavyweight checkout)
 
610
        :return: The tree of the created checkout
 
611
        """
 
612
        t = transport.get_transport(to_location)
 
613
        try:
 
614
            t.mkdir('.')
 
615
        except errors.FileExists:
 
616
            pass
 
617
        if lightweight:
 
618
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
619
            BranchReferenceFormat().initialize(checkout, self)
 
620
        else:
 
621
            format = self._get_checkout_format()
 
622
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
623
                to_location, force_new_tree=False, format=format)
 
624
            checkout = checkout_branch.bzrdir
 
625
            checkout_branch.bind(self)
 
626
            # pull up to the specified revision_id to set the initial 
 
627
            # branch tip correctly, and seed it with history.
 
628
            checkout_branch.pull(self, stop_revision=revision_id)
 
629
        return checkout.create_workingtree(revision_id)
 
630
 
574
631
 
575
632
class BranchFormat(object):
576
633
    """An encapsulation of the initialization and open routines for a format.
678
735
        utf8_files = [('revision-history', ''),
679
736
                      ('branch-name', ''),
680
737
                      ]
681
 
        control_files = LockableFiles(branch_transport, 'branch-lock',
682
 
                                      TransportLock)
 
738
        control_files = lockable_files.LockableFiles(branch_transport,
 
739
                             'branch-lock', lockable_files.TransportLock)
683
740
        control_files.create_lock()
684
741
        control_files.lock_write()
685
742
        try:
739
796
        utf8_files = [('revision-history', ''),
740
797
                      ('branch-name', ''),
741
798
                      ]
742
 
        control_files = LockableFiles(branch_transport, 'lock', lockdir.LockDir)
 
799
        control_files = lockable_files.LockableFiles(branch_transport, 'lock',
 
800
                                                     lockdir.LockDir)
743
801
        control_files.create_lock()
744
802
        control_files.lock_write()
745
803
        control_files.put_utf8('format', self.get_format_string())
764
822
            format = BranchFormat.find_format(a_bzrdir)
765
823
            assert format.__class__ == self.__class__
766
824
        transport = a_bzrdir.get_branch_transport(None)
767
 
        control_files = LockableFiles(transport, 'lock', lockdir.LockDir)
 
825
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
826
                                                     lockdir.LockDir)
768
827
        return BzrBranch5(_format=self,
769
828
                          _control_files=control_files,
770
829
                          a_bzrdir=a_bzrdir,
801
860
            raise errors.UninitializableFormat(self)
802
861
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
803
862
        branch_transport = a_bzrdir.get_branch_transport(self)
804
 
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
805
 
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
806
 
        branch_transport.put('format', StringIO(self.get_format_string()))
 
863
        branch_transport.put_bytes('location',
 
864
            target_branch.bzrdir.root_transport.base)
 
865
        branch_transport.put_bytes('format', self.get_format_string())
807
866
        return self.open(a_bzrdir, _found=True)
808
867
 
809
868
    def __init__(self):
920
979
 
921
980
    __repr__ = __str__
922
981
 
923
 
    def __del__(self):
924
 
        # TODO: It might be best to do this somewhere else,
925
 
        # but it is nice for a Branch object to automatically
926
 
        # cache it's information.
927
 
        # Alternatively, we could have the Transport objects cache requests
928
 
        # See the earlier discussion about how major objects (like Branch)
929
 
        # should never expect their __del__ function to run.
930
 
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
931
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
932
 
            try:
933
 
                osutils.rmtree(self.cache_root)
934
 
            except:
935
 
                pass
936
 
            self.cache_root = None
937
 
 
938
982
    def _get_base(self):
939
983
        return self._base
940
984
 
1063
1107
        transaction = self.get_transaction()
1064
1108
        history = transaction.map.find_revision_history()
1065
1109
        if history is not None:
1066
 
            mutter("cache hit for revision-history in %s", self)
 
1110
            # mutter("cache hit for revision-history in %s", self)
1067
1111
            return list(history)
1068
 
        history = [l.rstrip('\r\n') for l in
1069
 
                self.control_files.get_utf8('revision-history').readlines()]
 
1112
        decode_utf8 = cache_utf8.decode
 
1113
        history = [decode_utf8(l.rstrip('\r\n')) for l in
 
1114
                self.control_files.get('revision-history').readlines()]
1070
1115
        transaction.map.add_revision_history(history)
1071
1116
        # this call is disabled because revision_history is 
1072
1117
        # not really an object yet, and the transaction is for objects.
1092
1137
        # make a new revision history from the graph
1093
1138
        current_rev_id = revision_id
1094
1139
        new_history = []
1095
 
        while current_rev_id not in (None, revision.NULL_REVISION):
 
1140
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1096
1141
            new_history.append(current_rev_id)
1097
1142
            current_rev_id_parents = stop_graph[current_rev_id]
1098
1143
            try:
1148
1193
        try:
1149
1194
            old_count = len(self.revision_history())
1150
1195
            try:
1151
 
                self.update_revisions(source,stop_revision)
 
1196
                self.update_revisions(source, stop_revision)
1152
1197
            except DivergedBranches:
1153
1198
                if not overwrite:
1154
1199
                    raise
1173
1218
            # turn it into a url
1174
1219
            if parent.startswith('/'):
1175
1220
                parent = urlutils.local_path_to_url(parent.decode('utf8'))
1176
 
            return urlutils.join(self.base[:-1], parent)
 
1221
            try:
 
1222
                return urlutils.join(self.base[:-1], parent)
 
1223
            except errors.InvalidURLJoin, e:
 
1224
                raise errors.InaccessibleParent(parent, self.base)
1177
1225
        return None
1178
1226
 
1179
1227
    def get_push_location(self):
1183
1231
 
1184
1232
    def set_push_location(self, location):
1185
1233
        """See Branch.set_push_location."""
1186
 
        self.get_config().set_user_option('push_location', location, 
1187
 
                                          local=True)
 
1234
        self.get_config().set_user_option(
 
1235
            'push_location', location,
 
1236
            store=_mod_config.STORE_LOCATION_NORECURSE)
1188
1237
 
1189
1238
    @needs_write_lock
1190
1239
    def set_parent(self, url):
1206
1255
                        "use bzrlib.urlutils.escape")
1207
1256
                    
1208
1257
            url = urlutils.relative_url(self.base, url)
1209
 
            self.control_files.put('parent', url + '\n')
 
1258
            self.control_files.put('parent', StringIO(url + '\n'))
1210
1259
 
1211
1260
    @deprecated_function(zero_nine)
1212
1261
    def tree_config(self):
1286
1335
 
1287
1336
    @needs_write_lock
1288
1337
    def bind(self, other):
1289
 
        """Bind the local branch the other branch.
 
1338
        """Bind this branch to the branch other.
1290
1339
 
 
1340
        This does not push or pull data between the branches, though it does
 
1341
        check for divergence to raise an error when the branches are not
 
1342
        either the same, or one a prefix of the other. That behaviour may not
 
1343
        be useful, so that check may be removed in future.
 
1344
        
1291
1345
        :param other: The branch to bind to
1292
1346
        :type other: Branch
1293
1347
        """
1298
1352
        #       but binding itself may not be.
1299
1353
        #       Since we *have* to check at commit time, we don't
1300
1354
        #       *need* to check here
1301
 
        self.pull(other)
1302
 
 
1303
 
        # we are now equal to or a suffix of other.
1304
 
 
1305
 
        # Since we have 'pulled' from the remote location,
1306
 
        # now we should try to pull in the opposite direction
1307
 
        # in case the local tree has more revisions than the
1308
 
        # remote one.
1309
 
        # There may be a different check you could do here
1310
 
        # rather than actually trying to install revisions remotely.
1311
 
        # TODO: capture an exception which indicates the remote branch
1312
 
        #       is not writable. 
1313
 
        #       If it is up-to-date, this probably should not be a failure
1314
 
        
1315
 
        # lock other for write so the revision-history syncing cannot race
1316
 
        other.lock_write()
1317
 
        try:
1318
 
            other.pull(self)
1319
 
            # if this does not error, other now has the same last rev we do
1320
 
            # it can only error if the pull from other was concurrent with
1321
 
            # a commit to other from someone else.
1322
 
 
1323
 
            # until we ditch revision-history, we need to sync them up:
1324
 
            self.set_revision_history(other.revision_history())
1325
 
            # now other and self are up to date with each other and have the
1326
 
            # same revision-history.
1327
 
        finally:
1328
 
            other.unlock()
1329
 
 
 
1355
 
 
1356
        # we want to raise diverged if:
 
1357
        # last_rev is not in the other_last_rev history, AND
 
1358
        # other_last_rev is not in our history, and do it without pulling
 
1359
        # history around
 
1360
        last_rev = self.last_revision()
 
1361
        if last_rev is not None:
 
1362
            other.lock_read()
 
1363
            try:
 
1364
                other_last_rev = other.last_revision()
 
1365
                if other_last_rev is not None:
 
1366
                    # neither branch is new, we have to do some work to
 
1367
                    # ascertain diversion.
 
1368
                    remote_graph = other.repository.get_revision_graph(
 
1369
                        other_last_rev)
 
1370
                    local_graph = self.repository.get_revision_graph(last_rev)
 
1371
                    if (last_rev not in remote_graph and
 
1372
                        other_last_rev not in local_graph):
 
1373
                        raise errors.DivergedBranches(self, other)
 
1374
            finally:
 
1375
                other.unlock()
1330
1376
        self.set_bound_location(other.base)
1331
1377
 
1332
1378
    @needs_write_lock
1408
1454
@deprecated_function(zero_eight)
1409
1455
def is_control_file(*args, **kwargs):
1410
1456
    """See bzrlib.workingtree.is_control_file."""
1411
 
    return bzrlib.workingtree.is_control_file(*args, **kwargs)
 
1457
    from bzrlib import workingtree
 
1458
    return workingtree.is_control_file(*args, **kwargs)