~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

Change the return values for bisect functions so they just return
the found dictionaries. This saves processing, and is more useful for a future
_bisect_recursive function.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
 
import errno
21
 
import os
22
 
import shutil
23
 
import sys
24
23
from unittest import TestSuite
25
24
from warnings import warn
26
25
 
27
26
import bzrlib
28
 
import bzrlib.bzrdir as bzrdir
29
 
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
 
30
45
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
 
from bzrlib.delta import compare_trees
32
 
import bzrlib.errors as errors
33
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
 
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
 
                           DivergedBranches, LockError,
36
 
                           UninitializableFormat,
37
 
                           UnlistableStore,
38
 
                           UnlistableBranch, NoSuchFile, NotVersionedError,
39
 
                           NoWorkingTree)
40
 
import bzrlib.inventory as inventory
41
 
from bzrlib.inventory import Inventory
42
 
from bzrlib.lockable_files import LockableFiles, TransportLock
43
 
from bzrlib.lockdir import LockDir
44
 
from bzrlib.osutils import (isdir, quotefn,
45
 
                            rename, splitpath, sha_file,
46
 
                            file_kind, abspath, normpath, pathjoin,
47
 
                            safe_unicode,
48
 
                            rmtree,
49
 
                            )
50
 
from bzrlib.textui import show_status
 
46
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
 
47
                           HistoryMissing, InvalidRevisionId,
 
48
                           InvalidRevisionNumber, LockError, NoSuchFile,
 
49
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
 
50
                           NotBranchError, UninitializableFormat,
 
51
                           UnlistableStore, UnlistableBranch,
 
52
                           )
 
53
from bzrlib.symbol_versioning import (deprecated_function,
 
54
                                      deprecated_method,
 
55
                                      DEPRECATED_PARAMETER,
 
56
                                      deprecated_passed,
 
57
                                      zero_eight, zero_nine,
 
58
                                      )
51
59
from bzrlib.trace import mutter, note
52
 
from bzrlib.tree import EmptyTree, RevisionTree
53
 
from bzrlib.repository import Repository
54
 
from bzrlib.revision import (
55
 
                             is_ancestor,
56
 
                             NULL_REVISION,
57
 
                             Revision,
58
 
                             )
59
 
from bzrlib.store import copy_all
60
 
from bzrlib.symbol_versioning import *
61
 
import bzrlib.transactions as transactions
62
 
from bzrlib.transport import Transport, get_transport
63
 
from bzrlib.tree import EmptyTree, RevisionTree
64
 
import bzrlib.ui
65
 
import bzrlib.xml5
66
60
 
67
61
 
68
62
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
87
81
 
88
82
    base
89
83
        Base directory/url of the branch.
 
84
 
 
85
    hooks: An instance of BranchHooks.
90
86
    """
91
87
    # this is really an instance variable - FIXME move it there
92
88
    # - RBC 20060112
117
113
        
118
114
    @staticmethod
119
115
    def open(base, _unsupported=False):
120
 
        """Open the repository rooted at base.
 
116
        """Open the branch rooted at base.
121
117
 
122
 
        For instance, if the repository is at URL/.bzr/repository,
123
 
        Repository.open(URL) -> a Repository instance.
 
118
        For instance, if the branch is at URL/.bzr/branch,
 
119
        Branch.open(URL) -> a Branch instance.
124
120
        """
125
121
        control = bzrdir.BzrDir.open(base, _unsupported)
126
122
        return control.open_branch(_unsupported)
150
146
        """
151
147
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
152
148
 
 
149
    @deprecated_function(zero_eight)
153
150
    def setup_caching(self, cache_root):
154
151
        """Subclasses that care about caching should override this, and set
155
152
        up cached stores located under cache_root.
 
153
        
 
154
        NOTE: This is unused.
156
155
        """
157
 
        # seems to be unused, 2006-01-13 mbp
158
 
        warn('%s is deprecated' % self.setup_caching)
159
 
        self.cache_root = cache_root
 
156
        pass
 
157
 
 
158
    def get_config(self):
 
159
        return BranchConfig(self)
160
160
 
161
161
    def _get_nick(self):
162
 
        cfg = self.tree_config()
163
 
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
 
162
        return self.get_config().get_nickname()
164
163
 
165
164
    def _set_nick(self, nick):
166
 
        cfg = self.tree_config()
167
 
        cfg.set_option(nick, "nickname")
168
 
        assert cfg.get_option("nickname") == nick
 
165
        self.get_config().set_user_option('nickname', nick)
169
166
 
170
167
    nick = property(_get_nick, _set_nick)
171
168
 
172
169
    def is_locked(self):
173
 
        raise NotImplementedError('is_locked is abstract')
 
170
        raise NotImplementedError(self.is_locked)
174
171
 
175
172
    def lock_write(self):
176
 
        raise NotImplementedError('lock_write is abstract')
 
173
        raise NotImplementedError(self.lock_write)
177
174
 
178
175
    def lock_read(self):
179
 
        raise NotImplementedError('lock_read is abstract')
 
176
        raise NotImplementedError(self.lock_read)
180
177
 
181
178
    def unlock(self):
182
 
        raise NotImplementedError('unlock is abstract')
 
179
        raise NotImplementedError(self.unlock)
183
180
 
184
181
    def peek_lock_mode(self):
185
182
        """Return lock mode for the Branch: 'r', 'w' or None"""
186
183
        raise NotImplementedError(self.peek_lock_mode)
187
184
 
188
185
    def get_physical_lock_status(self):
189
 
        raise NotImplementedError('get_physical_lock_status is abstract')
 
186
        raise NotImplementedError(self.get_physical_lock_status)
190
187
 
191
188
    def abspath(self, name):
192
189
        """Return absolute filename for something in the branch
194
191
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
195
192
        method and not a tree method.
196
193
        """
197
 
        raise NotImplementedError('abspath is abstract')
 
194
        raise NotImplementedError(self.abspath)
198
195
 
199
196
    def bind(self, other):
200
197
        """Bind the local branch the other branch.
219
216
        if self.base == from_branch.base:
220
217
            return (0, [])
221
218
        if pb is None:
222
 
            nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
 
219
            nested_pb = ui.ui_factory.nested_progress_bar()
223
220
            pb = nested_pb
224
221
        else:
225
222
            nested_pb = None
228
225
        try:
229
226
            if last_revision is None:
230
227
                pb.update('get source history')
231
 
                from_history = from_branch.revision_history()
232
 
                if from_history:
233
 
                    last_revision = from_history[-1]
234
 
                else:
235
 
                    # no history in the source branch
236
 
                    last_revision = NULL_REVISION
 
228
                last_revision = from_branch.last_revision()
 
229
                if last_revision is None:
 
230
                    last_revision = _mod_revision.NULL_REVISION
237
231
            return self.repository.fetch(from_branch.repository,
238
232
                                         revision_id=last_revision,
239
233
                                         pb=nested_pb)
249
243
        branch.
250
244
        """
251
245
        return None
 
246
    
 
247
    def get_old_bound_location(self):
 
248
        """Return the URL of the branch we used to be bound to
 
249
        """
 
250
        raise errors.UpgradeRequired(self.base)
 
251
 
 
252
    def get_commit_builder(self, parents, config=None, timestamp=None, 
 
253
                           timezone=None, committer=None, revprops=None, 
 
254
                           revision_id=None):
 
255
        """Obtain a CommitBuilder for this branch.
 
256
        
 
257
        :param parents: Revision ids of the parents of the new revision.
 
258
        :param config: Optional configuration to use.
 
259
        :param timestamp: Optional timestamp recorded for commit.
 
260
        :param timezone: Optional timezone for timestamp.
 
261
        :param committer: Optional committer to set for commit.
 
262
        :param revprops: Optional dictionary of revision properties.
 
263
        :param revision_id: Optional revision id.
 
264
        """
 
265
 
 
266
        if config is None:
 
267
            config = self.get_config()
 
268
        
 
269
        return self.repository.get_commit_builder(self, parents, config,
 
270
            timestamp, timezone, committer, revprops, revision_id)
252
271
 
253
272
    def get_master_branch(self):
254
273
        """Return the branch we are bound to.
257
276
        """
258
277
        return None
259
278
 
 
279
    def get_revision_delta(self, revno):
 
280
        """Return the delta for one revision.
 
281
 
 
282
        The delta is relative to its mainline predecessor, or the
 
283
        empty tree for revision 1.
 
284
        """
 
285
        assert isinstance(revno, int)
 
286
        rh = self.revision_history()
 
287
        if not (1 <= revno <= len(rh)):
 
288
            raise InvalidRevisionNumber(revno)
 
289
        return self.repository.get_revision_delta(rh[revno-1])
 
290
 
260
291
    def get_root_id(self):
261
292
        """Return the id of this branches root"""
262
 
        raise NotImplementedError('get_root_id is abstract')
 
293
        raise NotImplementedError(self.get_root_id)
263
294
 
264
295
    def print_file(self, file, revision_id):
265
296
        """Print `file` to stdout."""
266
 
        raise NotImplementedError('print_file is abstract')
 
297
        raise NotImplementedError(self.print_file)
267
298
 
268
299
    def append_revision(self, *revision_ids):
269
 
        raise NotImplementedError('append_revision is abstract')
 
300
        raise NotImplementedError(self.append_revision)
270
301
 
271
302
    def set_revision_history(self, rev_history):
272
 
        raise NotImplementedError('set_revision_history is abstract')
 
303
        raise NotImplementedError(self.set_revision_history)
273
304
 
274
305
    def revision_history(self):
275
306
        """Return sequence of revision hashes on to this branch."""
276
 
        raise NotImplementedError('revision_history is abstract')
 
307
        raise NotImplementedError(self.revision_history)
277
308
 
278
309
    def revno(self):
279
310
        """Return current revision number for this branch.
287
318
        """Older format branches cannot bind or unbind."""
288
319
        raise errors.UpgradeRequired(self.base)
289
320
 
 
321
    def set_append_revisions_only(self, enabled):
 
322
        """Older format branches are never restricted to append-only"""
 
323
        raise errors.UpgradeRequired(self.base)
 
324
 
290
325
    def last_revision(self):
291
 
        """Return last patch hash, or None if no history."""
 
326
        """Return last revision id, or None"""
292
327
        ph = self.revision_history()
293
328
        if ph:
294
329
            return ph[-1]
295
330
        else:
296
331
            return None
297
332
 
 
333
    def last_revision_info(self):
 
334
        """Return information about the last revision.
 
335
 
 
336
        :return: A tuple (revno, last_revision_id).
 
337
        """
 
338
        rh = self.revision_history()
 
339
        revno = len(rh)
 
340
        if revno:
 
341
            return (revno, rh[-1])
 
342
        else:
 
343
            return (0, _mod_revision.NULL_REVISION)
 
344
 
298
345
    def missing_revisions(self, other, stop_revision=None):
299
346
        """Return a list of new revisions that would perfectly fit.
300
347
        
301
348
        If self and other have not diverged, return a list of the revisions
302
349
        present in other, but missing from self.
303
 
 
304
 
        >>> from bzrlib.workingtree import WorkingTree
305
 
        >>> bzrlib.trace.silent = True
306
 
        >>> d1 = bzrdir.ScratchDir()
307
 
        >>> br1 = d1.open_branch()
308
 
        >>> wt1 = d1.open_workingtree()
309
 
        >>> d2 = bzrdir.ScratchDir()
310
 
        >>> br2 = d2.open_branch()
311
 
        >>> wt2 = d2.open_workingtree()
312
 
        >>> br1.missing_revisions(br2)
313
 
        []
314
 
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
315
 
        >>> br1.missing_revisions(br2)
316
 
        [u'REVISION-ID-1']
317
 
        >>> br2.missing_revisions(br1)
318
 
        []
319
 
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
320
 
        >>> br1.missing_revisions(br2)
321
 
        []
322
 
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
323
 
        >>> br1.missing_revisions(br2)
324
 
        [u'REVISION-ID-2A']
325
 
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
326
 
        >>> br1.missing_revisions(br2)
327
 
        Traceback (most recent call last):
328
 
        DivergedBranches: These branches have diverged.  Try merge.
329
350
        """
330
351
        self_history = self.revision_history()
331
352
        self_len = len(self_history)
341
362
        else:
342
363
            assert isinstance(stop_revision, int)
343
364
            if stop_revision > other_len:
344
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
365
                raise errors.NoSuchRevision(self, stop_revision)
345
366
        return other_history[self_len:stop_revision]
346
367
 
347
368
    def update_revisions(self, other, stop_revision=None):
351
372
        :param stop_revision: Updated until the given revision
352
373
        :return: None
353
374
        """
354
 
        raise NotImplementedError('update_revisions is abstract')
 
375
        raise NotImplementedError(self.update_revisions)
355
376
 
356
377
    def revision_id_to_revno(self, revision_id):
357
378
        """Given a revision id, return its revno"""
358
379
        if revision_id is None:
359
380
            return 0
 
381
        revision_id = osutils.safe_revision_id(revision_id)
360
382
        history = self.revision_history()
361
383
        try:
362
384
            return history.index(revision_id) + 1
369
391
            return None
370
392
        if history is None:
371
393
            history = self.revision_history()
372
 
        elif revno <= 0 or revno > len(history):
 
394
        if revno <= 0 or revno > len(history):
373
395
            raise bzrlib.errors.NoSuchRevision(self, revno)
374
396
        return history[revno - 1]
375
397
 
376
398
    def pull(self, source, overwrite=False, stop_revision=None):
377
 
        raise NotImplementedError('pull is abstract')
 
399
        """Mirror source into this branch.
 
400
 
 
401
        This branch is considered to be 'local', having low latency.
 
402
        """
 
403
        raise NotImplementedError(self.pull)
 
404
 
 
405
    def push(self, target, overwrite=False, stop_revision=None):
 
406
        """Mirror this branch into target.
 
407
 
 
408
        This branch is considered to be 'local', having low latency.
 
409
        """
 
410
        raise NotImplementedError(self.push)
378
411
 
379
412
    def basis_tree(self):
380
 
        """Return `Tree` object for last revision.
381
 
 
382
 
        If there are no revisions yet, return an `EmptyTree`.
383
 
        """
 
413
        """Return `Tree` object for last revision."""
384
414
        return self.repository.revision_tree(self.last_revision())
385
415
 
386
416
    def rename_one(self, from_rel, to_rel):
388
418
 
389
419
        This can change the directory or the filename or both.
390
420
        """
391
 
        raise NotImplementedError('rename_one is abstract')
 
421
        raise NotImplementedError(self.rename_one)
392
422
 
393
423
    def move(self, from_paths, to_name):
394
424
        """Rename files.
404
434
        This returns a list of (from_path, to_path) pairs for each
405
435
        entry that is moved.
406
436
        """
407
 
        raise NotImplementedError('move is abstract')
 
437
        raise NotImplementedError(self.move)
408
438
 
409
439
    def get_parent(self):
410
440
        """Return the parent location of the branch.
413
443
        pattern is that the user can override it by specifying a
414
444
        location.
415
445
        """
416
 
        raise NotImplementedError('get_parent is abstract')
 
446
        raise NotImplementedError(self.get_parent)
 
447
 
 
448
    def get_submit_branch(self):
 
449
        """Return the submit location of the branch.
 
450
 
 
451
        This is the default location for bundle.  The usual
 
452
        pattern is that the user can override it by specifying a
 
453
        location.
 
454
        """
 
455
        return self.get_config().get_user_option('submit_branch')
 
456
 
 
457
    def set_submit_branch(self, location):
 
458
        """Return the submit location of the branch.
 
459
 
 
460
        This is the default location for bundle.  The usual
 
461
        pattern is that the user can override it by specifying a
 
462
        location.
 
463
        """
 
464
        self.get_config().set_user_option('submit_branch', location)
417
465
 
418
466
    def get_push_location(self):
419
467
        """Return the None or the location to push this branch to."""
420
 
        raise NotImplementedError('get_push_location is abstract')
 
468
        raise NotImplementedError(self.get_push_location)
421
469
 
422
470
    def set_push_location(self, location):
423
471
        """Set a new push location for this branch."""
424
 
        raise NotImplementedError('set_push_location is abstract')
 
472
        raise NotImplementedError(self.set_push_location)
425
473
 
426
474
    def set_parent(self, url):
427
 
        raise NotImplementedError('set_parent is abstract')
 
475
        raise NotImplementedError(self.set_parent)
428
476
 
429
477
    @needs_write_lock
430
478
    def update(self):
457
505
        revision_id: if not None, the revision history in the new branch will
458
506
                     be truncated to end with revision_id.
459
507
        """
460
 
        # for API compatability, until 0.8 releases we provide the old api:
 
508
        # for API compatibility, until 0.8 releases we provide the old api:
461
509
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
462
510
        # after 0.8 releases, the *args and **kwargs should be changed:
463
511
        # def clone(self, to_bzrdir, revision_id=None):
465
513
            kwargs.get('revision', None) or
466
514
            kwargs.get('basis_branch', None) or
467
515
            (len(args) and isinstance(args[0], basestring))):
468
 
            # backwards compatability api:
 
516
            # backwards compatibility api:
469
517
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
470
518
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
471
519
            # get basis_branch
518
566
        result.set_parent(self.bzrdir.root_transport.base)
519
567
        return result
520
568
 
521
 
    @needs_read_lock
522
 
    def copy_content_into(self, destination, revision_id=None):
523
 
        """Copy the content of self into destination.
524
 
 
525
 
        revision_id: if not None, the revision history in the new branch will
526
 
                     be truncated to end with revision_id.
 
569
    def _synchronize_history(self, destination, revision_id):
 
570
        """Synchronize last revision and revision history between branches.
 
571
 
 
572
        This version is most efficient when the destination is also a
 
573
        BzrBranch5, but works for BzrBranch6 as long as the revision
 
574
        history is the true lefthand parent history, and all of the revisions
 
575
        are in the destination's repository.  If not, set_revision_history
 
576
        will fail.
 
577
 
 
578
        :param destination: The branch to copy the history into
 
579
        :param revision_id: The revision-id to truncate history at.  May
 
580
          be None to copy complete history.
527
581
        """
528
582
        new_history = self.revision_history()
529
583
        if revision_id is not None:
 
584
            revision_id = osutils.safe_revision_id(revision_id)
530
585
            try:
531
586
                new_history = new_history[:new_history.index(revision_id) + 1]
532
587
            except ValueError:
533
588
                rev = self.repository.get_revision(revision_id)
534
589
                new_history = rev.get_history(self.repository)[1:]
535
590
        destination.set_revision_history(new_history)
536
 
        parent = self.get_parent()
537
 
        if parent:
538
 
            destination.set_parent(parent)
 
591
 
 
592
    @needs_read_lock
 
593
    def copy_content_into(self, destination, revision_id=None):
 
594
        """Copy the content of self into destination.
 
595
 
 
596
        revision_id: if not None, the revision history in the new branch will
 
597
                     be truncated to end with revision_id.
 
598
        """
 
599
        self._synchronize_history(destination, revision_id)
 
600
        try:
 
601
            parent = self.get_parent()
 
602
        except errors.InaccessibleParent, e:
 
603
            mutter('parent was not accessible to copy: %s', e)
 
604
        else:
 
605
            if parent:
 
606
                destination.set_parent(parent)
 
607
 
 
608
    @needs_read_lock
 
609
    def check(self):
 
610
        """Check consistency of the branch.
 
611
 
 
612
        In particular this checks that revisions given in the revision-history
 
613
        do actually match up in the revision graph, and that they're all 
 
614
        present in the repository.
 
615
        
 
616
        Callers will typically also want to check the repository.
 
617
 
 
618
        :return: A BranchCheckResult.
 
619
        """
 
620
        mainline_parent_id = None
 
621
        for revision_id in self.revision_history():
 
622
            try:
 
623
                revision = self.repository.get_revision(revision_id)
 
624
            except errors.NoSuchRevision, e:
 
625
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
 
626
                            % revision_id)
 
627
            # In general the first entry on the revision history has no parents.
 
628
            # But it's not illegal for it to have parents listed; this can happen
 
629
            # in imports from Arch when the parents weren't reachable.
 
630
            if mainline_parent_id is not None:
 
631
                if mainline_parent_id not in revision.parent_ids:
 
632
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
 
633
                                        "parents of {%s}"
 
634
                                        % (mainline_parent_id, revision_id))
 
635
            mainline_parent_id = revision_id
 
636
        return BranchCheckResult(self)
 
637
 
 
638
    def _get_checkout_format(self):
 
639
        """Return the most suitable metadir for a checkout of this branch.
 
640
        Weaves are used if this branch's repostory uses weaves.
 
641
        """
 
642
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
 
643
            from bzrlib.repofmt import weaverepo
 
644
            format = bzrdir.BzrDirMetaFormat1()
 
645
            format.repository_format = weaverepo.RepositoryFormat7()
 
646
        else:
 
647
            format = self.repository.bzrdir.cloning_metadir()
 
648
            format.branch_format = self._format
 
649
        return format
 
650
 
 
651
    def create_checkout(self, to_location, revision_id=None,
 
652
                        lightweight=False):
 
653
        """Create a checkout of a branch.
 
654
        
 
655
        :param to_location: The url to produce the checkout at
 
656
        :param revision_id: The revision to check out
 
657
        :param lightweight: If True, produce a lightweight checkout, otherwise,
 
658
        produce a bound branch (heavyweight checkout)
 
659
        :return: The tree of the created checkout
 
660
        """
 
661
        t = transport.get_transport(to_location)
 
662
        try:
 
663
            t.mkdir('.')
 
664
        except errors.FileExists:
 
665
            pass
 
666
        if lightweight:
 
667
            checkout = bzrdir.BzrDirMetaFormat1().initialize_on_transport(t)
 
668
            BranchReferenceFormat().initialize(checkout, self)
 
669
        else:
 
670
            format = self._get_checkout_format()
 
671
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
 
672
                to_location, force_new_tree=False, format=format)
 
673
            checkout = checkout_branch.bzrdir
 
674
            checkout_branch.bind(self)
 
675
            # pull up to the specified revision_id to set the initial 
 
676
            # branch tip correctly, and seed it with history.
 
677
            checkout_branch.pull(self, stop_revision=revision_id)
 
678
        return checkout.create_workingtree(revision_id)
539
679
 
540
680
 
541
681
class BranchFormat(object):
572
712
        except NoSuchFile:
573
713
            raise NotBranchError(path=transport.base)
574
714
        except KeyError:
575
 
            raise errors.UnknownFormatError(format_string)
 
715
            raise errors.UnknownFormatError(format=format_string)
576
716
 
577
717
    @classmethod
578
718
    def get_default_format(klass):
585
725
 
586
726
    def get_format_description(self):
587
727
        """Return the short format description for this format."""
588
 
        raise NotImplementedError(self.get_format_string)
 
728
        raise NotImplementedError(self.get_format_description)
 
729
 
 
730
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
 
731
                           set_format=True):
 
732
        """Initialize a branch in a bzrdir, with specified files
 
733
 
 
734
        :param a_bzrdir: The bzrdir to initialize the branch in
 
735
        :param utf8_files: The files to create as a list of
 
736
            (filename, content) tuples
 
737
        :param set_format: If True, set the format with
 
738
            self.get_format_string.  (BzrBranch4 has its format set
 
739
            elsewhere)
 
740
        :return: a branch in this format
 
741
        """
 
742
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
 
743
        branch_transport = a_bzrdir.get_branch_transport(self)
 
744
        lock_map = {
 
745
            'metadir': ('lock', lockdir.LockDir),
 
746
            'branch4': ('branch-lock', lockable_files.TransportLock),
 
747
        }
 
748
        lock_name, lock_class = lock_map[lock_type]
 
749
        control_files = lockable_files.LockableFiles(branch_transport,
 
750
            lock_name, lock_class)
 
751
        control_files.create_lock()
 
752
        control_files.lock_write()
 
753
        if set_format:
 
754
            control_files.put_utf8('format', self.get_format_string())
 
755
        try:
 
756
            for file, content in utf8_files:
 
757
                control_files.put_utf8(file, content)
 
758
        finally:
 
759
            control_files.unlock()
 
760
        return self.open(a_bzrdir, _found=True)
589
761
 
590
762
    def initialize(self, a_bzrdir):
591
763
        """Create a branch of this format in a_bzrdir."""
592
 
        raise NotImplementedError(self.initialized)
 
764
        raise NotImplementedError(self.initialize)
593
765
 
594
766
    def is_supported(self):
595
767
        """Is this format supported?
625
797
        return self.get_format_string().rstrip()
626
798
 
627
799
 
 
800
class BranchHooks(dict):
 
801
    """A dictionary mapping hook name to a list of callables for branch hooks.
 
802
    
 
803
    e.g. ['set_rh'] Is the list of items to be called when the
 
804
    set_revision_history function is invoked.
 
805
    """
 
806
 
 
807
    def __init__(self):
 
808
        """Create the default hooks.
 
809
 
 
810
        These are all empty initially, because by default nothing should get
 
811
        notified.
 
812
        """
 
813
        dict.__init__(self)
 
814
        # Introduced in 0.15:
 
815
        # invoked whenever the revision history has been set
 
816
        # with set_revision_history. The api signature is
 
817
        # (branch, revision_history), and the branch will
 
818
        # be write-locked.
 
819
        self['set_rh'] = []
 
820
        # invoked after a push operation completes.
 
821
        # the api signature is
 
822
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
 
823
        # where local is the local branch or None, master is the target 
 
824
        # master branch, and the rest should be self explanatory. The source
 
825
        # is read locked and the target branches write locked. Source will
 
826
        # be the local low-latency branch.
 
827
        self['post_push'] = []
 
828
        # invoked after a pull operation completes.
 
829
        # the api signature is
 
830
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
 
831
        # where local is the local branch or None, master is the target 
 
832
        # master branch, and the rest should be self explanatory. The source
 
833
        # is read locked and the target branches write locked. The local
 
834
        # branch is the low-latency branch.
 
835
        self['post_pull'] = []
 
836
        # invoked after a commit operation completes.
 
837
        # the api signature is 
 
838
        # (local, master, old_revno, old_revid, new_revno, new_revid)
 
839
        # old_revid is NULL_REVISION for the first commit to a branch.
 
840
        self['post_commit'] = []
 
841
        # invoked after a uncommit operation completes.
 
842
        # the api signature is
 
843
        # (local, master, old_revno, old_revid, new_revno, new_revid) where
 
844
        # local is the local branch or None, master is the target branch,
 
845
        # and an empty branch recieves new_revno of 0, new_revid of None.
 
846
        self['post_uncommit'] = []
 
847
 
 
848
    def install_hook(self, hook_name, a_callable):
 
849
        """Install a_callable in to the hook hook_name.
 
850
 
 
851
        :param hook_name: A hook name. See the __init__ method of BranchHooks
 
852
            for the complete list of hooks.
 
853
        :param a_callable: The callable to be invoked when the hook triggers.
 
854
            The exact signature will depend on the hook - see the __init__ 
 
855
            method of BranchHooks for details on each hook.
 
856
        """
 
857
        try:
 
858
            self[hook_name].append(a_callable)
 
859
        except KeyError:
 
860
            raise errors.UnknownHook('branch', hook_name)
 
861
 
 
862
 
 
863
# install the default hooks into the Branch class.
 
864
Branch.hooks = BranchHooks()
 
865
 
 
866
 
628
867
class BzrBranchFormat4(BranchFormat):
629
868
    """Bzr branch format 4.
630
869
 
639
878
 
640
879
    def initialize(self, a_bzrdir):
641
880
        """Create a branch of this format in a_bzrdir."""
642
 
        mutter('creating branch in %s', a_bzrdir.transport.base)
643
 
        branch_transport = a_bzrdir.get_branch_transport(self)
644
881
        utf8_files = [('revision-history', ''),
645
882
                      ('branch-name', ''),
646
883
                      ]
647
 
        control_files = LockableFiles(branch_transport, 'branch-lock',
648
 
                                      TransportLock)
649
 
        control_files.create_lock()
650
 
        control_files.lock_write()
651
 
        try:
652
 
            for file, content in utf8_files:
653
 
                control_files.put_utf8(file, content)
654
 
        finally:
655
 
            control_files.unlock()
656
 
        return self.open(a_bzrdir, _found=True)
 
884
        return self._initialize_helper(a_bzrdir, utf8_files,
 
885
                                       lock_type='branch4', set_format=False)
657
886
 
658
887
    def __init__(self):
659
888
        super(BzrBranchFormat4, self).__init__()
700
929
        
701
930
    def initialize(self, a_bzrdir):
702
931
        """Create a branch of this format in a_bzrdir."""
703
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
704
 
        branch_transport = a_bzrdir.get_branch_transport(self)
705
932
        utf8_files = [('revision-history', ''),
706
933
                      ('branch-name', ''),
707
934
                      ]
708
 
        control_files = LockableFiles(branch_transport, 'lock', LockDir)
709
 
        control_files.create_lock()
710
 
        control_files.lock_write()
711
 
        control_files.put_utf8('format', self.get_format_string())
712
 
        try:
713
 
            for file, content in utf8_files:
714
 
                control_files.put_utf8(file, content)
715
 
        finally:
716
 
            control_files.unlock()
717
 
        return self.open(a_bzrdir, _found=True, )
 
935
        return self._initialize_helper(a_bzrdir, utf8_files)
718
936
 
719
937
    def __init__(self):
720
938
        super(BzrBranchFormat5, self).__init__()
730
948
            format = BranchFormat.find_format(a_bzrdir)
731
949
            assert format.__class__ == self.__class__
732
950
        transport = a_bzrdir.get_branch_transport(None)
733
 
        control_files = LockableFiles(transport, 'lock', LockDir)
 
951
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
952
                                                     lockdir.LockDir)
734
953
        return BzrBranch5(_format=self,
735
954
                          _control_files=control_files,
736
955
                          a_bzrdir=a_bzrdir,
740
959
        return "Bazaar-NG Metadir branch format 5"
741
960
 
742
961
 
 
962
class BzrBranchFormat6(BzrBranchFormat5):
 
963
    """Branch format with last-revision
 
964
 
 
965
    Unlike previous formats, this has no explicit revision history. Instead,
 
966
    this just stores the last-revision, and the left-hand history leading
 
967
    up to there is the history.
 
968
 
 
969
    This format was introduced in bzr 0.15
 
970
    """
 
971
 
 
972
    def get_format_string(self):
 
973
        """See BranchFormat.get_format_string()."""
 
974
        return "Bazaar-NG branch format 6\n"
 
975
 
 
976
    def get_format_description(self):
 
977
        """See BranchFormat.get_format_description()."""
 
978
        return "Branch format 6"
 
979
 
 
980
    def initialize(self, a_bzrdir):
 
981
        """Create a branch of this format in a_bzrdir."""
 
982
        utf8_files = [('last-revision', '0 null:\n'),
 
983
                      ('branch-name', ''),
 
984
                      ('branch.conf', '')
 
985
                      ]
 
986
        return self._initialize_helper(a_bzrdir, utf8_files)
 
987
 
 
988
    def open(self, a_bzrdir, _found=False):
 
989
        """Return the branch object for a_bzrdir
 
990
 
 
991
        _found is a private parameter, do not use it. It is used to indicate
 
992
               if format probing has already be done.
 
993
        """
 
994
        if not _found:
 
995
            format = BranchFormat.find_format(a_bzrdir)
 
996
            assert format.__class__ == self.__class__
 
997
        transport = a_bzrdir.get_branch_transport(None)
 
998
        control_files = lockable_files.LockableFiles(transport, 'lock',
 
999
                                                     lockdir.LockDir)
 
1000
        return BzrBranch6(_format=self,
 
1001
                          _control_files=control_files,
 
1002
                          a_bzrdir=a_bzrdir,
 
1003
                          _repository=a_bzrdir.find_repository())
 
1004
 
 
1005
 
743
1006
class BranchReferenceFormat(BranchFormat):
744
1007
    """Bzr branch reference format.
745
1008
 
767
1030
            raise errors.UninitializableFormat(self)
768
1031
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
769
1032
        branch_transport = a_bzrdir.get_branch_transport(self)
770
 
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
771
 
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
772
 
        branch_transport.put('format', StringIO(self.get_format_string()))
 
1033
        branch_transport.put_bytes('location',
 
1034
            target_branch.bzrdir.root_transport.base)
 
1035
        branch_transport.put_bytes('format', self.get_format_string())
773
1036
        return self.open(a_bzrdir, _found=True)
774
1037
 
775
1038
    def __init__(self):
815
1078
__default_format = BzrBranchFormat5()
816
1079
BranchFormat.register_format(__default_format)
817
1080
BranchFormat.register_format(BranchReferenceFormat())
 
1081
BranchFormat.register_format(BzrBranchFormat6())
818
1082
BranchFormat.set_default_format(__default_format)
819
1083
_legacy_formats = [BzrBranchFormat4(),
820
1084
                   ]
851
1115
        self._base = self._transport.base
852
1116
        self._format = _format
853
1117
        if _control_files is None:
854
 
            raise BzrBadParameterMissing('_control_files')
 
1118
            raise ValueError('BzrBranch _control_files is None')
855
1119
        self.control_files = _control_files
856
1120
        if deprecated_passed(init):
857
1121
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
872
1136
                 stacklevel=2)
873
1137
            if (not relax_version_check
874
1138
                and not self._format.is_supported()):
875
 
                raise errors.UnsupportedFormatError(
876
 
                        'sorry, branch format %r not supported' % fmt,
877
 
                        ['use a different bzr version',
878
 
                         'or remove the .bzr directory'
879
 
                         ' and "bzr init" again'])
 
1139
                raise errors.UnsupportedFormatError(format=fmt)
880
1140
        if deprecated_passed(transport):
881
1141
            warn("BzrBranch.__init__(transport=XXX...): The transport "
882
1142
                 "parameter is deprecated as of bzr 0.8. "
890
1150
 
891
1151
    __repr__ = __str__
892
1152
 
893
 
    def __del__(self):
894
 
        # TODO: It might be best to do this somewhere else,
895
 
        # but it is nice for a Branch object to automatically
896
 
        # cache it's information.
897
 
        # Alternatively, we could have the Transport objects cache requests
898
 
        # See the earlier discussion about how major objects (like Branch)
899
 
        # should never expect their __del__ function to run.
900
 
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
901
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
902
 
            try:
903
 
                rmtree(self.cache_root)
904
 
            except:
905
 
                pass
906
 
            self.cache_root = None
907
 
 
908
1153
    def _get_base(self):
909
1154
        return self._base
910
1155
 
949
1194
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
950
1195
        """
951
1196
        if format is None:
952
 
            format = BzrBranchFormat.find_format(self.bzrdir)
 
1197
            format = BranchFormat.find_format(self.bzrdir)
953
1198
        self._format = format
954
1199
        mutter("got branch format %s", self._format)
955
1200
 
963
1208
        return self.control_files.is_locked()
964
1209
 
965
1210
    def lock_write(self):
966
 
        # TODO: test for failed two phase locks. This is known broken.
967
 
        self.control_files.lock_write()
968
1211
        self.repository.lock_write()
 
1212
        try:
 
1213
            self.control_files.lock_write()
 
1214
        except:
 
1215
            self.repository.unlock()
 
1216
            raise
969
1217
 
970
1218
    def lock_read(self):
971
 
        # TODO: test for failed two phase locks. This is known broken.
972
 
        self.control_files.lock_read()
973
1219
        self.repository.lock_read()
 
1220
        try:
 
1221
            self.control_files.lock_read()
 
1222
        except:
 
1223
            self.repository.unlock()
 
1224
            raise
974
1225
 
975
1226
    def unlock(self):
976
1227
        # TODO: test for failed two phase locks. This is known broken.
977
1228
        try:
978
 
            self.repository.unlock()
979
 
        finally:
980
1229
            self.control_files.unlock()
 
1230
        finally:
 
1231
            self.repository.unlock()
981
1232
        
982
1233
    def peek_lock_mode(self):
983
1234
        if self.control_files._lock_count == 0:
996
1247
    @needs_write_lock
997
1248
    def append_revision(self, *revision_ids):
998
1249
        """See Branch.append_revision."""
 
1250
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
999
1251
        for revision_id in revision_ids:
 
1252
            _mod_revision.check_not_reserved_id(revision_id)
1000
1253
            mutter("add {%s} to revision-history" % revision_id)
1001
1254
        rev_history = self.revision_history()
1002
1255
        rev_history.extend(revision_ids)
1003
1256
        self.set_revision_history(rev_history)
1004
1257
 
 
1258
    def _write_revision_history(self, history):
 
1259
        """Factored out of set_revision_history.
 
1260
 
 
1261
        This performs the actual writing to disk.
 
1262
        It is intended to be called by BzrBranch5.set_revision_history."""
 
1263
        self.control_files.put_bytes(
 
1264
            'revision-history', '\n'.join(history))
 
1265
 
1005
1266
    @needs_write_lock
1006
1267
    def set_revision_history(self, rev_history):
1007
1268
        """See Branch.set_revision_history."""
1008
 
        self.control_files.put_utf8(
1009
 
            'revision-history', '\n'.join(rev_history))
 
1269
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
 
1270
        self._write_revision_history(rev_history)
1010
1271
        transaction = self.get_transaction()
1011
1272
        history = transaction.map.find_revision_history()
1012
1273
        if history is not None:
1020
1281
            # this call is disabled because revision_history is 
1021
1282
            # not really an object yet, and the transaction is for objects.
1022
1283
            # transaction.register_clean(history)
1023
 
 
1024
 
    def get_revision_delta(self, revno):
1025
 
        """Return the delta for one revision.
1026
 
 
1027
 
        The delta is relative to its mainline predecessor, or the
1028
 
        empty tree for revision 1.
1029
 
        """
1030
 
        assert isinstance(revno, int)
1031
 
        rh = self.revision_history()
1032
 
        if not (1 <= revno <= len(rh)):
1033
 
            raise InvalidRevisionNumber(revno)
1034
 
 
1035
 
        # revno is 1-based; list is 0-based
1036
 
 
1037
 
        new_tree = self.repository.revision_tree(rh[revno-1])
1038
 
        if revno == 1:
1039
 
            old_tree = EmptyTree()
1040
 
        else:
1041
 
            old_tree = self.repository.revision_tree(rh[revno-2])
1042
 
        return compare_trees(old_tree, new_tree)
 
1284
        for hook in Branch.hooks['set_rh']:
 
1285
            hook(self, rev_history)
 
1286
 
 
1287
    @needs_write_lock
 
1288
    def set_last_revision_info(self, revno, revision_id):
 
1289
        revision_id = osutils.safe_revision_id(revision_id)
 
1290
        history = self._lefthand_history(revision_id)
 
1291
        assert len(history) == revno, '%d != %d' % (len(history), revno)
 
1292
        self.set_revision_history(history)
 
1293
 
 
1294
    def _gen_revision_history(self):
 
1295
        get_cached_utf8 = cache_utf8.get_cached_utf8
 
1296
        history = [get_cached_utf8(l.rstrip('\r\n')) for l in
 
1297
                self.control_files.get('revision-history').readlines()]
 
1298
        return history
1043
1299
 
1044
1300
    @needs_read_lock
1045
1301
    def revision_history(self):
1047
1303
        transaction = self.get_transaction()
1048
1304
        history = transaction.map.find_revision_history()
1049
1305
        if history is not None:
1050
 
            mutter("cache hit for revision-history in %s", self)
 
1306
            # mutter("cache hit for revision-history in %s", self)
1051
1307
            return list(history)
1052
 
        history = [l.rstrip('\r\n') for l in
1053
 
                self.control_files.get_utf8('revision-history').readlines()]
 
1308
        history = self._gen_revision_history()
1054
1309
        transaction.map.add_revision_history(history)
1055
1310
        # this call is disabled because revision_history is 
1056
1311
        # not really an object yet, and the transaction is for objects.
1057
1312
        # transaction.register_clean(history, precious=True)
1058
1313
        return list(history)
1059
1314
 
 
1315
    def _lefthand_history(self, revision_id, last_rev=None,
 
1316
                          other_branch=None):
 
1317
        # stop_revision must be a descendant of last_revision
 
1318
        stop_graph = self.repository.get_revision_graph(revision_id)
 
1319
        if last_rev is not None and last_rev not in stop_graph:
 
1320
            # our previous tip is not merged into stop_revision
 
1321
            raise errors.DivergedBranches(self, other_branch)
 
1322
        # make a new revision history from the graph
 
1323
        current_rev_id = revision_id
 
1324
        new_history = []
 
1325
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
 
1326
            new_history.append(current_rev_id)
 
1327
            current_rev_id_parents = stop_graph[current_rev_id]
 
1328
            try:
 
1329
                current_rev_id = current_rev_id_parents[0]
 
1330
            except IndexError:
 
1331
                current_rev_id = None
 
1332
        new_history.reverse()
 
1333
        return new_history
 
1334
 
 
1335
    @needs_write_lock
 
1336
    def generate_revision_history(self, revision_id, last_rev=None,
 
1337
        other_branch=None):
 
1338
        """Create a new revision history that will finish with revision_id.
 
1339
 
 
1340
        :param revision_id: the new tip to use.
 
1341
        :param last_rev: The previous last_revision. If not None, then this
 
1342
            must be a ancestory of revision_id, or DivergedBranches is raised.
 
1343
        :param other_branch: The other branch that DivergedBranches should
 
1344
            raise with respect to.
 
1345
        """
 
1346
        revision_id = osutils.safe_revision_id(revision_id)
 
1347
        self.set_revision_history(self._lefthand_history(revision_id,
 
1348
            last_rev, other_branch))
 
1349
 
1060
1350
    @needs_write_lock
1061
1351
    def update_revisions(self, other, stop_revision=None):
1062
1352
        """See Branch.update_revisions."""
1067
1357
                if stop_revision is None:
1068
1358
                    # if there are no commits, we're done.
1069
1359
                    return
 
1360
            else:
 
1361
                stop_revision = osutils.safe_revision_id(stop_revision)
1070
1362
            # whats the current last revision, before we fetch [and change it
1071
1363
            # possibly]
1072
1364
            last_rev = self.last_revision()
1077
1369
            if stop_revision in my_ancestry:
1078
1370
                # last_revision is a descendant of stop_revision
1079
1371
                return
1080
 
            # stop_revision must be a descendant of last_revision
1081
 
            stop_graph = self.repository.get_revision_graph(stop_revision)
1082
 
            if last_rev is not None and last_rev not in stop_graph:
1083
 
                # our previous tip is not merged into stop_revision
1084
 
                raise errors.DivergedBranches(self, other)
1085
 
            # make a new revision history from the graph
1086
 
            current_rev_id = stop_revision
1087
 
            new_history = []
1088
 
            while current_rev_id not in (None, NULL_REVISION):
1089
 
                new_history.append(current_rev_id)
1090
 
                current_rev_id_parents = stop_graph[current_rev_id]
1091
 
                try:
1092
 
                    current_rev_id = current_rev_id_parents[0]
1093
 
                except IndexError:
1094
 
                    current_rev_id = None
1095
 
            new_history.reverse()
1096
 
            self.set_revision_history(new_history)
 
1372
            self.generate_revision_history(stop_revision, last_rev=last_rev,
 
1373
                other_branch=other)
1097
1374
        finally:
1098
1375
            other.unlock()
1099
1376
 
1104
1381
    @deprecated_method(zero_eight)
1105
1382
    def working_tree(self):
1106
1383
        """Create a Working tree object for this branch."""
1107
 
        from bzrlib.workingtree import WorkingTree
 
1384
 
1108
1385
        from bzrlib.transport.local import LocalTransport
1109
1386
        if (self.base.find('://') != -1 or 
1110
1387
            not isinstance(self._transport, LocalTransport)):
1112
1389
        return self.bzrdir.open_workingtree()
1113
1390
 
1114
1391
    @needs_write_lock
1115
 
    def pull(self, source, overwrite=False, stop_revision=None):
1116
 
        """See Branch.pull."""
 
1392
    def pull(self, source, overwrite=False, stop_revision=None,
 
1393
        _hook_master=None, _run_hooks=True):
 
1394
        """See Branch.pull.
 
1395
 
 
1396
        :param _hook_master: Private parameter - set the branch to 
 
1397
            be supplied as the master to push hooks.
 
1398
        :param _run_hooks: Private parameter - allow disabling of
 
1399
            hooks, used when pushing to a master branch.
 
1400
        """
1117
1401
        source.lock_read()
1118
1402
        try:
1119
 
            old_count = len(self.revision_history())
 
1403
            old_count, old_tip = self.last_revision_info()
1120
1404
            try:
1121
 
                self.update_revisions(source,stop_revision)
 
1405
                self.update_revisions(source, stop_revision)
1122
1406
            except DivergedBranches:
1123
1407
                if not overwrite:
1124
1408
                    raise
1125
1409
            if overwrite:
1126
1410
                self.set_revision_history(source.revision_history())
1127
 
            new_count = len(self.revision_history())
 
1411
            new_count, new_tip = self.last_revision_info()
 
1412
            if _run_hooks:
 
1413
                if _hook_master:
 
1414
                    _hook_local = self
 
1415
                else:
 
1416
                    _hook_master = self
 
1417
                    _hook_local = None
 
1418
                for hook in Branch.hooks['post_pull']:
 
1419
                    hook(source, _hook_local, _hook_master, old_count, old_tip,
 
1420
                        new_count, new_tip)
1128
1421
            return new_count - old_count
1129
1422
        finally:
1130
1423
            source.unlock()
1131
1424
 
1132
 
    def get_parent(self):
1133
 
        """See Branch.get_parent."""
1134
 
        import errno
 
1425
    def _get_parent_location(self):
1135
1426
        _locs = ['parent', 'pull', 'x-pull']
1136
1427
        for l in _locs:
1137
1428
            try:
1138
 
                return self.control_files.get_utf8(l).read().strip('\n')
 
1429
                return self.control_files.get(l).read().strip('\n')
1139
1430
            except NoSuchFile:
1140
1431
                pass
1141
1432
        return None
1142
1433
 
 
1434
    @needs_read_lock
 
1435
    def push(self, target, overwrite=False, stop_revision=None,
 
1436
        _hook_master=None, _run_hooks=True):
 
1437
        """See Branch.push.
 
1438
        
 
1439
        :param _hook_master: Private parameter - set the branch to 
 
1440
            be supplied as the master to push hooks.
 
1441
        :param _run_hooks: Private parameter - allow disabling of
 
1442
            hooks, used when pushing to a master branch.
 
1443
        """
 
1444
        target.lock_write()
 
1445
        try:
 
1446
            old_count, old_tip = target.last_revision_info()
 
1447
            try:
 
1448
                target.update_revisions(self, stop_revision)
 
1449
            except DivergedBranches:
 
1450
                if not overwrite:
 
1451
                    raise
 
1452
            if overwrite:
 
1453
                target.set_revision_history(self.revision_history())
 
1454
            new_count, new_tip = target.last_revision_info()
 
1455
            if _run_hooks:
 
1456
                if _hook_master:
 
1457
                    _hook_local = target
 
1458
                else:
 
1459
                    _hook_master = target
 
1460
                    _hook_local = None
 
1461
                for hook in Branch.hooks['post_push']:
 
1462
                    hook(self, _hook_local, _hook_master, old_count, old_tip,
 
1463
                        new_count, new_tip)
 
1464
            return new_count - old_count
 
1465
        finally:
 
1466
            target.unlock()
 
1467
 
 
1468
    def get_parent(self):
 
1469
        """See Branch.get_parent."""
 
1470
 
 
1471
        assert self.base[-1] == '/'
 
1472
        parent = self._get_parent_location()
 
1473
        if parent is None:
 
1474
            return parent
 
1475
        # This is an old-format absolute path to a local branch
 
1476
        # turn it into a url
 
1477
        if parent.startswith('/'):
 
1478
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
 
1479
        try:
 
1480
            return urlutils.join(self.base[:-1], parent)
 
1481
        except errors.InvalidURLJoin, e:
 
1482
            raise errors.InaccessibleParent(parent, self.base)
 
1483
 
1143
1484
    def get_push_location(self):
1144
1485
        """See Branch.get_push_location."""
1145
 
        config = bzrlib.config.BranchConfig(self)
1146
 
        push_loc = config.get_user_option('push_location')
 
1486
        push_loc = self.get_config().get_user_option('push_location')
1147
1487
        return push_loc
1148
1488
 
1149
1489
    def set_push_location(self, location):
1150
1490
        """See Branch.set_push_location."""
1151
 
        config = bzrlib.config.LocationConfig(self.base)
1152
 
        config.set_user_option('push_location', location)
 
1491
        self.get_config().set_user_option(
 
1492
            'push_location', location,
 
1493
            store=_mod_config.STORE_LOCATION_NORECURSE)
1153
1494
 
1154
1495
    @needs_write_lock
1155
1496
    def set_parent(self, url):
1159
1500
        # FIXUP this and get_parent in a future branch format bump:
1160
1501
        # read and rewrite the file, and have the new format code read
1161
1502
        # using .get not .get_utf8. RBC 20060125
 
1503
        if url is not None:
 
1504
            if isinstance(url, unicode):
 
1505
                try: 
 
1506
                    url = url.encode('ascii')
 
1507
                except UnicodeEncodeError:
 
1508
                    raise bzrlib.errors.InvalidURL(url,
 
1509
                        "Urls must be 7-bit ascii, "
 
1510
                        "use bzrlib.urlutils.escape")
 
1511
            url = urlutils.relative_url(self.base, url)
 
1512
        self._set_parent_location(url)
 
1513
 
 
1514
    def _set_parent_location(self, url):
1162
1515
        if url is None:
1163
1516
            self.control_files._transport.delete('parent')
1164
1517
        else:
1165
 
            self.control_files.put_utf8('parent', url + '\n')
 
1518
            assert isinstance(url, str)
 
1519
            self.control_files.put_bytes('parent', url + '\n')
1166
1520
 
 
1521
    @deprecated_function(zero_nine)
1167
1522
    def tree_config(self):
 
1523
        """DEPRECATED; call get_config instead.  
 
1524
        TreeConfig has become part of BranchConfig."""
1168
1525
        return TreeConfig(self)
1169
1526
 
1170
1527
 
1185
1542
                                         _repository=_repository)
1186
1543
        
1187
1544
    @needs_write_lock
1188
 
    def pull(self, source, overwrite=False, stop_revision=None):
1189
 
        """Updates branch.pull to be bound branch aware."""
 
1545
    def pull(self, source, overwrite=False, stop_revision=None,
 
1546
        _run_hooks=True):
 
1547
        """Extends branch.pull to be bound branch aware.
 
1548
        
 
1549
        :param _run_hooks: Private parameter used to force hook running
 
1550
            off during bound branch double-pushing.
 
1551
        """
1190
1552
        bound_location = self.get_bound_location()
1191
 
        if source.base != bound_location:
 
1553
        master_branch = None
 
1554
        if bound_location and source.base != bound_location:
1192
1555
            # not pulling from master, so we need to update master.
1193
1556
            master_branch = self.get_master_branch()
1194
 
            if master_branch:
1195
 
                master_branch.pull(source)
1196
 
                source = master_branch
1197
 
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
 
1557
            master_branch.lock_write()
 
1558
        try:
 
1559
            if master_branch:
 
1560
                # pull from source into master.
 
1561
                master_branch.pull(source, overwrite, stop_revision,
 
1562
                    _run_hooks=False)
 
1563
            return super(BzrBranch5, self).pull(source, overwrite,
 
1564
                stop_revision, _hook_master=master_branch,
 
1565
                _run_hooks=_run_hooks)
 
1566
        finally:
 
1567
            if master_branch:
 
1568
                master_branch.unlock()
 
1569
 
 
1570
    @needs_read_lock
 
1571
    def push(self, target, overwrite=False, stop_revision=None):
 
1572
        """Updates branch.push to be bound branch aware."""
 
1573
        bound_location = target.get_bound_location()
 
1574
        master_branch = None
 
1575
        if bound_location and target.base != bound_location:
 
1576
            # not pushing to master, so we need to update master.
 
1577
            master_branch = target.get_master_branch()
 
1578
            master_branch.lock_write()
 
1579
        try:
 
1580
            if master_branch:
 
1581
                # push into the master from this branch.
 
1582
                super(BzrBranch5, self).push(master_branch, overwrite,
 
1583
                    stop_revision, _run_hooks=False)
 
1584
            # and push into the target branch from this. Note that we push from
 
1585
            # this branch again, because its considered the highest bandwidth
 
1586
            # repository.
 
1587
            return super(BzrBranch5, self).push(target, overwrite,
 
1588
                stop_revision, _hook_master=master_branch)
 
1589
        finally:
 
1590
            if master_branch:
 
1591
                master_branch.unlock()
1198
1592
 
1199
1593
    def get_bound_location(self):
1200
1594
        try:
1210
1604
 
1211
1605
        This could memoise the branch, but if thats done
1212
1606
        it must be revalidated on each new lock.
1213
 
        So for now we just dont memoise it.
 
1607
        So for now we just don't memoise it.
1214
1608
        # RBC 20060304 review this decision.
1215
1609
        """
1216
1610
        bound_loc = self.get_bound_location()
1239
1633
 
1240
1634
    @needs_write_lock
1241
1635
    def bind(self, other):
1242
 
        """Bind the local branch the other branch.
 
1636
        """Bind this branch to the branch other.
1243
1637
 
 
1638
        This does not push or pull data between the branches, though it does
 
1639
        check for divergence to raise an error when the branches are not
 
1640
        either the same, or one a prefix of the other. That behaviour may not
 
1641
        be useful, so that check may be removed in future.
 
1642
        
1244
1643
        :param other: The branch to bind to
1245
1644
        :type other: Branch
1246
1645
        """
1251
1650
        #       but binding itself may not be.
1252
1651
        #       Since we *have* to check at commit time, we don't
1253
1652
        #       *need* to check here
1254
 
        self.pull(other)
1255
 
 
1256
 
        # we are now equal to or a suffix of other.
1257
 
 
1258
 
        # Since we have 'pulled' from the remote location,
1259
 
        # now we should try to pull in the opposite direction
1260
 
        # in case the local tree has more revisions than the
1261
 
        # remote one.
1262
 
        # There may be a different check you could do here
1263
 
        # rather than actually trying to install revisions remotely.
1264
 
        # TODO: capture an exception which indicates the remote branch
1265
 
        #       is not writeable. 
1266
 
        #       If it is up-to-date, this probably should not be a failure
1267
 
        
1268
 
        # lock other for write so the revision-history syncing cannot race
1269
 
        other.lock_write()
1270
 
        try:
1271
 
            other.pull(self)
1272
 
            # if this does not error, other now has the same last rev we do
1273
 
            # it can only error if the pull from other was concurrent with
1274
 
            # a commit to other from someone else.
1275
 
 
1276
 
            # until we ditch revision-history, we need to sync them up:
1277
 
            self.set_revision_history(other.revision_history())
1278
 
            # now other and self are up to date with each other and have the
1279
 
            # same revision-history.
1280
 
        finally:
1281
 
            other.unlock()
1282
 
 
 
1653
 
 
1654
        # we want to raise diverged if:
 
1655
        # last_rev is not in the other_last_rev history, AND
 
1656
        # other_last_rev is not in our history, and do it without pulling
 
1657
        # history around
 
1658
        last_rev = self.last_revision()
 
1659
        if last_rev is not None:
 
1660
            other.lock_read()
 
1661
            try:
 
1662
                other_last_rev = other.last_revision()
 
1663
                if other_last_rev is not None:
 
1664
                    # neither branch is new, we have to do some work to
 
1665
                    # ascertain diversion.
 
1666
                    remote_graph = other.repository.get_revision_graph(
 
1667
                        other_last_rev)
 
1668
                    local_graph = self.repository.get_revision_graph(last_rev)
 
1669
                    if (last_rev not in remote_graph and
 
1670
                        other_last_rev not in local_graph):
 
1671
                        raise errors.DivergedBranches(self, other)
 
1672
            finally:
 
1673
                other.unlock()
1283
1674
        self.set_bound_location(other.base)
1284
1675
 
1285
1676
    @needs_write_lock
1304
1695
        return None
1305
1696
 
1306
1697
 
 
1698
class BzrBranch6(BzrBranch5):
 
1699
 
 
1700
    @needs_read_lock
 
1701
    def last_revision_info(self):
 
1702
        revision_string = self.control_files.get('last-revision').read()
 
1703
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
 
1704
        revision_id = cache_utf8.get_cached_utf8(revision_id)
 
1705
        revno = int(revno)
 
1706
        return revno, revision_id
 
1707
 
 
1708
    def last_revision(self):
 
1709
        """Return last revision id, or None"""
 
1710
        revision_id = self.last_revision_info()[1]
 
1711
        if revision_id == _mod_revision.NULL_REVISION:
 
1712
            revision_id = None
 
1713
        return revision_id
 
1714
 
 
1715
    def _write_last_revision_info(self, revno, revision_id):
 
1716
        """Simply write out the revision id, with no checks.
 
1717
 
 
1718
        Use set_last_revision_info to perform this safely.
 
1719
 
 
1720
        Does not update the revision_history cache.
 
1721
        Intended to be called by set_last_revision_info and
 
1722
        _write_revision_history.
 
1723
        """
 
1724
        if revision_id is None:
 
1725
            revision_id = 'null:'
 
1726
        out_string = '%d %s\n' % (revno, revision_id)
 
1727
        self.control_files.put_bytes('last-revision', out_string)
 
1728
 
 
1729
    @needs_write_lock
 
1730
    def set_last_revision_info(self, revno, revision_id):
 
1731
        revision_id = osutils.safe_revision_id(revision_id)
 
1732
        if self._get_append_revisions_only():
 
1733
            self._check_history_violation(revision_id)
 
1734
        self._write_last_revision_info(revno, revision_id)
 
1735
        transaction = self.get_transaction()
 
1736
        cached_history = transaction.map.find_revision_history()
 
1737
        if cached_history is not None:
 
1738
            transaction.map.remove_object(cached_history)
 
1739
 
 
1740
    def _check_history_violation(self, revision_id):
 
1741
        last_revision = self.last_revision()
 
1742
        if last_revision is None:
 
1743
            return
 
1744
        if last_revision not in self._lefthand_history(revision_id):
 
1745
            raise errors.AppendRevisionsOnlyViolation(self.base)
 
1746
 
 
1747
    def _gen_revision_history(self):
 
1748
        """Generate the revision history from last revision
 
1749
        """
 
1750
        history = list(self.repository.iter_reverse_revision_history(
 
1751
            self.last_revision()))
 
1752
        history.reverse()
 
1753
        return history
 
1754
 
 
1755
    def _write_revision_history(self, history):
 
1756
        """Factored out of set_revision_history.
 
1757
 
 
1758
        This performs the actual writing to disk, with format-specific checks.
 
1759
        It is intended to be called by BzrBranch5.set_revision_history.
 
1760
        """
 
1761
        if len(history) == 0:
 
1762
            last_revision = 'null:'
 
1763
        else:
 
1764
            if history != self._lefthand_history(history[-1]):
 
1765
                raise errors.NotLefthandHistory(history)
 
1766
            last_revision = history[-1]
 
1767
        if self._get_append_revisions_only():
 
1768
            self._check_history_violation(last_revision)
 
1769
        self._write_last_revision_info(len(history), last_revision)
 
1770
 
 
1771
    @needs_write_lock
 
1772
    def append_revision(self, *revision_ids):
 
1773
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
 
1774
        if len(revision_ids) == 0:
 
1775
            return
 
1776
        prev_revno, prev_revision = self.last_revision_info()
 
1777
        for revision in self.repository.get_revisions(revision_ids):
 
1778
            if prev_revision == _mod_revision.NULL_REVISION:
 
1779
                if revision.parent_ids != []:
 
1780
                    raise errors.NotLeftParentDescendant(self, prev_revision,
 
1781
                                                         revision.revision_id)
 
1782
            else:
 
1783
                if revision.parent_ids[0] != prev_revision:
 
1784
                    raise errors.NotLeftParentDescendant(self, prev_revision,
 
1785
                                                         revision.revision_id)
 
1786
            prev_revision = revision.revision_id
 
1787
        self.set_last_revision_info(prev_revno + len(revision_ids),
 
1788
                                    revision_ids[-1])
 
1789
 
 
1790
    def _set_config_location(self, name, url, config=None,
 
1791
                             make_relative=False):
 
1792
        if config is None:
 
1793
            config = self.get_config()
 
1794
        if url is None:
 
1795
            url = ''
 
1796
        elif make_relative:
 
1797
            url = urlutils.relative_url(self.base, url)
 
1798
        config.set_user_option(name, url)
 
1799
 
 
1800
 
 
1801
    def _get_config_location(self, name, config=None):
 
1802
        if config is None:
 
1803
            config = self.get_config()
 
1804
        location = config.get_user_option(name)
 
1805
        if location == '':
 
1806
            location = None
 
1807
        return location
 
1808
 
 
1809
    @needs_write_lock
 
1810
    def _set_parent_location(self, url):
 
1811
        """Set the parent branch"""
 
1812
        self._set_config_location('parent_location', url, make_relative=True)
 
1813
 
 
1814
    @needs_read_lock
 
1815
    def _get_parent_location(self):
 
1816
        """Set the parent branch"""
 
1817
        return self._get_config_location('parent_location')
 
1818
 
 
1819
    def set_push_location(self, location):
 
1820
        """See Branch.set_push_location."""
 
1821
        self._set_config_location('push_location', location)
 
1822
 
 
1823
    def set_bound_location(self, location):
 
1824
        """See Branch.set_push_location."""
 
1825
        result = None
 
1826
        config = self.get_config()
 
1827
        if location is None:
 
1828
            if config.get_user_option('bound') != 'True':
 
1829
                return False
 
1830
            else:
 
1831
                config.set_user_option('bound', 'False')
 
1832
                return True
 
1833
        else:
 
1834
            self._set_config_location('bound_location', location,
 
1835
                                      config=config)
 
1836
            config.set_user_option('bound', 'True')
 
1837
        return True
 
1838
 
 
1839
    def _get_bound_location(self, bound):
 
1840
        """Return the bound location in the config file.
 
1841
 
 
1842
        Return None if the bound parameter does not match"""
 
1843
        config = self.get_config()
 
1844
        config_bound = (config.get_user_option('bound') == 'True')
 
1845
        if config_bound != bound:
 
1846
            return None
 
1847
        return self._get_config_location('bound_location', config=config)
 
1848
 
 
1849
    def get_bound_location(self):
 
1850
        """See Branch.set_push_location."""
 
1851
        return self._get_bound_location(True)
 
1852
 
 
1853
    def get_old_bound_location(self):
 
1854
        """See Branch.get_old_bound_location"""
 
1855
        return self._get_bound_location(False)
 
1856
 
 
1857
    def set_append_revisions_only(self, enabled):
 
1858
        if enabled:
 
1859
            value = 'True'
 
1860
        else:
 
1861
            value = 'False'
 
1862
        self.get_config().set_user_option('append_revisions_only', value)
 
1863
 
 
1864
    def _get_append_revisions_only(self):
 
1865
        value = self.get_config().get_user_option('append_revisions_only')
 
1866
        return value == 'True'
 
1867
 
 
1868
    def _synchronize_history(self, destination, revision_id):
 
1869
        """Synchronize last revision and revision history between branches.
 
1870
 
 
1871
        This version is most efficient when the destination is also a
 
1872
        BzrBranch6, but works for BzrBranch5, as long as the destination's
 
1873
        repository contains all the lefthand ancestors of the intended
 
1874
        last_revision.  If not, set_last_revision_info will fail.
 
1875
 
 
1876
        :param destination: The branch to copy the history into
 
1877
        :param revision_id: The revision-id to truncate history at.  May
 
1878
          be None to copy complete history.
 
1879
        """
 
1880
        if revision_id is None:
 
1881
            revno, revision_id = self.last_revision_info()
 
1882
        else:
 
1883
            revno = self.revision_id_to_revno(revision_id)
 
1884
        destination.set_last_revision_info(revno, revision_id)
 
1885
 
 
1886
 
1307
1887
class BranchTestProviderAdapter(object):
1308
1888
    """A tool to generate a suite testing multiple branch formats at once.
1309
1889
 
1334
1914
        return result
1335
1915
 
1336
1916
 
 
1917
class BranchCheckResult(object):
 
1918
    """Results of checking branch consistency.
 
1919
 
 
1920
    :see: Branch.check
 
1921
    """
 
1922
 
 
1923
    def __init__(self, branch):
 
1924
        self.branch = branch
 
1925
 
 
1926
    def report_results(self, verbose):
 
1927
        """Report the check results via trace.note.
 
1928
        
 
1929
        :param verbose: Requests more detailed display of what was checked,
 
1930
            if any.
 
1931
        """
 
1932
        note('checked branch %s format %s',
 
1933
             self.branch.base,
 
1934
             self.branch._format)
 
1935
 
 
1936
 
1337
1937
######################################################################
1338
1938
# predicates
1339
1939
 
1340
1940
 
1341
1941
@deprecated_function(zero_eight)
1342
 
def ScratchBranch(*args, **kwargs):
1343
 
    """See bzrlib.bzrdir.ScratchDir."""
1344
 
    d = ScratchDir(*args, **kwargs)
1345
 
    return d.open_branch()
1346
 
 
1347
 
 
1348
 
@deprecated_function(zero_eight)
1349
1942
def is_control_file(*args, **kwargs):
1350
1943
    """See bzrlib.workingtree.is_control_file."""
1351
 
    return bzrlib.workingtree.is_control_file(*args, **kwargs)
 
1944
    from bzrlib import workingtree
 
1945
    return workingtree.is_control_file(*args, **kwargs)
 
1946
 
 
1947
 
 
1948
class Converter5to6(object):
 
1949
    """Perform an in-place upgrade of format 5 to format 6"""
 
1950
 
 
1951
    def convert(self, branch):
 
1952
        # Data for 5 and 6 can peacefully coexist.
 
1953
        format = BzrBranchFormat6()
 
1954
        new_branch = format.open(branch.bzrdir, _found=True)
 
1955
 
 
1956
        # Copy source data into target
 
1957
        new_branch.set_last_revision_info(*branch.last_revision_info())
 
1958
        new_branch.set_parent(branch.get_parent())
 
1959
        new_branch.set_bound_location(branch.get_bound_location())
 
1960
        new_branch.set_push_location(branch.get_push_location())
 
1961
 
 
1962
        # Copying done; now update target format
 
1963
        new_branch.control_files.put_utf8('format',
 
1964
            format.get_format_string())
 
1965
 
 
1966
        # Clean up old files
 
1967
        new_branch.control_files._transport.delete('revision-history')
 
1968
        try:
 
1969
            branch.set_parent(None)
 
1970
        except NoSuchFile:
 
1971
            pass
 
1972
        branch.set_bound_location(None)