~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-06-22 06:37:43 UTC
  • Revision ID: mbp@sourcefrog.net-20050622063743-e395f04c4db8977f
- move old blackbox code from testbzr into bzrlib.selftest.blackbox

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
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(), """
22
 
from warnings import warn
 
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
 
19
import traceback, socket, fnmatch, difflib, time
 
20
from binascii import hexlify
23
21
 
24
22
import bzrlib
25
 
from bzrlib import (
26
 
        bzrdir,
27
 
        cache_utf8,
28
 
        config as _mod_config,
29
 
        errors,
30
 
        lockdir,
31
 
        lockable_files,
32
 
        osutils,
33
 
        revision as _mod_revision,
34
 
        transport,
35
 
        tree,
36
 
        tsort,
37
 
        ui,
38
 
        urlutils,
39
 
        )
40
 
from bzrlib.config import BranchConfig, TreeConfig
41
 
from bzrlib.lockable_files import LockableFiles, TransportLock
42
 
from bzrlib.tag import (
43
 
    BasicTags,
44
 
    DisabledTags,
45
 
    )
46
 
""")
47
 
 
48
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
49
 
from bzrlib.errors import (BzrError, BzrCheckError, DivergedBranches,
50
 
                           HistoryMissing, InvalidRevisionId,
51
 
                           InvalidRevisionNumber, LockError, NoSuchFile,
52
 
                           NoSuchRevision, NoWorkingTree, NotVersionedError,
53
 
                           NotBranchError, UninitializableFormat,
54
 
                           UnlistableStore, UnlistableBranch,
55
 
                           )
56
 
from bzrlib.hooks import Hooks
57
 
from bzrlib.symbol_versioning import (deprecated_function,
58
 
                                      deprecated_method,
59
 
                                      DEPRECATED_PARAMETER,
60
 
                                      deprecated_passed,
61
 
                                      zero_eight, zero_nine, zero_sixteen,
62
 
                                      )
63
 
from bzrlib.trace import mutter, note
64
 
 
65
 
 
66
 
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
67
 
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
68
 
BZR_BRANCH_FORMAT_6 = "Bazaar Branch Format 6 (bzr 0.15)\n"
69
 
 
70
 
 
71
 
# TODO: Maybe include checks for common corruption of newlines, etc?
72
 
 
73
 
# TODO: Some operations like log might retrieve the same revisions
74
 
# repeatedly to calculate deltas.  We could perhaps have a weakref
75
 
# cache in memory to make this faster.  In general anything can be
76
 
# cached in memory between lock and unlock operations. .. nb thats
77
 
# what the transaction identity map provides
 
23
from inventory import Inventory
 
24
from trace import mutter, note
 
25
from tree import Tree, EmptyTree, RevisionTree
 
26
from inventory import InventoryEntry, Inventory
 
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
 
28
     format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
 
29
     joinpath, sha_file, sha_string, file_kind, local_time_offset, appendpath
 
30
from store import ImmutableStore
 
31
from revision import Revision
 
32
from errors import BzrError
 
33
from textui import show_status
 
34
 
 
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
 
36
## TODO: Maybe include checks for common corruption of newlines, etc?
 
37
 
 
38
 
 
39
 
 
40
def find_branch(f, **args):
 
41
    if f and (f.startswith('http://') or f.startswith('https://')):
 
42
        import remotebranch 
 
43
        return remotebranch.RemoteBranch(f, **args)
 
44
    else:
 
45
        return Branch(f, **args)
 
46
 
 
47
 
 
48
 
 
49
def _relpath(base, path):
 
50
    """Return path relative to base, or raise exception.
 
51
 
 
52
    The path may be either an absolute path or a path relative to the
 
53
    current working directory.
 
54
 
 
55
    Lifted out of Branch.relpath for ease of testing.
 
56
 
 
57
    os.path.commonprefix (python2.4) has a bad bug that it works just
 
58
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
 
59
    avoids that problem."""
 
60
    rp = os.path.abspath(path)
 
61
 
 
62
    s = []
 
63
    head = rp
 
64
    while len(head) >= len(base):
 
65
        if head == base:
 
66
            break
 
67
        head, tail = os.path.split(head)
 
68
        if tail:
 
69
            s.insert(0, tail)
 
70
    else:
 
71
        from errors import NotBranchError
 
72
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
 
73
 
 
74
    return os.sep.join(s)
 
75
        
 
76
 
 
77
def find_branch_root(f=None):
 
78
    """Find the branch root enclosing f, or pwd.
 
79
 
 
80
    f may be a filename or a URL.
 
81
 
 
82
    It is not necessary that f exists.
 
83
 
 
84
    Basically we keep looking up until we find the control directory or
 
85
    run into the root."""
 
86
    if f == None:
 
87
        f = os.getcwd()
 
88
    elif hasattr(os.path, 'realpath'):
 
89
        f = os.path.realpath(f)
 
90
    else:
 
91
        f = os.path.abspath(f)
 
92
    if not os.path.exists(f):
 
93
        raise BzrError('%r does not exist' % f)
 
94
        
 
95
 
 
96
    orig_f = f
 
97
 
 
98
    while True:
 
99
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
 
100
            return f
 
101
        head, tail = os.path.split(f)
 
102
        if head == f:
 
103
            # reached the root, whatever that may be
 
104
            raise BzrError('%r is not in a branch' % orig_f)
 
105
        f = head
 
106
    
 
107
class DivergedBranches(Exception):
 
108
    def __init__(self, branch1, branch2):
 
109
        self.branch1 = branch1
 
110
        self.branch2 = branch2
 
111
        Exception.__init__(self, "These branches have diverged.")
 
112
 
 
113
 
 
114
class NoSuchRevision(BzrError):
 
115
    def __init__(self, branch, revision):
 
116
        self.branch = branch
 
117
        self.revision = revision
 
118
        msg = "Branch %s has no revision %d" % (branch, revision)
 
119
        BzrError.__init__(self, msg)
78
120
 
79
121
 
80
122
######################################################################
84
126
    """Branch holding a history of revisions.
85
127
 
86
128
    base
87
 
        Base directory/url of the branch.
88
 
 
89
 
    hooks: An instance of BranchHooks.
 
129
        Base directory of the branch.
 
130
 
 
131
    _lock_mode
 
132
        None, or 'r' or 'w'
 
133
 
 
134
    _lock_count
 
135
        If _lock_mode is true, a positive count of the number of times the
 
136
        lock has been taken.
 
137
 
 
138
    _lock
 
139
        Lock object from bzrlib.lock.
90
140
    """
91
 
    # this is really an instance variable - FIXME move it there
92
 
    # - RBC 20060112
93
141
    base = None
94
 
 
95
 
    # override this to set the strategy for storing tags
96
 
    def _make_tags(self):
97
 
        return DisabledTags(self)
98
 
 
99
 
    def __init__(self, *ignored, **ignored_too):
100
 
        self.tags = self._make_tags()
101
 
        self._revision_history_cache = None
102
 
        self._revision_id_to_revno_cache = None
103
 
 
104
 
    def break_lock(self):
105
 
        """Break a lock if one is present from another instance.
106
 
 
107
 
        Uses the ui factory to ask for confirmation if the lock may be from
108
 
        an active process.
109
 
 
110
 
        This will probe the repository for its lock as well.
111
 
        """
112
 
        self.control_files.break_lock()
113
 
        self.repository.break_lock()
114
 
        master = self.get_master_branch()
115
 
        if master is not None:
116
 
            master.break_lock()
117
 
 
118
 
    @staticmethod
119
 
    @deprecated_method(zero_eight)
120
 
    def open_downlevel(base):
121
 
        """Open a branch which may be of an old format."""
122
 
        return Branch.open(base, _unsupported=True)
123
 
        
124
 
    @staticmethod
125
 
    def open(base, _unsupported=False):
126
 
        """Open the branch rooted at base.
127
 
 
128
 
        For instance, if the branch is at URL/.bzr/branch,
129
 
        Branch.open(URL) -> a Branch instance.
130
 
        """
131
 
        control = bzrdir.BzrDir.open(base, _unsupported)
132
 
        return control.open_branch(_unsupported)
133
 
 
134
 
    @staticmethod
135
 
    def open_containing(url):
136
 
        """Open an existing branch which contains url.
137
 
        
138
 
        This probes for a branch at url, and searches upwards from there.
139
 
 
140
 
        Basically we keep looking up until we find the control directory or
141
 
        run into the root.  If there isn't one, raises NotBranchError.
142
 
        If there is one and it is either an unrecognised format or an unsupported 
143
 
        format, UnknownFormatError or UnsupportedFormatError are raised.
144
 
        If there is one, it is returned, along with the unused portion of url.
145
 
        """
146
 
        control, relpath = bzrdir.BzrDir.open_containing(url)
147
 
        return control.open_branch(), relpath
148
 
 
149
 
    @staticmethod
150
 
    @deprecated_function(zero_eight)
151
 
    def initialize(base):
152
 
        """Create a new working tree and branch, rooted at 'base' (url)
153
 
 
154
 
        NOTE: This will soon be deprecated in favour of creation
155
 
        through a BzrDir.
156
 
        """
157
 
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
158
 
 
159
 
    @deprecated_function(zero_eight)
160
 
    def setup_caching(self, cache_root):
161
 
        """Subclasses that care about caching should override this, and set
162
 
        up cached stores located under cache_root.
163
 
        
164
 
        NOTE: This is unused.
165
 
        """
166
 
        pass
167
 
 
168
 
    def get_config(self):
169
 
        return BranchConfig(self)
170
 
 
171
 
    def _get_nick(self):
172
 
        return self.get_config().get_nickname()
173
 
 
174
 
    def _set_nick(self, nick):
175
 
        self.get_config().set_user_option('nickname', nick, warn_masked=True)
176
 
 
177
 
    nick = property(_get_nick, _set_nick)
178
 
 
179
 
    def is_locked(self):
180
 
        raise NotImplementedError(self.is_locked)
 
142
    _lock_mode = None
 
143
    _lock_count = None
 
144
    _lock = None
 
145
    
 
146
    def __init__(self, base, init=False, find_root=True):
 
147
        """Create new branch object at a particular location.
 
148
 
 
149
        base -- Base directory for the branch.
 
150
        
 
151
        init -- If True, create new control files in a previously
 
152
             unversioned directory.  If False, the branch must already
 
153
             be versioned.
 
154
 
 
155
        find_root -- If true and init is false, find the root of the
 
156
             existing branch containing base.
 
157
 
 
158
        In the test suite, creation of new trees is tested using the
 
159
        `ScratchBranch` class.
 
160
        """
 
161
        if init:
 
162
            self.base = os.path.realpath(base)
 
163
            self._make_control()
 
164
        elif find_root:
 
165
            self.base = find_branch_root(base)
 
166
        else:
 
167
            self.base = os.path.realpath(base)
 
168
            if not isdir(self.controlfilename('.')):
 
169
                from errors import NotBranchError
 
170
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
 
171
                                     ['use "bzr init" to initialize a new working tree',
 
172
                                      'current bzr can only operate from top-of-tree'])
 
173
        self._check_format()
 
174
 
 
175
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
 
176
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
 
177
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
 
178
 
 
179
 
 
180
    def __str__(self):
 
181
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
182
 
 
183
 
 
184
    __repr__ = __str__
 
185
 
 
186
 
 
187
    def __del__(self):
 
188
        if self._lock_mode or self._lock:
 
189
            from warnings import warn
 
190
            warn("branch %r was not explicitly unlocked" % self)
 
191
            self._lock.unlock()
 
192
 
 
193
 
181
194
 
182
195
    def lock_write(self):
183
 
        raise NotImplementedError(self.lock_write)
 
196
        if self._lock_mode:
 
197
            if self._lock_mode != 'w':
 
198
                from errors import LockError
 
199
                raise LockError("can't upgrade to a write lock from %r" %
 
200
                                self._lock_mode)
 
201
            self._lock_count += 1
 
202
        else:
 
203
            from bzrlib.lock import WriteLock
 
204
 
 
205
            self._lock = WriteLock(self.controlfilename('branch-lock'))
 
206
            self._lock_mode = 'w'
 
207
            self._lock_count = 1
 
208
 
 
209
 
184
210
 
185
211
    def lock_read(self):
186
 
        raise NotImplementedError(self.lock_read)
187
 
 
 
212
        if self._lock_mode:
 
213
            assert self._lock_mode in ('r', 'w'), \
 
214
                   "invalid lock mode %r" % self._lock_mode
 
215
            self._lock_count += 1
 
216
        else:
 
217
            from bzrlib.lock import ReadLock
 
218
 
 
219
            self._lock = ReadLock(self.controlfilename('branch-lock'))
 
220
            self._lock_mode = 'r'
 
221
            self._lock_count = 1
 
222
                        
 
223
 
 
224
            
188
225
    def unlock(self):
189
 
        raise NotImplementedError(self.unlock)
190
 
 
191
 
    def peek_lock_mode(self):
192
 
        """Return lock mode for the Branch: 'r', 'w' or None"""
193
 
        raise NotImplementedError(self.peek_lock_mode)
194
 
 
195
 
    def get_physical_lock_status(self):
196
 
        raise NotImplementedError(self.get_physical_lock_status)
197
 
 
198
 
    @needs_read_lock
199
 
    def get_revision_id_to_revno_map(self):
200
 
        """Return the revision_id => dotted revno map.
201
 
 
202
 
        This will be regenerated on demand, but will be cached.
203
 
 
204
 
        :return: A dictionary mapping revision_id => dotted revno.
205
 
            This dictionary should not be modified by the caller.
206
 
        """
207
 
        if self._revision_id_to_revno_cache is not None:
208
 
            mapping = self._revision_id_to_revno_cache
 
226
        if not self._lock_mode:
 
227
            from errors import LockError
 
228
            raise LockError('branch %r is not locked' % (self))
 
229
 
 
230
        if self._lock_count > 1:
 
231
            self._lock_count -= 1
209
232
        else:
210
 
            mapping = self._gen_revno_map()
211
 
            self._cache_revision_id_to_revno(mapping)
212
 
        # TODO: jam 20070417 Since this is being cached, should we be returning
213
 
        #       a copy?
214
 
        # I would rather not, and instead just declare that users should not
215
 
        # modify the return value.
216
 
        return mapping
217
 
 
218
 
    def _gen_revno_map(self):
219
 
        """Create a new mapping from revision ids to dotted revnos.
220
 
 
221
 
        Dotted revnos are generated based on the current tip in the revision
222
 
        history.
223
 
        This is the worker function for get_revision_id_to_revno_map, which
224
 
        just caches the return value.
225
 
 
226
 
        :return: A dictionary mapping revision_id => dotted revno.
227
 
        """
228
 
        last_revision = self.last_revision()
229
 
        revision_graph = self.repository.get_revision_graph(last_revision)
230
 
        merge_sorted_revisions = tsort.merge_sort(
231
 
            revision_graph,
232
 
            last_revision,
233
 
            None,
234
 
            generate_revno=True)
235
 
        revision_id_to_revno = dict((rev_id, revno)
236
 
                                    for seq_num, rev_id, depth, revno, end_of_merge
237
 
                                     in merge_sorted_revisions)
238
 
        return revision_id_to_revno
239
 
 
240
 
    def leave_lock_in_place(self):
241
 
        """Tell this branch object not to release the physical lock when this
242
 
        object is unlocked.
243
 
        
244
 
        If lock_write doesn't return a token, then this method is not supported.
245
 
        """
246
 
        self.control_files.leave_in_place()
247
 
 
248
 
    def dont_leave_lock_in_place(self):
249
 
        """Tell this branch object to release the physical lock when this
250
 
        object is unlocked, even if it didn't originally acquire it.
251
 
 
252
 
        If lock_write doesn't return a token, then this method is not supported.
253
 
        """
254
 
        self.control_files.dont_leave_in_place()
 
233
            self._lock.unlock()
 
234
            self._lock = None
 
235
            self._lock_mode = self._lock_count = None
 
236
 
255
237
 
256
238
    def abspath(self, name):
257
 
        """Return absolute filename for something in the branch
258
 
        
259
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
260
 
        method and not a tree method.
261
 
        """
262
 
        raise NotImplementedError(self.abspath)
263
 
 
264
 
    def bind(self, other):
265
 
        """Bind the local branch the other branch.
266
 
 
267
 
        :param other: The branch to bind to
268
 
        :type other: Branch
269
 
        """
270
 
        raise errors.UpgradeRequired(self.base)
271
 
 
272
 
    @needs_write_lock
273
 
    def fetch(self, from_branch, last_revision=None, pb=None):
274
 
        """Copy revisions from from_branch into this branch.
275
 
 
276
 
        :param from_branch: Where to copy from.
277
 
        :param last_revision: What revision to stop at (None for at the end
278
 
                              of the branch.
279
 
        :param pb: An optional progress bar to use.
280
 
 
281
 
        Returns the copied revision count and the failed revisions in a tuple:
282
 
        (copied, failures).
283
 
        """
284
 
        if self.base == from_branch.base:
285
 
            return (0, [])
286
 
        if pb is None:
287
 
            nested_pb = ui.ui_factory.nested_progress_bar()
288
 
            pb = nested_pb
289
 
        else:
290
 
            nested_pb = None
291
 
 
292
 
        from_branch.lock_read()
293
 
        try:
294
 
            if last_revision is None:
295
 
                pb.update('get source history')
296
 
                last_revision = from_branch.last_revision()
297
 
                if last_revision is None:
298
 
                    last_revision = _mod_revision.NULL_REVISION
299
 
            return self.repository.fetch(from_branch.repository,
300
 
                                         revision_id=last_revision,
301
 
                                         pb=nested_pb)
302
 
        finally:
303
 
            if nested_pb is not None:
304
 
                nested_pb.finished()
305
 
            from_branch.unlock()
306
 
 
307
 
    def get_bound_location(self):
308
 
        """Return the URL of the branch we are bound to.
309
 
 
310
 
        Older format branches cannot bind, please be sure to use a metadir
311
 
        branch.
312
 
        """
313
 
        return None
314
 
    
315
 
    def get_old_bound_location(self):
316
 
        """Return the URL of the branch we used to be bound to
317
 
        """
318
 
        raise errors.UpgradeRequired(self.base)
319
 
 
320
 
    def get_commit_builder(self, parents, config=None, timestamp=None, 
321
 
                           timezone=None, committer=None, revprops=None, 
322
 
                           revision_id=None):
323
 
        """Obtain a CommitBuilder for this branch.
324
 
        
325
 
        :param parents: Revision ids of the parents of the new revision.
326
 
        :param config: Optional configuration to use.
327
 
        :param timestamp: Optional timestamp recorded for commit.
328
 
        :param timezone: Optional timezone for timestamp.
329
 
        :param committer: Optional committer to set for commit.
330
 
        :param revprops: Optional dictionary of revision properties.
331
 
        :param revision_id: Optional revision id.
332
 
        """
333
 
 
334
 
        if config is None:
335
 
            config = self.get_config()
336
 
        
337
 
        return self.repository.get_commit_builder(self, parents, config,
338
 
            timestamp, timezone, committer, revprops, revision_id)
339
 
 
340
 
    def get_master_branch(self):
341
 
        """Return the branch we are bound to.
342
 
        
343
 
        :return: Either a Branch, or None
344
 
        """
345
 
        return None
346
 
 
347
 
    def get_revision_delta(self, revno):
348
 
        """Return the delta for one revision.
349
 
 
350
 
        The delta is relative to its mainline predecessor, or the
351
 
        empty tree for revision 1.
352
 
        """
353
 
        assert isinstance(revno, int)
354
 
        rh = self.revision_history()
355
 
        if not (1 <= revno <= len(rh)):
356
 
            raise InvalidRevisionNumber(revno)
357
 
        return self.repository.get_revision_delta(rh[revno-1])
358
 
 
359
 
    @deprecated_method(zero_sixteen)
360
 
    def get_root_id(self):
361
 
        """Return the id of this branches root
362
 
 
363
 
        Deprecated: branches don't have root ids-- trees do.
364
 
        Use basis_tree().get_root_id() instead.
365
 
        """
366
 
        raise NotImplementedError(self.get_root_id)
367
 
 
368
 
    def print_file(self, file, revision_id):
 
239
        """Return absolute filename for something in the branch"""
 
240
        return os.path.join(self.base, name)
 
241
 
 
242
 
 
243
    def relpath(self, path):
 
244
        """Return path relative to this branch of something inside it.
 
245
 
 
246
        Raises an error if path is not in this branch."""
 
247
        return _relpath(self.base, path)
 
248
 
 
249
 
 
250
    def controlfilename(self, file_or_path):
 
251
        """Return location relative to branch."""
 
252
        if isinstance(file_or_path, types.StringTypes):
 
253
            file_or_path = [file_or_path]
 
254
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
255
 
 
256
 
 
257
    def controlfile(self, file_or_path, mode='r'):
 
258
        """Open a control file for this branch.
 
259
 
 
260
        There are two classes of file in the control directory: text
 
261
        and binary.  binary files are untranslated byte streams.  Text
 
262
        control files are stored with Unix newlines and in UTF-8, even
 
263
        if the platform or locale defaults are different.
 
264
 
 
265
        Controlfiles should almost never be opened in write mode but
 
266
        rather should be atomically copied and replaced using atomicfile.
 
267
        """
 
268
 
 
269
        fn = self.controlfilename(file_or_path)
 
270
 
 
271
        if mode == 'rb' or mode == 'wb':
 
272
            return file(fn, mode)
 
273
        elif mode == 'r' or mode == 'w':
 
274
            # open in binary mode anyhow so there's no newline translation;
 
275
            # codecs uses line buffering by default; don't want that.
 
276
            import codecs
 
277
            return codecs.open(fn, mode + 'b', 'utf-8',
 
278
                               buffering=60000)
 
279
        else:
 
280
            raise BzrError("invalid controlfile mode %r" % mode)
 
281
 
 
282
 
 
283
 
 
284
    def _make_control(self):
 
285
        os.mkdir(self.controlfilename([]))
 
286
        self.controlfile('README', 'w').write(
 
287
            "This is a Bazaar-NG control directory.\n"
 
288
            "Do not change any files in this directory.\n")
 
289
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
 
290
        for d in ('text-store', 'inventory-store', 'revision-store'):
 
291
            os.mkdir(self.controlfilename(d))
 
292
        for f in ('revision-history', 'merged-patches',
 
293
                  'pending-merged-patches', 'branch-name',
 
294
                  'branch-lock'):
 
295
            self.controlfile(f, 'w').write('')
 
296
        mutter('created control directory in ' + self.base)
 
297
        Inventory().write_xml(self.controlfile('inventory','w'))
 
298
 
 
299
 
 
300
    def _check_format(self):
 
301
        """Check this branch format is supported.
 
302
 
 
303
        The current tool only supports the current unstable format.
 
304
 
 
305
        In the future, we might need different in-memory Branch
 
306
        classes to support downlevel branches.  But not yet.
 
307
        """
 
308
        # This ignores newlines so that we can open branches created
 
309
        # on Windows from Linux and so on.  I think it might be better
 
310
        # to always make all internal files in unix format.
 
311
        fmt = self.controlfile('branch-format', 'r').read()
 
312
        fmt.replace('\r\n', '')
 
313
        if fmt != BZR_BRANCH_FORMAT:
 
314
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
315
                           ['use a different bzr version',
 
316
                            'or remove the .bzr directory and "bzr init" again'])
 
317
 
 
318
 
 
319
 
 
320
    def read_working_inventory(self):
 
321
        """Read the working inventory."""
 
322
        before = time.time()
 
323
        # ElementTree does its own conversion from UTF-8, so open in
 
324
        # binary.
 
325
        self.lock_read()
 
326
        try:
 
327
            inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
 
328
            mutter("loaded inventory of %d items in %f"
 
329
                   % (len(inv), time.time() - before))
 
330
            return inv
 
331
        finally:
 
332
            self.unlock()
 
333
            
 
334
 
 
335
    def _write_inventory(self, inv):
 
336
        """Update the working inventory.
 
337
 
 
338
        That is to say, the inventory describing changes underway, that
 
339
        will be committed to the next revision.
 
340
        """
 
341
        ## TODO: factor out to atomicfile?  is rename safe on windows?
 
342
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
 
343
        tmpfname = self.controlfilename('inventory.tmp')
 
344
        tmpf = file(tmpfname, 'wb')
 
345
        inv.write_xml(tmpf)
 
346
        tmpf.close()
 
347
        inv_fname = self.controlfilename('inventory')
 
348
        if sys.platform == 'win32':
 
349
            os.remove(inv_fname)
 
350
        os.rename(tmpfname, inv_fname)
 
351
        mutter('wrote working inventory')
 
352
            
 
353
 
 
354
    inventory = property(read_working_inventory, _write_inventory, None,
 
355
                         """Inventory for the working copy.""")
 
356
 
 
357
 
 
358
    def add(self, files, verbose=False, ids=None):
 
359
        """Make files versioned.
 
360
 
 
361
        Note that the command line normally calls smart_add instead.
 
362
 
 
363
        This puts the files in the Added state, so that they will be
 
364
        recorded by the next commit.
 
365
 
 
366
        files
 
367
            List of paths to add, relative to the base of the tree.
 
368
 
 
369
        ids
 
370
            If set, use these instead of automatically generated ids.
 
371
            Must be the same length as the list of files, but may
 
372
            contain None for ids that are to be autogenerated.
 
373
 
 
374
        TODO: Perhaps have an option to add the ids even if the files do
 
375
              not (yet) exist.
 
376
 
 
377
        TODO: Perhaps return the ids of the files?  But then again it
 
378
              is easy to retrieve them if they're needed.
 
379
 
 
380
        TODO: Adding a directory should optionally recurse down and
 
381
              add all non-ignored children.  Perhaps do that in a
 
382
              higher-level method.
 
383
        """
 
384
        # TODO: Re-adding a file that is removed in the working copy
 
385
        # should probably put it back with the previous ID.
 
386
        if isinstance(files, types.StringTypes):
 
387
            assert(ids is None or isinstance(ids, types.StringTypes))
 
388
            files = [files]
 
389
            if ids is not None:
 
390
                ids = [ids]
 
391
 
 
392
        if ids is None:
 
393
            ids = [None] * len(files)
 
394
        else:
 
395
            assert(len(ids) == len(files))
 
396
 
 
397
        self.lock_write()
 
398
        try:
 
399
            inv = self.read_working_inventory()
 
400
            for f,file_id in zip(files, ids):
 
401
                if is_control_file(f):
 
402
                    raise BzrError("cannot add control file %s" % quotefn(f))
 
403
 
 
404
                fp = splitpath(f)
 
405
 
 
406
                if len(fp) == 0:
 
407
                    raise BzrError("cannot add top-level %r" % f)
 
408
 
 
409
                fullpath = os.path.normpath(self.abspath(f))
 
410
 
 
411
                try:
 
412
                    kind = file_kind(fullpath)
 
413
                except OSError:
 
414
                    # maybe something better?
 
415
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
416
 
 
417
                if kind != 'file' and kind != 'directory':
 
418
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
419
 
 
420
                if file_id is None:
 
421
                    file_id = gen_file_id(f)
 
422
                inv.add_path(f, kind=kind, file_id=file_id)
 
423
 
 
424
                if verbose:
 
425
                    show_status('A', kind, quotefn(f))
 
426
 
 
427
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
428
 
 
429
            self._write_inventory(inv)
 
430
        finally:
 
431
            self.unlock()
 
432
            
 
433
 
 
434
    def print_file(self, file, revno):
369
435
        """Print `file` to stdout."""
370
 
        raise NotImplementedError(self.print_file)
371
 
 
372
 
    def append_revision(self, *revision_ids):
373
 
        raise NotImplementedError(self.append_revision)
374
 
 
375
 
    def set_revision_history(self, rev_history):
376
 
        raise NotImplementedError(self.set_revision_history)
377
 
 
378
 
    def _cache_revision_history(self, rev_history):
379
 
        """Set the cached revision history to rev_history.
380
 
 
381
 
        The revision_history method will use this cache to avoid regenerating
382
 
        the revision history.
383
 
 
384
 
        This API is semi-public; it only for use by subclasses, all other code
385
 
        should consider it to be private.
386
 
        """
387
 
        self._revision_history_cache = rev_history
388
 
 
389
 
    def _cache_revision_id_to_revno(self, revision_id_to_revno):
390
 
        """Set the cached revision_id => revno map to revision_id_to_revno.
391
 
 
392
 
        This API is semi-public; it only for use by subclasses, all other code
393
 
        should consider it to be private.
394
 
        """
395
 
        self._revision_id_to_revno_cache = revision_id_to_revno
396
 
 
397
 
    def _clear_cached_state(self):
398
 
        """Clear any cached data on this branch, e.g. cached revision history.
399
 
 
400
 
        This means the next call to revision_history will need to call
401
 
        _gen_revision_history.
402
 
 
403
 
        This API is semi-public; it only for use by subclasses, all other code
404
 
        should consider it to be private.
405
 
        """
406
 
        self._revision_history_cache = None
407
 
        self._revision_id_to_revno_cache = None
408
 
 
409
 
    def _gen_revision_history(self):
410
 
        """Return sequence of revision hashes on to this branch.
411
 
        
412
 
        Unlike revision_history, this method always regenerates or rereads the
413
 
        revision history, i.e. it does not cache the result, so repeated calls
414
 
        may be expensive.
415
 
 
416
 
        Concrete subclasses should override this instead of revision_history so
417
 
        that subclasses do not need to deal with caching logic.
418
 
        
419
 
        This API is semi-public; it only for use by subclasses, all other code
420
 
        should consider it to be private.
421
 
        """
422
 
        raise NotImplementedError(self._gen_revision_history)
423
 
 
424
 
    @needs_read_lock
 
436
        self.lock_read()
 
437
        try:
 
438
            tree = self.revision_tree(self.lookup_revision(revno))
 
439
            # use inventory as it was in that revision
 
440
            file_id = tree.inventory.path2id(file)
 
441
            if not file_id:
 
442
                raise BzrError("%r is not present in revision %d" % (file, revno))
 
443
            tree.print_file(file_id)
 
444
        finally:
 
445
            self.unlock()
 
446
 
 
447
 
 
448
    def remove(self, files, verbose=False):
 
449
        """Mark nominated files for removal from the inventory.
 
450
 
 
451
        This does not remove their text.  This does not run on 
 
452
 
 
453
        TODO: Refuse to remove modified files unless --force is given?
 
454
 
 
455
        TODO: Do something useful with directories.
 
456
 
 
457
        TODO: Should this remove the text or not?  Tough call; not
 
458
        removing may be useful and the user can just use use rm, and
 
459
        is the opposite of add.  Removing it is consistent with most
 
460
        other tools.  Maybe an option.
 
461
        """
 
462
        ## TODO: Normalize names
 
463
        ## TODO: Remove nested loops; better scalability
 
464
        if isinstance(files, types.StringTypes):
 
465
            files = [files]
 
466
 
 
467
        self.lock_write()
 
468
 
 
469
        try:
 
470
            tree = self.working_tree()
 
471
            inv = tree.inventory
 
472
 
 
473
            # do this before any modifications
 
474
            for f in files:
 
475
                fid = inv.path2id(f)
 
476
                if not fid:
 
477
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
 
478
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
 
479
                if verbose:
 
480
                    # having remove it, it must be either ignored or unknown
 
481
                    if tree.is_ignored(f):
 
482
                        new_status = 'I'
 
483
                    else:
 
484
                        new_status = '?'
 
485
                    show_status(new_status, inv[fid].kind, quotefn(f))
 
486
                del inv[fid]
 
487
 
 
488
            self._write_inventory(inv)
 
489
        finally:
 
490
            self.unlock()
 
491
 
 
492
 
 
493
    # FIXME: this doesn't need to be a branch method
 
494
    def set_inventory(self, new_inventory_list):
 
495
        inv = Inventory()
 
496
        for path, file_id, parent, kind in new_inventory_list:
 
497
            name = os.path.basename(path)
 
498
            if name == "":
 
499
                continue
 
500
            inv.add(InventoryEntry(file_id, name, kind, parent))
 
501
        self._write_inventory(inv)
 
502
 
 
503
 
 
504
    def unknowns(self):
 
505
        """Return all unknown files.
 
506
 
 
507
        These are files in the working directory that are not versioned or
 
508
        control files or ignored.
 
509
        
 
510
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
511
        >>> list(b.unknowns())
 
512
        ['foo']
 
513
        >>> b.add('foo')
 
514
        >>> list(b.unknowns())
 
515
        []
 
516
        >>> b.remove('foo')
 
517
        >>> list(b.unknowns())
 
518
        ['foo']
 
519
        """
 
520
        return self.working_tree().unknowns()
 
521
 
 
522
 
 
523
    def append_revision(self, revision_id):
 
524
        mutter("add {%s} to revision-history" % revision_id)
 
525
        rev_history = self.revision_history()
 
526
 
 
527
        tmprhname = self.controlfilename('revision-history.tmp')
 
528
        rhname = self.controlfilename('revision-history')
 
529
        
 
530
        f = file(tmprhname, 'wt')
 
531
        rev_history.append(revision_id)
 
532
        f.write('\n'.join(rev_history))
 
533
        f.write('\n')
 
534
        f.close()
 
535
 
 
536
        if sys.platform == 'win32':
 
537
            os.remove(rhname)
 
538
        os.rename(tmprhname, rhname)
 
539
        
 
540
 
 
541
 
 
542
    def get_revision(self, revision_id):
 
543
        """Return the Revision object for a named revision"""
 
544
        if not revision_id or not isinstance(revision_id, basestring):
 
545
            raise ValueError('invalid revision-id: %r' % revision_id)
 
546
        r = Revision.read_xml(self.revision_store[revision_id])
 
547
        assert r.revision_id == revision_id
 
548
        return r
 
549
 
 
550
    def get_revision_sha1(self, revision_id):
 
551
        """Hash the stored value of a revision, and return it."""
 
552
        # In the future, revision entries will be signed. At that
 
553
        # point, it is probably best *not* to include the signature
 
554
        # in the revision hash. Because that lets you re-sign
 
555
        # the revision, (add signatures/remove signatures) and still
 
556
        # have all hash pointers stay consistent.
 
557
        # But for now, just hash the contents.
 
558
        return sha_file(self.revision_store[revision_id])
 
559
 
 
560
 
 
561
    def get_inventory(self, inventory_id):
 
562
        """Get Inventory object by hash.
 
563
 
 
564
        TODO: Perhaps for this and similar methods, take a revision
 
565
               parameter which can be either an integer revno or a
 
566
               string hash."""
 
567
        i = Inventory.read_xml(self.inventory_store[inventory_id])
 
568
        return i
 
569
 
 
570
    def get_inventory_sha1(self, inventory_id):
 
571
        """Return the sha1 hash of the inventory entry
 
572
        """
 
573
        return sha_file(self.inventory_store[inventory_id])
 
574
 
 
575
 
 
576
    def get_revision_inventory(self, revision_id):
 
577
        """Return inventory of a past revision."""
 
578
        if revision_id == None:
 
579
            return Inventory()
 
580
        else:
 
581
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
 
582
 
 
583
 
425
584
    def revision_history(self):
426
585
        """Return sequence of revision hashes on to this branch.
427
 
        
428
 
        This method will cache the revision history for as long as it is safe to
429
 
        do so.
430
 
        """
431
 
        if self._revision_history_cache is not None:
432
 
            history = self._revision_history_cache
 
586
 
 
587
        >>> ScratchBranch().revision_history()
 
588
        []
 
589
        """
 
590
        self.lock_read()
 
591
        try:
 
592
            return [l.rstrip('\r\n') for l in
 
593
                    self.controlfile('revision-history', 'r').readlines()]
 
594
        finally:
 
595
            self.unlock()
 
596
 
 
597
 
 
598
    def common_ancestor(self, other, self_revno=None, other_revno=None):
 
599
        """
 
600
        >>> import commit
 
601
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
 
602
        >>> sb.common_ancestor(sb) == (None, None)
 
603
        True
 
604
        >>> commit.commit(sb, "Committing first revision", verbose=False)
 
605
        >>> sb.common_ancestor(sb)[0]
 
606
        1
 
607
        >>> clone = sb.clone()
 
608
        >>> commit.commit(sb, "Committing second revision", verbose=False)
 
609
        >>> sb.common_ancestor(sb)[0]
 
610
        2
 
611
        >>> sb.common_ancestor(clone)[0]
 
612
        1
 
613
        >>> commit.commit(clone, "Committing divergent second revision", 
 
614
        ...               verbose=False)
 
615
        >>> sb.common_ancestor(clone)[0]
 
616
        1
 
617
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
 
618
        True
 
619
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
 
620
        True
 
621
        >>> clone2 = sb.clone()
 
622
        >>> sb.common_ancestor(clone2)[0]
 
623
        2
 
624
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
 
625
        1
 
626
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
 
627
        1
 
628
        """
 
629
        my_history = self.revision_history()
 
630
        other_history = other.revision_history()
 
631
        if self_revno is None:
 
632
            self_revno = len(my_history)
 
633
        if other_revno is None:
 
634
            other_revno = len(other_history)
 
635
        indices = range(min((self_revno, other_revno)))
 
636
        indices.reverse()
 
637
        for r in indices:
 
638
            if my_history[r] == other_history[r]:
 
639
                return r+1, my_history[r]
 
640
        return None, None
 
641
 
 
642
    def enum_history(self, direction):
 
643
        """Return (revno, revision_id) for history of branch.
 
644
 
 
645
        direction
 
646
            'forward' is from earliest to latest
 
647
            'reverse' is from latest to earliest
 
648
        """
 
649
        rh = self.revision_history()
 
650
        if direction == 'forward':
 
651
            i = 1
 
652
            for rid in rh:
 
653
                yield i, rid
 
654
                i += 1
 
655
        elif direction == 'reverse':
 
656
            i = len(rh)
 
657
            while i > 0:
 
658
                yield i, rh[i-1]
 
659
                i -= 1
433
660
        else:
434
 
            history = self._gen_revision_history()
435
 
            self._cache_revision_history(history)
436
 
        return list(history)
 
661
            raise ValueError('invalid history direction', direction)
 
662
 
437
663
 
438
664
    def revno(self):
439
665
        """Return current revision number for this branch.
443
669
        """
444
670
        return len(self.revision_history())
445
671
 
446
 
    def unbind(self):
447
 
        """Older format branches cannot bind or unbind."""
448
 
        raise errors.UpgradeRequired(self.base)
449
 
 
450
 
    def set_append_revisions_only(self, enabled):
451
 
        """Older format branches are never restricted to append-only"""
452
 
        raise errors.UpgradeRequired(self.base)
453
 
 
454
 
    def last_revision(self):
455
 
        """Return last revision id, or None"""
 
672
 
 
673
    def last_patch(self):
 
674
        """Return last patch hash, or None if no history.
 
675
        """
456
676
        ph = self.revision_history()
457
677
        if ph:
458
678
            return ph[-1]
459
679
        else:
460
680
            return None
461
681
 
462
 
    def last_revision_info(self):
463
 
        """Return information about the last revision.
464
 
 
465
 
        :return: A tuple (revno, last_revision_id).
466
 
        """
467
 
        rh = self.revision_history()
468
 
        revno = len(rh)
469
 
        if revno:
470
 
            return (revno, rh[-1])
471
 
        else:
472
 
            return (0, _mod_revision.NULL_REVISION)
473
682
 
474
683
    def missing_revisions(self, other, stop_revision=None):
475
 
        """Return a list of new revisions that would perfectly fit.
476
 
        
 
684
        """
477
685
        If self and other have not diverged, return a list of the revisions
478
686
        present in other, but missing from self.
 
687
 
 
688
        >>> from bzrlib.commit import commit
 
689
        >>> bzrlib.trace.silent = True
 
690
        >>> br1 = ScratchBranch()
 
691
        >>> br2 = ScratchBranch()
 
692
        >>> br1.missing_revisions(br2)
 
693
        []
 
694
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
695
        >>> br1.missing_revisions(br2)
 
696
        [u'REVISION-ID-1']
 
697
        >>> br2.missing_revisions(br1)
 
698
        []
 
699
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
700
        >>> br1.missing_revisions(br2)
 
701
        []
 
702
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
703
        >>> br1.missing_revisions(br2)
 
704
        [u'REVISION-ID-2A']
 
705
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
706
        >>> br1.missing_revisions(br2)
 
707
        Traceback (most recent call last):
 
708
        DivergedBranches: These branches have diverged.
479
709
        """
480
710
        self_history = self.revision_history()
481
711
        self_len = len(self_history)
488
718
 
489
719
        if stop_revision is None:
490
720
            stop_revision = other_len
491
 
        else:
492
 
            assert isinstance(stop_revision, int)
493
 
            if stop_revision > other_len:
494
 
                raise errors.NoSuchRevision(self, stop_revision)
 
721
        elif stop_revision > other_len:
 
722
            raise NoSuchRevision(self, stop_revision)
 
723
        
495
724
        return other_history[self_len:stop_revision]
496
725
 
 
726
 
497
727
    def update_revisions(self, other, stop_revision=None):
498
 
        """Pull in new perfect-fit revisions.
499
 
 
500
 
        :param other: Another Branch to pull from
501
 
        :param stop_revision: Updated until the given revision
502
 
        :return: None
 
728
        """Pull in all new revisions from other branch.
 
729
        
 
730
        >>> from bzrlib.commit import commit
 
731
        >>> bzrlib.trace.silent = True
 
732
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
 
733
        >>> br1.add('foo')
 
734
        >>> br1.add('bar')
 
735
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
 
736
        >>> br2 = ScratchBranch()
 
737
        >>> br2.update_revisions(br1)
 
738
        Added 2 texts.
 
739
        Added 1 inventories.
 
740
        Added 1 revisions.
 
741
        >>> br2.revision_history()
 
742
        [u'REVISION-ID-1']
 
743
        >>> br2.update_revisions(br1)
 
744
        Added 0 texts.
 
745
        Added 0 inventories.
 
746
        Added 0 revisions.
 
747
        >>> br1.text_store.total_size() == br2.text_store.total_size()
 
748
        True
503
749
        """
504
 
        raise NotImplementedError(self.update_revisions)
505
 
 
506
 
    def revision_id_to_revno(self, revision_id):
507
 
        """Given a revision id, return its revno"""
508
 
        if revision_id is None:
509
 
            return 0
510
 
        revision_id = osutils.safe_revision_id(revision_id)
511
 
        history = self.revision_history()
512
 
        try:
513
 
            return history.index(revision_id) + 1
514
 
        except ValueError:
515
 
            raise errors.NoSuchRevision(self, revision_id)
516
 
 
517
 
    def get_rev_id(self, revno, history=None):
518
 
        """Find the revision id of the specified revno."""
 
750
        from bzrlib.progress import ProgressBar
 
751
 
 
752
        pb = ProgressBar()
 
753
 
 
754
        pb.update('comparing histories')
 
755
        revision_ids = self.missing_revisions(other, stop_revision)
 
756
        revisions = []
 
757
        needed_texts = sets.Set()
 
758
        i = 0
 
759
        for rev_id in revision_ids:
 
760
            i += 1
 
761
            pb.update('fetching revision', i, len(revision_ids))
 
762
            rev = other.get_revision(rev_id)
 
763
            revisions.append(rev)
 
764
            inv = other.get_inventory(str(rev.inventory_id))
 
765
            for key, entry in inv.iter_entries():
 
766
                if entry.text_id is None:
 
767
                    continue
 
768
                if entry.text_id not in self.text_store:
 
769
                    needed_texts.add(entry.text_id)
 
770
 
 
771
        pb.clear()
 
772
                    
 
773
        count = self.text_store.copy_multi(other.text_store, needed_texts)
 
774
        print "Added %d texts." % count 
 
775
        inventory_ids = [ f.inventory_id for f in revisions ]
 
776
        count = self.inventory_store.copy_multi(other.inventory_store, 
 
777
                                                inventory_ids)
 
778
        print "Added %d inventories." % count 
 
779
        revision_ids = [ f.revision_id for f in revisions]
 
780
        count = self.revision_store.copy_multi(other.revision_store, 
 
781
                                               revision_ids)
 
782
        for revision_id in revision_ids:
 
783
            self.append_revision(revision_id)
 
784
        print "Added %d revisions." % count
 
785
                    
 
786
        
 
787
    def commit(self, *args, **kw):
 
788
        """Deprecated"""
 
789
        from bzrlib.commit import commit
 
790
        commit(self, *args, **kw)
 
791
        
 
792
 
 
793
    def lookup_revision(self, revno):
 
794
        """Return revision hash for revision number."""
519
795
        if revno == 0:
520
796
            return None
521
 
        if history is None:
522
 
            history = self.revision_history()
523
 
        if revno <= 0 or revno > len(history):
524
 
            raise errors.NoSuchRevision(self, revno)
525
 
        return history[revno - 1]
526
 
 
527
 
    def pull(self, source, overwrite=False, stop_revision=None):
528
 
        """Mirror source into this branch.
529
 
 
530
 
        This branch is considered to be 'local', having low latency.
531
 
 
532
 
        :returns: PullResult instance
533
 
        """
534
 
        raise NotImplementedError(self.pull)
535
 
 
536
 
    def push(self, target, overwrite=False, stop_revision=None):
537
 
        """Mirror this branch into target.
538
 
 
539
 
        This branch is considered to be 'local', having low latency.
540
 
        """
541
 
        raise NotImplementedError(self.push)
 
797
 
 
798
        try:
 
799
            # list is 0-based; revisions are 1-based
 
800
            return self.revision_history()[revno-1]
 
801
        except IndexError:
 
802
            raise BzrError("no such revision %s" % revno)
 
803
 
 
804
 
 
805
    def revision_tree(self, revision_id):
 
806
        """Return Tree for a revision on this branch.
 
807
 
 
808
        `revision_id` may be None for the null revision, in which case
 
809
        an `EmptyTree` is returned."""
 
810
        # TODO: refactor this to use an existing revision object
 
811
        # so we don't need to read it in twice.
 
812
        if revision_id == None:
 
813
            return EmptyTree()
 
814
        else:
 
815
            inv = self.get_revision_inventory(revision_id)
 
816
            return RevisionTree(self.text_store, inv)
 
817
 
 
818
 
 
819
    def working_tree(self):
 
820
        """Return a `Tree` for the working copy."""
 
821
        from workingtree import WorkingTree
 
822
        return WorkingTree(self.base, self.read_working_inventory())
 
823
 
542
824
 
543
825
    def basis_tree(self):
544
 
        """Return `Tree` object for last revision."""
545
 
        return self.repository.revision_tree(self.last_revision())
 
826
        """Return `Tree` object for last revision.
 
827
 
 
828
        If there are no revisions yet, return an `EmptyTree`.
 
829
        """
 
830
        r = self.last_patch()
 
831
        if r == None:
 
832
            return EmptyTree()
 
833
        else:
 
834
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
 
835
 
 
836
 
546
837
 
547
838
    def rename_one(self, from_rel, to_rel):
548
839
        """Rename one file.
549
840
 
550
841
        This can change the directory or the filename or both.
551
842
        """
552
 
        raise NotImplementedError(self.rename_one)
 
843
        self.lock_write()
 
844
        try:
 
845
            tree = self.working_tree()
 
846
            inv = tree.inventory
 
847
            if not tree.has_filename(from_rel):
 
848
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
849
            if tree.has_filename(to_rel):
 
850
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
851
 
 
852
            file_id = inv.path2id(from_rel)
 
853
            if file_id == None:
 
854
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
855
 
 
856
            if inv.path2id(to_rel):
 
857
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
858
 
 
859
            to_dir, to_tail = os.path.split(to_rel)
 
860
            to_dir_id = inv.path2id(to_dir)
 
861
            if to_dir_id == None and to_dir != '':
 
862
                raise BzrError("can't determine destination directory id for %r" % to_dir)
 
863
 
 
864
            mutter("rename_one:")
 
865
            mutter("  file_id    {%s}" % file_id)
 
866
            mutter("  from_rel   %r" % from_rel)
 
867
            mutter("  to_rel     %r" % to_rel)
 
868
            mutter("  to_dir     %r" % to_dir)
 
869
            mutter("  to_dir_id  {%s}" % to_dir_id)
 
870
 
 
871
            inv.rename(file_id, to_dir_id, to_tail)
 
872
 
 
873
            print "%s => %s" % (from_rel, to_rel)
 
874
 
 
875
            from_abs = self.abspath(from_rel)
 
876
            to_abs = self.abspath(to_rel)
 
877
            try:
 
878
                os.rename(from_abs, to_abs)
 
879
            except OSError, e:
 
880
                raise BzrError("failed to rename %r to %r: %s"
 
881
                        % (from_abs, to_abs, e[1]),
 
882
                        ["rename rolled back"])
 
883
 
 
884
            self._write_inventory(inv)
 
885
        finally:
 
886
            self.unlock()
 
887
 
553
888
 
554
889
    def move(self, from_paths, to_name):
555
890
        """Rename files.
561
896
 
562
897
        Note that to_name is only the last component of the new name;
563
898
        this doesn't change the directory.
564
 
 
565
 
        This returns a list of (from_path, to_path) pairs for each
566
 
        entry that is moved.
567
 
        """
568
 
        raise NotImplementedError(self.move)
569
 
 
570
 
    def get_parent(self):
571
 
        """Return the parent location of the branch.
572
 
 
573
 
        This is the default location for push/pull/missing.  The usual
574
 
        pattern is that the user can override it by specifying a
575
 
        location.
576
 
        """
577
 
        raise NotImplementedError(self.get_parent)
578
 
 
579
 
    def _set_config_location(self, name, url, config=None,
580
 
                             make_relative=False):
581
 
        if config is None:
582
 
            config = self.get_config()
583
 
        if url is None:
584
 
            url = ''
585
 
        elif make_relative:
586
 
            url = urlutils.relative_url(self.base, url)
587
 
        config.set_user_option(name, url, warn_masked=True)
588
 
 
589
 
    def _get_config_location(self, name, config=None):
590
 
        if config is None:
591
 
            config = self.get_config()
592
 
        location = config.get_user_option(name)
593
 
        if location == '':
594
 
            location = None
595
 
        return location
596
 
 
597
 
    def get_submit_branch(self):
598
 
        """Return the submit location of the branch.
599
 
 
600
 
        This is the default location for bundle.  The usual
601
 
        pattern is that the user can override it by specifying a
602
 
        location.
603
 
        """
604
 
        return self.get_config().get_user_option('submit_branch')
605
 
 
606
 
    def set_submit_branch(self, location):
607
 
        """Return the submit location of the branch.
608
 
 
609
 
        This is the default location for bundle.  The usual
610
 
        pattern is that the user can override it by specifying a
611
 
        location.
612
 
        """
613
 
        self.get_config().set_user_option('submit_branch', location,
614
 
            warn_masked=True)
615
 
 
616
 
    def get_public_branch(self):
617
 
        """Return the public location of the branch.
618
 
 
619
 
        This is is used by merge directives.
620
 
        """
621
 
        return self._get_config_location('public_branch')
622
 
 
623
 
    def set_public_branch(self, location):
624
 
        """Return the submit location of the branch.
625
 
 
626
 
        This is the default location for bundle.  The usual
627
 
        pattern is that the user can override it by specifying a
628
 
        location.
629
 
        """
630
 
        self._set_config_location('public_branch', location)
631
 
 
632
 
    def get_push_location(self):
633
 
        """Return the None or the location to push this branch to."""
634
 
        push_loc = self.get_config().get_user_option('push_location')
635
 
        return push_loc
636
 
 
637
 
    def set_push_location(self, location):
638
 
        """Set a new push location for this branch."""
639
 
        raise NotImplementedError(self.set_push_location)
640
 
 
641
 
    def set_parent(self, url):
642
 
        raise NotImplementedError(self.set_parent)
643
 
 
644
 
    @needs_write_lock
645
 
    def update(self):
646
 
        """Synchronise this branch with the master branch if any. 
647
 
 
648
 
        :return: None or the last_revision pivoted out during the update.
649
 
        """
650
 
        return None
651
 
 
652
 
    def check_revno(self, revno):
653
 
        """\
654
 
        Check whether a revno corresponds to any revision.
655
 
        Zero (the NULL revision) is considered valid.
656
 
        """
657
 
        if revno != 0:
658
 
            self.check_real_revno(revno)
 
899
        """
 
900
        self.lock_write()
 
901
        try:
 
902
            ## TODO: Option to move IDs only
 
903
            assert not isinstance(from_paths, basestring)
 
904
            tree = self.working_tree()
 
905
            inv = tree.inventory
 
906
            to_abs = self.abspath(to_name)
 
907
            if not isdir(to_abs):
 
908
                raise BzrError("destination %r is not a directory" % to_abs)
 
909
            if not tree.has_filename(to_name):
 
910
                raise BzrError("destination %r not in working directory" % to_abs)
 
911
            to_dir_id = inv.path2id(to_name)
 
912
            if to_dir_id == None and to_name != '':
 
913
                raise BzrError("destination %r is not a versioned directory" % to_name)
 
914
            to_dir_ie = inv[to_dir_id]
 
915
            if to_dir_ie.kind not in ('directory', 'root_directory'):
 
916
                raise BzrError("destination %r is not a directory" % to_abs)
 
917
 
 
918
            to_idpath = inv.get_idpath(to_dir_id)
 
919
 
 
920
            for f in from_paths:
 
921
                if not tree.has_filename(f):
 
922
                    raise BzrError("%r does not exist in working tree" % f)
 
923
                f_id = inv.path2id(f)
 
924
                if f_id == None:
 
925
                    raise BzrError("%r is not versioned" % f)
 
926
                name_tail = splitpath(f)[-1]
 
927
                dest_path = appendpath(to_name, name_tail)
 
928
                if tree.has_filename(dest_path):
 
929
                    raise BzrError("destination %r already exists" % dest_path)
 
930
                if f_id in to_idpath:
 
931
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
 
932
 
 
933
            # OK, so there's a race here, it's possible that someone will
 
934
            # create a file in this interval and then the rename might be
 
935
            # left half-done.  But we should have caught most problems.
 
936
 
 
937
            for f in from_paths:
 
938
                name_tail = splitpath(f)[-1]
 
939
                dest_path = appendpath(to_name, name_tail)
 
940
                print "%s => %s" % (f, dest_path)
 
941
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
942
                try:
 
943
                    os.rename(self.abspath(f), self.abspath(dest_path))
 
944
                except OSError, e:
 
945
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
 
946
                            ["rename rolled back"])
 
947
 
 
948
            self._write_inventory(inv)
 
949
        finally:
 
950
            self.unlock()
 
951
 
 
952
 
 
953
 
 
954
class ScratchBranch(Branch):
 
955
    """Special test class: a branch that cleans up after itself.
 
956
 
 
957
    >>> b = ScratchBranch()
 
958
    >>> isdir(b.base)
 
959
    True
 
960
    >>> bd = b.base
 
961
    >>> b.destroy()
 
962
    >>> isdir(bd)
 
963
    False
 
964
    """
 
965
    def __init__(self, files=[], dirs=[], base=None):
 
966
        """Make a test branch.
 
967
 
 
968
        This creates a temporary directory and runs init-tree in it.
 
969
 
 
970
        If any files are listed, they are created in the working copy.
 
971
        """
 
972
        init = False
 
973
        if base is None:
 
974
            base = tempfile.mkdtemp()
 
975
            init = True
 
976
        Branch.__init__(self, base, init=init)
 
977
        for d in dirs:
 
978
            os.mkdir(self.abspath(d))
659
979
            
660
 
    def check_real_revno(self, revno):
661
 
        """\
662
 
        Check whether a revno corresponds to a real revision.
663
 
        Zero (the NULL revision) is considered invalid
664
 
        """
665
 
        if revno < 1 or revno > self.revno():
666
 
            raise InvalidRevisionNumber(revno)
667
 
 
668
 
    @needs_read_lock
669
 
    def clone(self, to_bzrdir, revision_id=None):
670
 
        """Clone this branch into to_bzrdir preserving all semantic values.
671
 
        
672
 
        revision_id: if not None, the revision history in the new branch will
673
 
                     be truncated to end with revision_id.
674
 
        """
675
 
        result = self._format.initialize(to_bzrdir)
676
 
        self.copy_content_into(result, revision_id=revision_id)
677
 
        return  result
678
 
 
679
 
    @needs_read_lock
680
 
    def sprout(self, to_bzrdir, revision_id=None):
681
 
        """Create a new line of development from the branch, into to_bzrdir.
682
 
        
683
 
        revision_id: if not None, the revision history in the new branch will
684
 
                     be truncated to end with revision_id.
685
 
        """
686
 
        result = self._format.initialize(to_bzrdir)
687
 
        self.copy_content_into(result, revision_id=revision_id)
688
 
        result.set_parent(self.bzrdir.root_transport.base)
689
 
        return result
690
 
 
691
 
    def _synchronize_history(self, destination, revision_id):
692
 
        """Synchronize last revision and revision history between branches.
693
 
 
694
 
        This version is most efficient when the destination is also a
695
 
        BzrBranch5, but works for BzrBranch6 as long as the revision
696
 
        history is the true lefthand parent history, and all of the revisions
697
 
        are in the destination's repository.  If not, set_revision_history
698
 
        will fail.
699
 
 
700
 
        :param destination: The branch to copy the history into
701
 
        :param revision_id: The revision-id to truncate history at.  May
702
 
          be None to copy complete history.
703
 
        """
704
 
        new_history = self.revision_history()
705
 
        if revision_id is not None:
706
 
            revision_id = osutils.safe_revision_id(revision_id)
707
 
            try:
708
 
                new_history = new_history[:new_history.index(revision_id) + 1]
709
 
            except ValueError:
710
 
                rev = self.repository.get_revision(revision_id)
711
 
                new_history = rev.get_history(self.repository)[1:]
712
 
        destination.set_revision_history(new_history)
713
 
 
714
 
    @needs_read_lock
715
 
    def copy_content_into(self, destination, revision_id=None):
716
 
        """Copy the content of self into destination.
717
 
 
718
 
        revision_id: if not None, the revision history in the new branch will
719
 
                     be truncated to end with revision_id.
720
 
        """
721
 
        self._synchronize_history(destination, revision_id)
722
 
        try:
723
 
            parent = self.get_parent()
724
 
        except errors.InaccessibleParent, e:
725
 
            mutter('parent was not accessible to copy: %s', e)
726
 
        else:
727
 
            if parent:
728
 
                destination.set_parent(parent)
729
 
        self.tags.merge_to(destination.tags)
730
 
 
731
 
    @needs_read_lock
732
 
    def check(self):
733
 
        """Check consistency of the branch.
734
 
 
735
 
        In particular this checks that revisions given in the revision-history
736
 
        do actually match up in the revision graph, and that they're all 
737
 
        present in the repository.
738
 
        
739
 
        Callers will typically also want to check the repository.
740
 
 
741
 
        :return: A BranchCheckResult.
742
 
        """
743
 
        mainline_parent_id = None
744
 
        for revision_id in self.revision_history():
745
 
            try:
746
 
                revision = self.repository.get_revision(revision_id)
747
 
            except errors.NoSuchRevision, e:
748
 
                raise errors.BzrCheckError("mainline revision {%s} not in repository"
749
 
                            % revision_id)
750
 
            # In general the first entry on the revision history has no parents.
751
 
            # But it's not illegal for it to have parents listed; this can happen
752
 
            # in imports from Arch when the parents weren't reachable.
753
 
            if mainline_parent_id is not None:
754
 
                if mainline_parent_id not in revision.parent_ids:
755
 
                    raise errors.BzrCheckError("previous revision {%s} not listed among "
756
 
                                        "parents of {%s}"
757
 
                                        % (mainline_parent_id, revision_id))
758
 
            mainline_parent_id = revision_id
759
 
        return BranchCheckResult(self)
760
 
 
761
 
    def _get_checkout_format(self):
762
 
        """Return the most suitable metadir for a checkout of this branch.
763
 
        Weaves are used if this branch's repository uses weaves.
764
 
        """
765
 
        if isinstance(self.bzrdir, bzrdir.BzrDirPreSplitOut):
766
 
            from bzrlib.repofmt import weaverepo
767
 
            format = bzrdir.BzrDirMetaFormat1()
768
 
            format.repository_format = weaverepo.RepositoryFormat7()
769
 
        else:
770
 
            format = self.repository.bzrdir.checkout_metadir()
771
 
            format.set_branch_format(self._format)
772
 
        return format
773
 
 
774
 
    def create_checkout(self, to_location, revision_id=None,
775
 
                        lightweight=False):
776
 
        """Create a checkout of a branch.
777
 
        
778
 
        :param to_location: The url to produce the checkout at
779
 
        :param revision_id: The revision to check out
780
 
        :param lightweight: If True, produce a lightweight checkout, otherwise,
781
 
        produce a bound branch (heavyweight checkout)
782
 
        :return: The tree of the created checkout
783
 
        """
784
 
        t = transport.get_transport(to_location)
785
 
        t.ensure_base()
786
 
        if lightweight:
787
 
            format = self._get_checkout_format()
788
 
            checkout = format.initialize_on_transport(t)
789
 
            BranchReferenceFormat().initialize(checkout, self)
790
 
        else:
791
 
            format = self._get_checkout_format()
792
 
            checkout_branch = bzrdir.BzrDir.create_branch_convenience(
793
 
                to_location, force_new_tree=False, format=format)
794
 
            checkout = checkout_branch.bzrdir
795
 
            checkout_branch.bind(self)
796
 
            # pull up to the specified revision_id to set the initial 
797
 
            # branch tip correctly, and seed it with history.
798
 
            checkout_branch.pull(self, stop_revision=revision_id)
799
 
        tree = checkout.create_workingtree(revision_id)
800
 
        basis_tree = tree.basis_tree()
801
 
        basis_tree.lock_read()
802
 
        try:
803
 
            for path, file_id in basis_tree.iter_references():
804
 
                reference_parent = self.reference_parent(file_id, path)
805
 
                reference_parent.create_checkout(tree.abspath(path),
806
 
                    basis_tree.get_reference_revision(file_id, path),
807
 
                    lightweight)
808
 
        finally:
809
 
            basis_tree.unlock()
810
 
        return tree
811
 
 
812
 
    def reference_parent(self, file_id, path):
813
 
        """Return the parent branch for a tree-reference file_id
814
 
        :param file_id: The file_id of the tree reference
815
 
        :param path: The path of the file_id in the tree
816
 
        :return: A branch associated with the file_id
817
 
        """
818
 
        # FIXME should provide multiple branches, based on config
819
 
        return Branch.open(self.bzrdir.root_transport.clone(path).base)
820
 
 
821
 
    def supports_tags(self):
822
 
        return self._format.supports_tags()
823
 
 
824
 
 
825
 
class BranchFormat(object):
826
 
    """An encapsulation of the initialization and open routines for a format.
827
 
 
828
 
    Formats provide three things:
829
 
     * An initialization routine,
830
 
     * a format string,
831
 
     * an open routine.
832
 
 
833
 
    Formats are placed in an dict by their format string for reference 
834
 
    during branch opening. Its not required that these be instances, they
835
 
    can be classes themselves with class methods - it simply depends on 
836
 
    whether state is needed for a given format or not.
837
 
 
838
 
    Once a format is deprecated, just deprecate the initialize and open
839
 
    methods on the format class. Do not deprecate the object, as the 
840
 
    object will be created every time regardless.
841
 
    """
842
 
 
843
 
    _default_format = None
844
 
    """The default format used for new branches."""
845
 
 
846
 
    _formats = {}
847
 
    """The known formats."""
848
 
 
849
 
    def __eq__(self, other):
850
 
        return self.__class__ is other.__class__
851
 
 
852
 
    def __ne__(self, other):
853
 
        return not (self == other)
854
 
 
855
 
    @classmethod
856
 
    def find_format(klass, a_bzrdir):
857
 
        """Return the format for the branch object in a_bzrdir."""
858
 
        try:
859
 
            transport = a_bzrdir.get_branch_transport(None)
860
 
            format_string = transport.get("format").read()
861
 
            return klass._formats[format_string]
862
 
        except NoSuchFile:
863
 
            raise NotBranchError(path=transport.base)
864
 
        except KeyError:
865
 
            raise errors.UnknownFormatError(format=format_string)
866
 
 
867
 
    @classmethod
868
 
    def get_default_format(klass):
869
 
        """Return the current default format."""
870
 
        return klass._default_format
871
 
 
872
 
    def get_reference(self, a_bzrdir):
873
 
        """Get the target reference of the branch in a_bzrdir.
874
 
 
875
 
        format probing must have been completed before calling
876
 
        this method - it is assumed that the format of the branch
877
 
        in a_bzrdir is correct.
878
 
 
879
 
        :param a_bzrdir: The bzrdir to get the branch data from.
880
 
        :return: None if the branch is not a reference branch.
881
 
        """
882
 
        return None
883
 
 
884
 
    def get_format_string(self):
885
 
        """Return the ASCII format string that identifies this format."""
886
 
        raise NotImplementedError(self.get_format_string)
887
 
 
888
 
    def get_format_description(self):
889
 
        """Return the short format description for this format."""
890
 
        raise NotImplementedError(self.get_format_description)
891
 
 
892
 
    def _initialize_helper(self, a_bzrdir, utf8_files, lock_type='metadir',
893
 
                           set_format=True):
894
 
        """Initialize a branch in a bzrdir, with specified files
895
 
 
896
 
        :param a_bzrdir: The bzrdir to initialize the branch in
897
 
        :param utf8_files: The files to create as a list of
898
 
            (filename, content) tuples
899
 
        :param set_format: If True, set the format with
900
 
            self.get_format_string.  (BzrBranch4 has its format set
901
 
            elsewhere)
902
 
        :return: a branch in this format
903
 
        """
904
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
905
 
        branch_transport = a_bzrdir.get_branch_transport(self)
906
 
        lock_map = {
907
 
            'metadir': ('lock', lockdir.LockDir),
908
 
            'branch4': ('branch-lock', lockable_files.TransportLock),
909
 
        }
910
 
        lock_name, lock_class = lock_map[lock_type]
911
 
        control_files = lockable_files.LockableFiles(branch_transport,
912
 
            lock_name, lock_class)
913
 
        control_files.create_lock()
914
 
        control_files.lock_write()
915
 
        if set_format:
916
 
            control_files.put_utf8('format', self.get_format_string())
917
 
        try:
918
 
            for file, content in utf8_files:
919
 
                control_files.put_utf8(file, content)
920
 
        finally:
921
 
            control_files.unlock()
922
 
        return self.open(a_bzrdir, _found=True)
923
 
 
924
 
    def initialize(self, a_bzrdir):
925
 
        """Create a branch of this format in a_bzrdir."""
926
 
        raise NotImplementedError(self.initialize)
927
 
 
928
 
    def is_supported(self):
929
 
        """Is this format supported?
930
 
 
931
 
        Supported formats can be initialized and opened.
932
 
        Unsupported formats may not support initialization or committing or 
933
 
        some other features depending on the reason for not being supported.
934
 
        """
935
 
        return True
936
 
 
937
 
    def open(self, a_bzrdir, _found=False):
938
 
        """Return the branch object for a_bzrdir
939
 
 
940
 
        _found is a private parameter, do not use it. It is used to indicate
941
 
               if format probing has already be done.
942
 
        """
943
 
        raise NotImplementedError(self.open)
944
 
 
945
 
    @classmethod
946
 
    def register_format(klass, format):
947
 
        klass._formats[format.get_format_string()] = format
948
 
 
949
 
    @classmethod
950
 
    def set_default_format(klass, format):
951
 
        klass._default_format = format
952
 
 
953
 
    @classmethod
954
 
    def unregister_format(klass, format):
955
 
        assert klass._formats[format.get_format_string()] is format
956
 
        del klass._formats[format.get_format_string()]
957
 
 
958
 
    def __str__(self):
959
 
        return self.get_format_string().rstrip()
960
 
 
961
 
    def supports_tags(self):
962
 
        """True if this format supports tags stored in the branch"""
963
 
        return False  # by default
964
 
 
965
 
    # XXX: Probably doesn't really belong here -- mbp 20070212
966
 
    def _initialize_control_files(self, a_bzrdir, utf8_files, lock_filename,
967
 
            lock_class):
968
 
        branch_transport = a_bzrdir.get_branch_transport(self)
969
 
        control_files = lockable_files.LockableFiles(branch_transport,
970
 
            lock_filename, lock_class)
971
 
        control_files.create_lock()
972
 
        control_files.lock_write()
973
 
        try:
974
 
            for filename, content in utf8_files:
975
 
                control_files.put_utf8(filename, content)
976
 
        finally:
977
 
            control_files.unlock()
978
 
 
979
 
 
980
 
class BranchHooks(Hooks):
981
 
    """A dictionary mapping hook name to a list of callables for branch hooks.
982
 
    
983
 
    e.g. ['set_rh'] Is the list of items to be called when the
984
 
    set_revision_history function is invoked.
985
 
    """
986
 
 
987
 
    def __init__(self):
988
 
        """Create the default hooks.
989
 
 
990
 
        These are all empty initially, because by default nothing should get
991
 
        notified.
992
 
        """
993
 
        Hooks.__init__(self)
994
 
        # Introduced in 0.15:
995
 
        # invoked whenever the revision history has been set
996
 
        # with set_revision_history. The api signature is
997
 
        # (branch, revision_history), and the branch will
998
 
        # be write-locked.
999
 
        self['set_rh'] = []
1000
 
        # invoked after a push operation completes.
1001
 
        # the api signature is
1002
 
        # (push_result)
1003
 
        # containing the members
1004
 
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
1005
 
        # where local is the local target branch or None, master is the target 
1006
 
        # master branch, and the rest should be self explanatory. The source
1007
 
        # is read locked and the target branches write locked. Source will
1008
 
        # be the local low-latency branch.
1009
 
        self['post_push'] = []
1010
 
        # invoked after a pull operation completes.
1011
 
        # the api signature is
1012
 
        # (pull_result)
1013
 
        # containing the members
1014
 
        # (source, local, master, old_revno, old_revid, new_revno, new_revid)
1015
 
        # where local is the local branch or None, master is the target 
1016
 
        # master branch, and the rest should be self explanatory. The source
1017
 
        # is read locked and the target branches write locked. The local
1018
 
        # branch is the low-latency branch.
1019
 
        self['post_pull'] = []
1020
 
        # invoked after a commit operation completes.
1021
 
        # the api signature is 
1022
 
        # (local, master, old_revno, old_revid, new_revno, new_revid)
1023
 
        # old_revid is NULL_REVISION for the first commit to a branch.
1024
 
        self['post_commit'] = []
1025
 
        # invoked after a uncommit operation completes.
1026
 
        # the api signature is
1027
 
        # (local, master, old_revno, old_revid, new_revno, new_revid) where
1028
 
        # local is the local branch or None, master is the target branch,
1029
 
        # and an empty branch recieves new_revno of 0, new_revid of None.
1030
 
        self['post_uncommit'] = []
1031
 
 
1032
 
 
1033
 
# install the default hooks into the Branch class.
1034
 
Branch.hooks = BranchHooks()
1035
 
 
1036
 
 
1037
 
class BzrBranchFormat4(BranchFormat):
1038
 
    """Bzr branch format 4.
1039
 
 
1040
 
    This format has:
1041
 
     - a revision-history file.
1042
 
     - a branch-lock lock file [ to be shared with the bzrdir ]
1043
 
    """
1044
 
 
1045
 
    def get_format_description(self):
1046
 
        """See BranchFormat.get_format_description()."""
1047
 
        return "Branch format 4"
1048
 
 
1049
 
    def initialize(self, a_bzrdir):
1050
 
        """Create a branch of this format in a_bzrdir."""
1051
 
        utf8_files = [('revision-history', ''),
1052
 
                      ('branch-name', ''),
1053
 
                      ]
1054
 
        return self._initialize_helper(a_bzrdir, utf8_files,
1055
 
                                       lock_type='branch4', set_format=False)
1056
 
 
1057
 
    def __init__(self):
1058
 
        super(BzrBranchFormat4, self).__init__()
1059
 
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
1060
 
 
1061
 
    def open(self, a_bzrdir, _found=False):
1062
 
        """Return the branch object for a_bzrdir
1063
 
 
1064
 
        _found is a private parameter, do not use it. It is used to indicate
1065
 
               if format probing has already be done.
1066
 
        """
1067
 
        if not _found:
1068
 
            # we are being called directly and must probe.
1069
 
            raise NotImplementedError
1070
 
        return BzrBranch(_format=self,
1071
 
                         _control_files=a_bzrdir._control_files,
1072
 
                         a_bzrdir=a_bzrdir,
1073
 
                         _repository=a_bzrdir.open_repository())
1074
 
 
1075
 
    def __str__(self):
1076
 
        return "Bazaar-NG branch format 4"
1077
 
 
1078
 
 
1079
 
class BzrBranchFormat5(BranchFormat):
1080
 
    """Bzr branch format 5.
1081
 
 
1082
 
    This format has:
1083
 
     - a revision-history file.
1084
 
     - a format string
1085
 
     - a lock dir guarding the branch itself
1086
 
     - all of this stored in a branch/ subdirectory
1087
 
     - works with shared repositories.
1088
 
 
1089
 
    This format is new in bzr 0.8.
1090
 
    """
1091
 
 
1092
 
    def get_format_string(self):
1093
 
        """See BranchFormat.get_format_string()."""
1094
 
        return "Bazaar-NG branch format 5\n"
1095
 
 
1096
 
    def get_format_description(self):
1097
 
        """See BranchFormat.get_format_description()."""
1098
 
        return "Branch format 5"
1099
 
        
1100
 
    def initialize(self, a_bzrdir):
1101
 
        """Create a branch of this format in a_bzrdir."""
1102
 
        utf8_files = [('revision-history', ''),
1103
 
                      ('branch-name', ''),
1104
 
                      ]
1105
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1106
 
 
1107
 
    def __init__(self):
1108
 
        super(BzrBranchFormat5, self).__init__()
1109
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1110
 
 
1111
 
    def open(self, a_bzrdir, _found=False):
1112
 
        """Return the branch object for a_bzrdir
1113
 
 
1114
 
        _found is a private parameter, do not use it. It is used to indicate
1115
 
               if format probing has already be done.
1116
 
        """
1117
 
        if not _found:
1118
 
            format = BranchFormat.find_format(a_bzrdir)
1119
 
            assert format.__class__ == self.__class__
1120
 
        try:
1121
 
            transport = a_bzrdir.get_branch_transport(None)
1122
 
            control_files = lockable_files.LockableFiles(transport, 'lock',
1123
 
                                                         lockdir.LockDir)
1124
 
            return BzrBranch5(_format=self,
1125
 
                              _control_files=control_files,
1126
 
                              a_bzrdir=a_bzrdir,
1127
 
                              _repository=a_bzrdir.find_repository())
1128
 
        except NoSuchFile:
1129
 
            raise NotBranchError(path=transport.base)
1130
 
 
1131
 
 
1132
 
class BzrBranchFormat6(BzrBranchFormat5):
1133
 
    """Branch format with last-revision
1134
 
 
1135
 
    Unlike previous formats, this has no explicit revision history. Instead,
1136
 
    this just stores the last-revision, and the left-hand history leading
1137
 
    up to there is the history.
1138
 
 
1139
 
    This format was introduced in bzr 0.15
1140
 
    """
1141
 
 
1142
 
    def get_format_string(self):
1143
 
        """See BranchFormat.get_format_string()."""
1144
 
        return "Bazaar Branch Format 6 (bzr 0.15)\n"
1145
 
 
1146
 
    def get_format_description(self):
1147
 
        """See BranchFormat.get_format_description()."""
1148
 
        return "Branch format 6"
1149
 
 
1150
 
    def initialize(self, a_bzrdir):
1151
 
        """Create a branch of this format in a_bzrdir."""
1152
 
        utf8_files = [('last-revision', '0 null:\n'),
1153
 
                      ('branch-name', ''),
1154
 
                      ('branch.conf', ''),
1155
 
                      ('tags', ''),
1156
 
                      ]
1157
 
        return self._initialize_helper(a_bzrdir, utf8_files)
1158
 
 
1159
 
    def open(self, a_bzrdir, _found=False):
1160
 
        """Return the branch object for a_bzrdir
1161
 
 
1162
 
        _found is a private parameter, do not use it. It is used to indicate
1163
 
               if format probing has already be done.
1164
 
        """
1165
 
        if not _found:
1166
 
            format = BranchFormat.find_format(a_bzrdir)
1167
 
            assert format.__class__ == self.__class__
1168
 
        transport = a_bzrdir.get_branch_transport(None)
1169
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1170
 
                                                     lockdir.LockDir)
1171
 
        return BzrBranch6(_format=self,
1172
 
                          _control_files=control_files,
1173
 
                          a_bzrdir=a_bzrdir,
1174
 
                          _repository=a_bzrdir.find_repository())
1175
 
 
1176
 
    def supports_tags(self):
1177
 
        return True
1178
 
 
1179
 
 
1180
 
class BranchReferenceFormat(BranchFormat):
1181
 
    """Bzr branch reference format.
1182
 
 
1183
 
    Branch references are used in implementing checkouts, they
1184
 
    act as an alias to the real branch which is at some other url.
1185
 
 
1186
 
    This format has:
1187
 
     - A location file
1188
 
     - a format string
1189
 
    """
1190
 
 
1191
 
    def get_format_string(self):
1192
 
        """See BranchFormat.get_format_string()."""
1193
 
        return "Bazaar-NG Branch Reference Format 1\n"
1194
 
 
1195
 
    def get_format_description(self):
1196
 
        """See BranchFormat.get_format_description()."""
1197
 
        return "Checkout reference format 1"
1198
 
        
1199
 
    def get_reference(self, a_bzrdir):
1200
 
        """See BranchFormat.get_reference()."""
1201
 
        transport = a_bzrdir.get_branch_transport(None)
1202
 
        return transport.get('location').read()
1203
 
 
1204
 
    def initialize(self, a_bzrdir, target_branch=None):
1205
 
        """Create a branch of this format in a_bzrdir."""
1206
 
        if target_branch is None:
1207
 
            # this format does not implement branch itself, thus the implicit
1208
 
            # creation contract must see it as uninitializable
1209
 
            raise errors.UninitializableFormat(self)
1210
 
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
1211
 
        branch_transport = a_bzrdir.get_branch_transport(self)
1212
 
        branch_transport.put_bytes('location',
1213
 
            target_branch.bzrdir.root_transport.base)
1214
 
        branch_transport.put_bytes('format', self.get_format_string())
1215
 
        return self.open(a_bzrdir, _found=True)
1216
 
 
1217
 
    def __init__(self):
1218
 
        super(BranchReferenceFormat, self).__init__()
1219
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1220
 
 
1221
 
    def _make_reference_clone_function(format, a_branch):
1222
 
        """Create a clone() routine for a branch dynamically."""
1223
 
        def clone(to_bzrdir, revision_id=None):
1224
 
            """See Branch.clone()."""
1225
 
            return format.initialize(to_bzrdir, a_branch)
1226
 
            # cannot obey revision_id limits when cloning a reference ...
1227
 
            # FIXME RBC 20060210 either nuke revision_id for clone, or
1228
 
            # emit some sort of warning/error to the caller ?!
1229
 
        return clone
1230
 
 
1231
 
    def open(self, a_bzrdir, _found=False, location=None):
1232
 
        """Return the branch that the branch reference in a_bzrdir points at.
1233
 
 
1234
 
        _found is a private parameter, do not use it. It is used to indicate
1235
 
               if format probing has already be done.
1236
 
        """
1237
 
        if not _found:
1238
 
            format = BranchFormat.find_format(a_bzrdir)
1239
 
            assert format.__class__ == self.__class__
1240
 
        if location is None:
1241
 
            location = self.get_reference(a_bzrdir)
1242
 
        real_bzrdir = bzrdir.BzrDir.open(location)
1243
 
        result = real_bzrdir.open_branch()
1244
 
        # this changes the behaviour of result.clone to create a new reference
1245
 
        # rather than a copy of the content of the branch.
1246
 
        # I did not use a proxy object because that needs much more extensive
1247
 
        # testing, and we are only changing one behaviour at the moment.
1248
 
        # If we decide to alter more behaviours - i.e. the implicit nickname
1249
 
        # then this should be refactored to introduce a tested proxy branch
1250
 
        # and a subclass of that for use in overriding clone() and ....
1251
 
        # - RBC 20060210
1252
 
        result.clone = self._make_reference_clone_function(result)
1253
 
        return result
1254
 
 
1255
 
 
1256
 
# formats which have no format string are not discoverable
1257
 
# and not independently creatable, so are not registered.
1258
 
__default_format = BzrBranchFormat5()
1259
 
BranchFormat.register_format(__default_format)
1260
 
BranchFormat.register_format(BranchReferenceFormat())
1261
 
BranchFormat.register_format(BzrBranchFormat6())
1262
 
BranchFormat.set_default_format(__default_format)
1263
 
_legacy_formats = [BzrBranchFormat4(),
1264
 
                   ]
1265
 
 
1266
 
class BzrBranch(Branch):
1267
 
    """A branch stored in the actual filesystem.
1268
 
 
1269
 
    Note that it's "local" in the context of the filesystem; it doesn't
1270
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
1271
 
    it's writable, and can be accessed via the normal filesystem API.
1272
 
    """
1273
 
    
1274
 
    def __init__(self, _format=None,
1275
 
                 _control_files=None, a_bzrdir=None, _repository=None):
1276
 
        """Create new branch object at a particular location."""
1277
 
        Branch.__init__(self)
1278
 
        if a_bzrdir is None:
1279
 
            raise ValueError('a_bzrdir must be supplied')
1280
 
        else:
1281
 
            self.bzrdir = a_bzrdir
1282
 
        # self._transport used to point to the directory containing the
1283
 
        # control directory, but was not used - now it's just the transport
1284
 
        # for the branch control files.  mbp 20070212
1285
 
        self._base = self.bzrdir.transport.clone('..').base
1286
 
        self._format = _format
1287
 
        if _control_files is None:
1288
 
            raise ValueError('BzrBranch _control_files is None')
1289
 
        self.control_files = _control_files
1290
 
        self._transport = _control_files._transport
1291
 
        self.repository = _repository
1292
 
 
1293
 
    def __str__(self):
1294
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
1295
 
 
1296
 
    __repr__ = __str__
1297
 
 
1298
 
    def _get_base(self):
1299
 
        """Returns the directory containing the control directory."""
1300
 
        return self._base
1301
 
 
1302
 
    base = property(_get_base, doc="The URL for the root of this branch.")
1303
 
 
1304
 
    def abspath(self, name):
1305
 
        """See Branch.abspath."""
1306
 
        return self.control_files._transport.abspath(name)
1307
 
 
1308
 
 
1309
 
    @deprecated_method(zero_sixteen)
1310
 
    @needs_read_lock
1311
 
    def get_root_id(self):
1312
 
        """See Branch.get_root_id."""
1313
 
        tree = self.repository.revision_tree(self.last_revision())
1314
 
        return tree.inventory.root.file_id
1315
 
 
1316
 
    def is_locked(self):
1317
 
        return self.control_files.is_locked()
1318
 
 
1319
 
    def lock_write(self, token=None):
1320
 
        repo_token = self.repository.lock_write()
1321
 
        try:
1322
 
            token = self.control_files.lock_write(token=token)
1323
 
        except:
1324
 
            self.repository.unlock()
1325
 
            raise
1326
 
        return token
1327
 
 
1328
 
    def lock_read(self):
1329
 
        self.repository.lock_read()
1330
 
        try:
1331
 
            self.control_files.lock_read()
1332
 
        except:
1333
 
            self.repository.unlock()
1334
 
            raise
1335
 
 
1336
 
    def unlock(self):
1337
 
        # TODO: test for failed two phase locks. This is known broken.
1338
 
        try:
1339
 
            self.control_files.unlock()
1340
 
        finally:
1341
 
            self.repository.unlock()
1342
 
        if not self.control_files.is_locked():
1343
 
            # we just released the lock
1344
 
            self._clear_cached_state()
1345
 
        
1346
 
    def peek_lock_mode(self):
1347
 
        if self.control_files._lock_count == 0:
1348
 
            return None
1349
 
        else:
1350
 
            return self.control_files._lock_mode
1351
 
 
1352
 
    def get_physical_lock_status(self):
1353
 
        return self.control_files.get_physical_lock_status()
1354
 
 
1355
 
    @needs_read_lock
1356
 
    def print_file(self, file, revision_id):
1357
 
        """See Branch.print_file."""
1358
 
        return self.repository.print_file(file, revision_id)
1359
 
 
1360
 
    @needs_write_lock
1361
 
    def append_revision(self, *revision_ids):
1362
 
        """See Branch.append_revision."""
1363
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1364
 
        for revision_id in revision_ids:
1365
 
            _mod_revision.check_not_reserved_id(revision_id)
1366
 
            mutter("add {%s} to revision-history" % revision_id)
1367
 
        rev_history = self.revision_history()
1368
 
        rev_history.extend(revision_ids)
1369
 
        self.set_revision_history(rev_history)
1370
 
 
1371
 
    def _write_revision_history(self, history):
1372
 
        """Factored out of set_revision_history.
1373
 
 
1374
 
        This performs the actual writing to disk.
1375
 
        It is intended to be called by BzrBranch5.set_revision_history."""
1376
 
        self.control_files.put_bytes(
1377
 
            'revision-history', '\n'.join(history))
1378
 
 
1379
 
    @needs_write_lock
1380
 
    def set_revision_history(self, rev_history):
1381
 
        """See Branch.set_revision_history."""
1382
 
        rev_history = [osutils.safe_revision_id(r) for r in rev_history]
1383
 
        self._clear_cached_state()
1384
 
        self._write_revision_history(rev_history)
1385
 
        self._cache_revision_history(rev_history)
1386
 
        for hook in Branch.hooks['set_rh']:
1387
 
            hook(self, rev_history)
1388
 
 
1389
 
    @needs_write_lock
1390
 
    def set_last_revision_info(self, revno, revision_id):
1391
 
        revision_id = osutils.safe_revision_id(revision_id)
1392
 
        history = self._lefthand_history(revision_id)
1393
 
        assert len(history) == revno, '%d != %d' % (len(history), revno)
1394
 
        self.set_revision_history(history)
1395
 
 
1396
 
    def _gen_revision_history(self):
1397
 
        history = self.control_files.get('revision-history').read().split('\n')
1398
 
        if history[-1:] == ['']:
1399
 
            # There shouldn't be a trailing newline, but just in case.
1400
 
            history.pop()
1401
 
        return history
1402
 
 
1403
 
    def _lefthand_history(self, revision_id, last_rev=None,
1404
 
                          other_branch=None):
1405
 
        # stop_revision must be a descendant of last_revision
1406
 
        stop_graph = self.repository.get_revision_graph(revision_id)
1407
 
        if last_rev is not None and last_rev not in stop_graph:
1408
 
            # our previous tip is not merged into stop_revision
1409
 
            raise errors.DivergedBranches(self, other_branch)
1410
 
        # make a new revision history from the graph
1411
 
        current_rev_id = revision_id
1412
 
        new_history = []
1413
 
        while current_rev_id not in (None, _mod_revision.NULL_REVISION):
1414
 
            new_history.append(current_rev_id)
1415
 
            current_rev_id_parents = stop_graph[current_rev_id]
1416
 
            try:
1417
 
                current_rev_id = current_rev_id_parents[0]
1418
 
            except IndexError:
1419
 
                current_rev_id = None
1420
 
        new_history.reverse()
1421
 
        return new_history
1422
 
 
1423
 
    @needs_write_lock
1424
 
    def generate_revision_history(self, revision_id, last_rev=None,
1425
 
        other_branch=None):
1426
 
        """Create a new revision history that will finish with revision_id.
1427
 
 
1428
 
        :param revision_id: the new tip to use.
1429
 
        :param last_rev: The previous last_revision. If not None, then this
1430
 
            must be a ancestory of revision_id, or DivergedBranches is raised.
1431
 
        :param other_branch: The other branch that DivergedBranches should
1432
 
            raise with respect to.
1433
 
        """
1434
 
        revision_id = osutils.safe_revision_id(revision_id)
1435
 
        self.set_revision_history(self._lefthand_history(revision_id,
1436
 
            last_rev, other_branch))
1437
 
 
1438
 
    @needs_write_lock
1439
 
    def update_revisions(self, other, stop_revision=None):
1440
 
        """See Branch.update_revisions."""
1441
 
        other.lock_read()
1442
 
        try:
1443
 
            if stop_revision is None:
1444
 
                stop_revision = other.last_revision()
1445
 
                if stop_revision is None:
1446
 
                    # if there are no commits, we're done.
1447
 
                    return
1448
 
            else:
1449
 
                stop_revision = osutils.safe_revision_id(stop_revision)
1450
 
            # whats the current last revision, before we fetch [and change it
1451
 
            # possibly]
1452
 
            last_rev = self.last_revision()
1453
 
            # we fetch here regardless of whether we need to so that we pickup
1454
 
            # filled in ghosts.
1455
 
            self.fetch(other, stop_revision)
1456
 
            my_ancestry = self.repository.get_ancestry(last_rev,
1457
 
                                                       topo_sorted=False)
1458
 
            if stop_revision in my_ancestry:
1459
 
                # last_revision is a descendant of stop_revision
1460
 
                return
1461
 
            self.generate_revision_history(stop_revision, last_rev=last_rev,
1462
 
                other_branch=other)
1463
 
        finally:
1464
 
            other.unlock()
1465
 
 
1466
 
    def basis_tree(self):
1467
 
        """See Branch.basis_tree."""
1468
 
        return self.repository.revision_tree(self.last_revision())
1469
 
 
1470
 
    @deprecated_method(zero_eight)
1471
 
    def working_tree(self):
1472
 
        """Create a Working tree object for this branch."""
1473
 
 
1474
 
        from bzrlib.transport.local import LocalTransport
1475
 
        if (self.base.find('://') != -1 or 
1476
 
            not isinstance(self._transport, LocalTransport)):
1477
 
            raise NoWorkingTree(self.base)
1478
 
        return self.bzrdir.open_workingtree()
1479
 
 
1480
 
    @needs_write_lock
1481
 
    def pull(self, source, overwrite=False, stop_revision=None,
1482
 
             _hook_master=None, run_hooks=True):
1483
 
        """See Branch.pull.
1484
 
 
1485
 
        :param _hook_master: Private parameter - set the branch to 
1486
 
            be supplied as the master to push hooks.
1487
 
        :param run_hooks: Private parameter - if false, this branch
1488
 
            is being called because it's the master of the primary branch,
1489
 
            so it should not run its hooks.
1490
 
        """
1491
 
        result = PullResult()
1492
 
        result.source_branch = source
1493
 
        result.target_branch = self
1494
 
        source.lock_read()
1495
 
        try:
1496
 
            result.old_revno, result.old_revid = self.last_revision_info()
1497
 
            try:
1498
 
                self.update_revisions(source, stop_revision)
1499
 
            except DivergedBranches:
1500
 
                if not overwrite:
1501
 
                    raise
1502
 
            if overwrite:
1503
 
                if stop_revision is None:
1504
 
                    stop_revision = source.last_revision()
1505
 
                self.generate_revision_history(stop_revision)
1506
 
            result.tag_conflicts = source.tags.merge_to(self.tags)
1507
 
            result.new_revno, result.new_revid = self.last_revision_info()
1508
 
            if _hook_master:
1509
 
                result.master_branch = _hook_master
1510
 
                result.local_branch = self
1511
 
            else:
1512
 
                result.master_branch = self
1513
 
                result.local_branch = None
1514
 
            if run_hooks:
1515
 
                for hook in Branch.hooks['post_pull']:
1516
 
                    hook(result)
1517
 
        finally:
1518
 
            source.unlock()
1519
 
        return result
1520
 
 
1521
 
    def _get_parent_location(self):
1522
 
        _locs = ['parent', 'pull', 'x-pull']
1523
 
        for l in _locs:
1524
 
            try:
1525
 
                return self.control_files.get(l).read().strip('\n')
1526
 
            except NoSuchFile:
1527
 
                pass
1528
 
        return None
1529
 
 
1530
 
    @needs_read_lock
1531
 
    def push(self, target, overwrite=False, stop_revision=None,
1532
 
             _override_hook_source_branch=None):
1533
 
        """See Branch.push.
1534
 
 
1535
 
        This is the basic concrete implementation of push()
1536
 
 
1537
 
        :param _override_hook_source_branch: If specified, run
1538
 
        the hooks passing this Branch as the source, rather than self.  
1539
 
        This is for use of RemoteBranch, where push is delegated to the
1540
 
        underlying vfs-based Branch. 
1541
 
        """
1542
 
        # TODO: Public option to disable running hooks - should be trivial but
1543
 
        # needs tests.
1544
 
        target.lock_write()
1545
 
        try:
1546
 
            result = self._push_with_bound_branches(target, overwrite,
1547
 
                    stop_revision,
1548
 
                    _override_hook_source_branch=_override_hook_source_branch)
1549
 
            return result
1550
 
        finally:
1551
 
            target.unlock()
1552
 
 
1553
 
    def _push_with_bound_branches(self, target, overwrite,
1554
 
            stop_revision,
1555
 
            _override_hook_source_branch=None):
1556
 
        """Push from self into target, and into target's master if any.
1557
 
        
1558
 
        This is on the base BzrBranch class even though it doesn't support 
1559
 
        bound branches because the *target* might be bound.
1560
 
        """
1561
 
        def _run_hooks():
1562
 
            if _override_hook_source_branch:
1563
 
                result.source_branch = _override_hook_source_branch
1564
 
            for hook in Branch.hooks['post_push']:
1565
 
                hook(result)
1566
 
 
1567
 
        bound_location = target.get_bound_location()
1568
 
        if bound_location and target.base != bound_location:
1569
 
            # there is a master branch.
1570
 
            #
1571
 
            # XXX: Why the second check?  Is it even supported for a branch to
1572
 
            # be bound to itself? -- mbp 20070507
1573
 
            master_branch = target.get_master_branch()
1574
 
            master_branch.lock_write()
1575
 
            try:
1576
 
                # push into the master from this branch.
1577
 
                self._basic_push(master_branch, overwrite, stop_revision)
1578
 
                # and push into the target branch from this. Note that we push from
1579
 
                # this branch again, because its considered the highest bandwidth
1580
 
                # repository.
1581
 
                result = self._basic_push(target, overwrite, stop_revision)
1582
 
                result.master_branch = master_branch
1583
 
                result.local_branch = target
1584
 
                _run_hooks()
1585
 
                return result
1586
 
            finally:
1587
 
                master_branch.unlock()
1588
 
        else:
1589
 
            # no master branch
1590
 
            result = self._basic_push(target, overwrite, stop_revision)
1591
 
            # TODO: Why set master_branch and local_branch if there's no
1592
 
            # binding?  Maybe cleaner to just leave them unset? -- mbp
1593
 
            # 20070504
1594
 
            result.master_branch = target
1595
 
            result.local_branch = None
1596
 
            _run_hooks()
1597
 
            return result
1598
 
 
1599
 
    def _basic_push(self, target, overwrite, stop_revision):
1600
 
        """Basic implementation of push without bound branches or hooks.
1601
 
 
1602
 
        Must be called with self read locked and target write locked.
1603
 
        """
1604
 
        result = PushResult()
1605
 
        result.source_branch = self
1606
 
        result.target_branch = target
1607
 
        result.old_revno, result.old_revid = target.last_revision_info()
1608
 
        try:
1609
 
            target.update_revisions(self, stop_revision)
1610
 
        except DivergedBranches:
1611
 
            if not overwrite:
1612
 
                raise
1613
 
        if overwrite:
1614
 
            target.set_revision_history(self.revision_history())
1615
 
        result.tag_conflicts = self.tags.merge_to(target.tags)
1616
 
        result.new_revno, result.new_revid = target.last_revision_info()
1617
 
        return result
1618
 
 
1619
 
    def get_parent(self):
1620
 
        """See Branch.get_parent."""
1621
 
 
1622
 
        assert self.base[-1] == '/'
1623
 
        parent = self._get_parent_location()
1624
 
        if parent is None:
1625
 
            return parent
1626
 
        # This is an old-format absolute path to a local branch
1627
 
        # turn it into a url
1628
 
        if parent.startswith('/'):
1629
 
            parent = urlutils.local_path_to_url(parent.decode('utf8'))
1630
 
        try:
1631
 
            return urlutils.join(self.base[:-1], parent)
1632
 
        except errors.InvalidURLJoin, e:
1633
 
            raise errors.InaccessibleParent(parent, self.base)
1634
 
 
1635
 
    def set_push_location(self, location):
1636
 
        """See Branch.set_push_location."""
1637
 
        self.get_config().set_user_option(
1638
 
            'push_location', location,
1639
 
            store=_mod_config.STORE_LOCATION_NORECURSE)
1640
 
 
1641
 
    @needs_write_lock
1642
 
    def set_parent(self, url):
1643
 
        """See Branch.set_parent."""
1644
 
        # TODO: Maybe delete old location files?
1645
 
        # URLs should never be unicode, even on the local fs,
1646
 
        # FIXUP this and get_parent in a future branch format bump:
1647
 
        # read and rewrite the file, and have the new format code read
1648
 
        # using .get not .get_utf8. RBC 20060125
1649
 
        if url is not None:
1650
 
            if isinstance(url, unicode):
1651
 
                try: 
1652
 
                    url = url.encode('ascii')
1653
 
                except UnicodeEncodeError:
1654
 
                    raise errors.InvalidURL(url,
1655
 
                        "Urls must be 7-bit ascii, "
1656
 
                        "use bzrlib.urlutils.escape")
1657
 
            url = urlutils.relative_url(self.base, url)
1658
 
        self._set_parent_location(url)
1659
 
 
1660
 
    def _set_parent_location(self, url):
1661
 
        if url is None:
1662
 
            self.control_files._transport.delete('parent')
1663
 
        else:
1664
 
            assert isinstance(url, str)
1665
 
            self.control_files.put_bytes('parent', url + '\n')
1666
 
 
1667
 
    @deprecated_function(zero_nine)
1668
 
    def tree_config(self):
1669
 
        """DEPRECATED; call get_config instead.  
1670
 
        TreeConfig has become part of BranchConfig."""
1671
 
        return TreeConfig(self)
1672
 
 
1673
 
 
1674
 
class BzrBranch5(BzrBranch):
1675
 
    """A format 5 branch. This supports new features over plan branches.
1676
 
 
1677
 
    It has support for a master_branch which is the data for bound branches.
1678
 
    """
1679
 
 
1680
 
    def __init__(self,
1681
 
                 _format,
1682
 
                 _control_files,
1683
 
                 a_bzrdir,
1684
 
                 _repository):
1685
 
        super(BzrBranch5, self).__init__(_format=_format,
1686
 
                                         _control_files=_control_files,
1687
 
                                         a_bzrdir=a_bzrdir,
1688
 
                                         _repository=_repository)
1689
 
        
1690
 
    @needs_write_lock
1691
 
    def pull(self, source, overwrite=False, stop_revision=None,
1692
 
             run_hooks=True):
1693
 
        """Pull from source into self, updating my master if any.
1694
 
        
1695
 
        :param run_hooks: Private parameter - if false, this branch
1696
 
            is being called because it's the master of the primary branch,
1697
 
            so it should not run its hooks.
1698
 
        """
1699
 
        bound_location = self.get_bound_location()
1700
 
        master_branch = None
1701
 
        if bound_location and source.base != bound_location:
1702
 
            # not pulling from master, so we need to update master.
1703
 
            master_branch = self.get_master_branch()
1704
 
            master_branch.lock_write()
1705
 
        try:
1706
 
            if master_branch:
1707
 
                # pull from source into master.
1708
 
                master_branch.pull(source, overwrite, stop_revision,
1709
 
                    run_hooks=False)
1710
 
            return super(BzrBranch5, self).pull(source, overwrite,
1711
 
                stop_revision, _hook_master=master_branch,
1712
 
                run_hooks=run_hooks)
1713
 
        finally:
1714
 
            if master_branch:
1715
 
                master_branch.unlock()
1716
 
 
1717
 
    def get_bound_location(self):
1718
 
        try:
1719
 
            return self.control_files.get_utf8('bound').read()[:-1]
1720
 
        except errors.NoSuchFile:
1721
 
            return None
1722
 
 
1723
 
    @needs_read_lock
1724
 
    def get_master_branch(self):
1725
 
        """Return the branch we are bound to.
1726
 
        
1727
 
        :return: Either a Branch, or None
1728
 
 
1729
 
        This could memoise the branch, but if thats done
1730
 
        it must be revalidated on each new lock.
1731
 
        So for now we just don't memoise it.
1732
 
        # RBC 20060304 review this decision.
1733
 
        """
1734
 
        bound_loc = self.get_bound_location()
1735
 
        if not bound_loc:
1736
 
            return None
1737
 
        try:
1738
 
            return Branch.open(bound_loc)
1739
 
        except (errors.NotBranchError, errors.ConnectionError), e:
1740
 
            raise errors.BoundBranchConnectionFailure(
1741
 
                    self, bound_loc, e)
1742
 
 
1743
 
    @needs_write_lock
1744
 
    def set_bound_location(self, location):
1745
 
        """Set the target where this branch is bound to.
1746
 
 
1747
 
        :param location: URL to the target branch
1748
 
        """
1749
 
        if location:
1750
 
            self.control_files.put_utf8('bound', location+'\n')
1751
 
        else:
1752
 
            try:
1753
 
                self.control_files._transport.delete('bound')
1754
 
            except NoSuchFile:
1755
 
                return False
 
980
        for f in files:
 
981
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
982
 
 
983
 
 
984
    def clone(self):
 
985
        """
 
986
        >>> orig = ScratchBranch(files=["file1", "file2"])
 
987
        >>> clone = orig.clone()
 
988
        >>> os.path.samefile(orig.base, clone.base)
 
989
        False
 
990
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
 
991
        True
 
992
        """
 
993
        base = tempfile.mkdtemp()
 
994
        os.rmdir(base)
 
995
        shutil.copytree(self.base, base, symlinks=True)
 
996
        return ScratchBranch(base=base)
 
997
        
 
998
    def __del__(self):
 
999
        self.destroy()
 
1000
 
 
1001
    def destroy(self):
 
1002
        """Destroy the test branch, removing the scratch directory."""
 
1003
        try:
 
1004
            if self.base:
 
1005
                mutter("delete ScratchBranch %s" % self.base)
 
1006
                shutil.rmtree(self.base)
 
1007
        except OSError, e:
 
1008
            # Work around for shutil.rmtree failing on Windows when
 
1009
            # readonly files are encountered
 
1010
            mutter("hit exception in destroying ScratchBranch: %s" % e)
 
1011
            for root, dirs, files in os.walk(self.base, topdown=False):
 
1012
                for name in files:
 
1013
                    os.chmod(os.path.join(root, name), 0700)
 
1014
            shutil.rmtree(self.base)
 
1015
        self.base = None
 
1016
 
 
1017
    
 
1018
 
 
1019
######################################################################
 
1020
# predicates
 
1021
 
 
1022
 
 
1023
def is_control_file(filename):
 
1024
    ## FIXME: better check
 
1025
    filename = os.path.normpath(filename)
 
1026
    while filename != '':
 
1027
        head, tail = os.path.split(filename)
 
1028
        ## mutter('check %r for control file' % ((head, tail), ))
 
1029
        if tail == bzrlib.BZRDIR:
1756
1030
            return True
1757
 
 
1758
 
    @needs_write_lock
1759
 
    def bind(self, other):
1760
 
        """Bind this branch to the branch other.
1761
 
 
1762
 
        This does not push or pull data between the branches, though it does
1763
 
        check for divergence to raise an error when the branches are not
1764
 
        either the same, or one a prefix of the other. That behaviour may not
1765
 
        be useful, so that check may be removed in future.
1766
 
        
1767
 
        :param other: The branch to bind to
1768
 
        :type other: Branch
1769
 
        """
1770
 
        # TODO: jam 20051230 Consider checking if the target is bound
1771
 
        #       It is debatable whether you should be able to bind to
1772
 
        #       a branch which is itself bound.
1773
 
        #       Committing is obviously forbidden,
1774
 
        #       but binding itself may not be.
1775
 
        #       Since we *have* to check at commit time, we don't
1776
 
        #       *need* to check here
1777
 
 
1778
 
        # we want to raise diverged if:
1779
 
        # last_rev is not in the other_last_rev history, AND
1780
 
        # other_last_rev is not in our history, and do it without pulling
1781
 
        # history around
1782
 
        last_rev = self.last_revision()
1783
 
        if last_rev is not None:
1784
 
            other.lock_read()
1785
 
            try:
1786
 
                other_last_rev = other.last_revision()
1787
 
                if other_last_rev is not None:
1788
 
                    # neither branch is new, we have to do some work to
1789
 
                    # ascertain diversion.
1790
 
                    remote_graph = other.repository.get_revision_graph(
1791
 
                        other_last_rev)
1792
 
                    local_graph = self.repository.get_revision_graph(last_rev)
1793
 
                    if (last_rev not in remote_graph and
1794
 
                        other_last_rev not in local_graph):
1795
 
                        raise errors.DivergedBranches(self, other)
1796
 
            finally:
1797
 
                other.unlock()
1798
 
        self.set_bound_location(other.base)
1799
 
 
1800
 
    @needs_write_lock
1801
 
    def unbind(self):
1802
 
        """If bound, unbind"""
1803
 
        return self.set_bound_location(None)
1804
 
 
1805
 
    @needs_write_lock
1806
 
    def update(self):
1807
 
        """Synchronise this branch with the master branch if any. 
1808
 
 
1809
 
        :return: None or the last_revision that was pivoted out during the
1810
 
                 update.
1811
 
        """
1812
 
        master = self.get_master_branch()
1813
 
        if master is not None:
1814
 
            old_tip = self.last_revision()
1815
 
            self.pull(master, overwrite=True)
1816
 
            if old_tip in self.repository.get_ancestry(self.last_revision(),
1817
 
                                                       topo_sorted=False):
1818
 
                return None
1819
 
            return old_tip
1820
 
        return None
1821
 
 
1822
 
 
1823
 
class BzrBranchExperimental(BzrBranch5):
1824
 
    """Bzr experimental branch format
1825
 
 
1826
 
    This format has:
1827
 
     - a revision-history file.
1828
 
     - a format string
1829
 
     - a lock dir guarding the branch itself
1830
 
     - all of this stored in a branch/ subdirectory
1831
 
     - works with shared repositories.
1832
 
     - a tag dictionary in the branch
1833
 
 
1834
 
    This format is new in bzr 0.15, but shouldn't be used for real data, 
1835
 
    only for testing.
1836
 
 
1837
 
    This class acts as it's own BranchFormat.
1838
 
    """
1839
 
 
1840
 
    _matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1841
 
 
1842
 
    @classmethod
1843
 
    def get_format_string(cls):
1844
 
        """See BranchFormat.get_format_string()."""
1845
 
        return "Bazaar-NG branch format experimental\n"
1846
 
 
1847
 
    @classmethod
1848
 
    def get_format_description(cls):
1849
 
        """See BranchFormat.get_format_description()."""
1850
 
        return "Experimental branch format"
1851
 
 
1852
 
    @classmethod
1853
 
    def get_reference(cls, a_bzrdir):
1854
 
        """Get the target reference of the branch in a_bzrdir.
1855
 
 
1856
 
        format probing must have been completed before calling
1857
 
        this method - it is assumed that the format of the branch
1858
 
        in a_bzrdir is correct.
1859
 
 
1860
 
        :param a_bzrdir: The bzrdir to get the branch data from.
1861
 
        :return: None if the branch is not a reference branch.
1862
 
        """
1863
 
        return None
1864
 
 
1865
 
    @classmethod
1866
 
    def _initialize_control_files(cls, a_bzrdir, utf8_files, lock_filename,
1867
 
            lock_class):
1868
 
        branch_transport = a_bzrdir.get_branch_transport(cls)
1869
 
        control_files = lockable_files.LockableFiles(branch_transport,
1870
 
            lock_filename, lock_class)
1871
 
        control_files.create_lock()
1872
 
        control_files.lock_write()
1873
 
        try:
1874
 
            for filename, content in utf8_files:
1875
 
                control_files.put_utf8(filename, content)
1876
 
        finally:
1877
 
            control_files.unlock()
1878
 
        
1879
 
    @classmethod
1880
 
    def initialize(cls, a_bzrdir):
1881
 
        """Create a branch of this format in a_bzrdir."""
1882
 
        utf8_files = [('format', cls.get_format_string()),
1883
 
                      ('revision-history', ''),
1884
 
                      ('branch-name', ''),
1885
 
                      ('tags', ''),
1886
 
                      ]
1887
 
        cls._initialize_control_files(a_bzrdir, utf8_files,
1888
 
            'lock', lockdir.LockDir)
1889
 
        return cls.open(a_bzrdir, _found=True)
1890
 
 
1891
 
    @classmethod
1892
 
    def open(cls, a_bzrdir, _found=False):
1893
 
        """Return the branch object for a_bzrdir
1894
 
 
1895
 
        _found is a private parameter, do not use it. It is used to indicate
1896
 
               if format probing has already be done.
1897
 
        """
1898
 
        if not _found:
1899
 
            format = BranchFormat.find_format(a_bzrdir)
1900
 
            assert format.__class__ == cls
1901
 
        transport = a_bzrdir.get_branch_transport(None)
1902
 
        control_files = lockable_files.LockableFiles(transport, 'lock',
1903
 
                                                     lockdir.LockDir)
1904
 
        return cls(_format=cls,
1905
 
            _control_files=control_files,
1906
 
            a_bzrdir=a_bzrdir,
1907
 
            _repository=a_bzrdir.find_repository())
1908
 
 
1909
 
    @classmethod
1910
 
    def is_supported(cls):
1911
 
        return True
1912
 
 
1913
 
    def _make_tags(self):
1914
 
        return BasicTags(self)
1915
 
 
1916
 
    @classmethod
1917
 
    def supports_tags(cls):
1918
 
        return True
1919
 
 
1920
 
 
1921
 
BranchFormat.register_format(BzrBranchExperimental)
1922
 
 
1923
 
 
1924
 
class BzrBranch6(BzrBranch5):
1925
 
 
1926
 
    @needs_read_lock
1927
 
    def last_revision_info(self):
1928
 
        revision_string = self.control_files.get('last-revision').read()
1929
 
        revno, revision_id = revision_string.rstrip('\n').split(' ', 1)
1930
 
        revision_id = cache_utf8.get_cached_utf8(revision_id)
1931
 
        revno = int(revno)
1932
 
        return revno, revision_id
1933
 
 
1934
 
    def last_revision(self):
1935
 
        """Return last revision id, or None"""
1936
 
        revision_id = self.last_revision_info()[1]
1937
 
        if revision_id == _mod_revision.NULL_REVISION:
1938
 
            revision_id = None
1939
 
        return revision_id
1940
 
 
1941
 
    def _write_last_revision_info(self, revno, revision_id):
1942
 
        """Simply write out the revision id, with no checks.
1943
 
 
1944
 
        Use set_last_revision_info to perform this safely.
1945
 
 
1946
 
        Does not update the revision_history cache.
1947
 
        Intended to be called by set_last_revision_info and
1948
 
        _write_revision_history.
1949
 
        """
1950
 
        if revision_id is None:
1951
 
            revision_id = 'null:'
1952
 
        out_string = '%d %s\n' % (revno, revision_id)
1953
 
        self.control_files.put_bytes('last-revision', out_string)
1954
 
 
1955
 
    @needs_write_lock
1956
 
    def set_last_revision_info(self, revno, revision_id):
1957
 
        revision_id = osutils.safe_revision_id(revision_id)
1958
 
        if self._get_append_revisions_only():
1959
 
            self._check_history_violation(revision_id)
1960
 
        self._write_last_revision_info(revno, revision_id)
1961
 
        self._clear_cached_state()
1962
 
 
1963
 
    def _check_history_violation(self, revision_id):
1964
 
        last_revision = self.last_revision()
1965
 
        if last_revision is None:
1966
 
            return
1967
 
        if last_revision not in self._lefthand_history(revision_id):
1968
 
            raise errors.AppendRevisionsOnlyViolation(self.base)
1969
 
 
1970
 
    def _gen_revision_history(self):
1971
 
        """Generate the revision history from last revision
1972
 
        """
1973
 
        history = list(self.repository.iter_reverse_revision_history(
1974
 
            self.last_revision()))
1975
 
        history.reverse()
1976
 
        return history
1977
 
 
1978
 
    def _write_revision_history(self, history):
1979
 
        """Factored out of set_revision_history.
1980
 
 
1981
 
        This performs the actual writing to disk, with format-specific checks.
1982
 
        It is intended to be called by BzrBranch5.set_revision_history.
1983
 
        """
1984
 
        if len(history) == 0:
1985
 
            last_revision = 'null:'
1986
 
        else:
1987
 
            if history != self._lefthand_history(history[-1]):
1988
 
                raise errors.NotLefthandHistory(history)
1989
 
            last_revision = history[-1]
1990
 
        if self._get_append_revisions_only():
1991
 
            self._check_history_violation(last_revision)
1992
 
        self._write_last_revision_info(len(history), last_revision)
1993
 
 
1994
 
    @needs_write_lock
1995
 
    def append_revision(self, *revision_ids):
1996
 
        revision_ids = [osutils.safe_revision_id(r) for r in revision_ids]
1997
 
        if len(revision_ids) == 0:
1998
 
            return
1999
 
        prev_revno, prev_revision = self.last_revision_info()
2000
 
        for revision in self.repository.get_revisions(revision_ids):
2001
 
            if prev_revision == _mod_revision.NULL_REVISION:
2002
 
                if revision.parent_ids != []:
2003
 
                    raise errors.NotLeftParentDescendant(self, prev_revision,
2004
 
                                                         revision.revision_id)
2005
 
            else:
2006
 
                if revision.parent_ids[0] != prev_revision:
2007
 
                    raise errors.NotLeftParentDescendant(self, prev_revision,
2008
 
                                                         revision.revision_id)
2009
 
            prev_revision = revision.revision_id
2010
 
        self.set_last_revision_info(prev_revno + len(revision_ids),
2011
 
                                    revision_ids[-1])
2012
 
 
2013
 
    @needs_write_lock
2014
 
    def _set_parent_location(self, url):
2015
 
        """Set the parent branch"""
2016
 
        self._set_config_location('parent_location', url, make_relative=True)
2017
 
 
2018
 
    @needs_read_lock
2019
 
    def _get_parent_location(self):
2020
 
        """Set the parent branch"""
2021
 
        return self._get_config_location('parent_location')
2022
 
 
2023
 
    def set_push_location(self, location):
2024
 
        """See Branch.set_push_location."""
2025
 
        self._set_config_location('push_location', location)
2026
 
 
2027
 
    def set_bound_location(self, location):
2028
 
        """See Branch.set_push_location."""
2029
 
        result = None
2030
 
        config = self.get_config()
2031
 
        if location is None:
2032
 
            if config.get_user_option('bound') != 'True':
2033
 
                return False
2034
 
            else:
2035
 
                config.set_user_option('bound', 'False', warn_masked=True)
2036
 
                return True
2037
 
        else:
2038
 
            self._set_config_location('bound_location', location,
2039
 
                                      config=config)
2040
 
            config.set_user_option('bound', 'True', warn_masked=True)
2041
 
        return True
2042
 
 
2043
 
    def _get_bound_location(self, bound):
2044
 
        """Return the bound location in the config file.
2045
 
 
2046
 
        Return None if the bound parameter does not match"""
2047
 
        config = self.get_config()
2048
 
        config_bound = (config.get_user_option('bound') == 'True')
2049
 
        if config_bound != bound:
2050
 
            return None
2051
 
        return self._get_config_location('bound_location', config=config)
2052
 
 
2053
 
    def get_bound_location(self):
2054
 
        """See Branch.set_push_location."""
2055
 
        return self._get_bound_location(True)
2056
 
 
2057
 
    def get_old_bound_location(self):
2058
 
        """See Branch.get_old_bound_location"""
2059
 
        return self._get_bound_location(False)
2060
 
 
2061
 
    def set_append_revisions_only(self, enabled):
2062
 
        if enabled:
2063
 
            value = 'True'
2064
 
        else:
2065
 
            value = 'False'
2066
 
        self.get_config().set_user_option('append_revisions_only', value,
2067
 
            warn_masked=True)
2068
 
 
2069
 
    def _get_append_revisions_only(self):
2070
 
        value = self.get_config().get_user_option('append_revisions_only')
2071
 
        return value == 'True'
2072
 
 
2073
 
    def _synchronize_history(self, destination, revision_id):
2074
 
        """Synchronize last revision and revision history between branches.
2075
 
 
2076
 
        This version is most efficient when the destination is also a
2077
 
        BzrBranch6, but works for BzrBranch5, as long as the destination's
2078
 
        repository contains all the lefthand ancestors of the intended
2079
 
        last_revision.  If not, set_last_revision_info will fail.
2080
 
 
2081
 
        :param destination: The branch to copy the history into
2082
 
        :param revision_id: The revision-id to truncate history at.  May
2083
 
          be None to copy complete history.
2084
 
        """
2085
 
        if revision_id is None:
2086
 
            revno, revision_id = self.last_revision_info()
2087
 
        else:
2088
 
            # To figure out the revno for a random revision, we need to build
2089
 
            # the revision history, and count its length.
2090
 
            # We don't care about the order, just how long it is.
2091
 
            # Alternatively, we could start at the current location, and count
2092
 
            # backwards. But there is no guarantee that we will find it since
2093
 
            # it may be a merged revision.
2094
 
            revno = len(list(self.repository.iter_reverse_revision_history(
2095
 
                                                                revision_id)))
2096
 
        destination.set_last_revision_info(revno, revision_id)
2097
 
 
2098
 
    def _make_tags(self):
2099
 
        return BasicTags(self)
2100
 
 
2101
 
 
2102
 
######################################################################
2103
 
# results of operations
2104
 
 
2105
 
 
2106
 
class _Result(object):
2107
 
 
2108
 
    def _show_tag_conficts(self, to_file):
2109
 
        if not getattr(self, 'tag_conflicts', None):
2110
 
            return
2111
 
        to_file.write('Conflicting tags:\n')
2112
 
        for name, value1, value2 in self.tag_conflicts:
2113
 
            to_file.write('    %s\n' % (name, ))
2114
 
 
2115
 
 
2116
 
class PullResult(_Result):
2117
 
    """Result of a Branch.pull operation.
2118
 
 
2119
 
    :ivar old_revno: Revision number before pull.
2120
 
    :ivar new_revno: Revision number after pull.
2121
 
    :ivar old_revid: Tip revision id before pull.
2122
 
    :ivar new_revid: Tip revision id after pull.
2123
 
    :ivar source_branch: Source (local) branch object.
2124
 
    :ivar master_branch: Master branch of the target, or None.
2125
 
    :ivar target_branch: Target/destination branch object.
2126
 
    """
2127
 
 
2128
 
    def __int__(self):
2129
 
        # DEPRECATED: pull used to return the change in revno
2130
 
        return self.new_revno - self.old_revno
2131
 
 
2132
 
    def report(self, to_file):
2133
 
        if self.old_revid == self.new_revid:
2134
 
            to_file.write('No revisions to pull.\n')
2135
 
        else:
2136
 
            to_file.write('Now on revision %d.\n' % self.new_revno)
2137
 
        self._show_tag_conficts(to_file)
2138
 
 
2139
 
 
2140
 
class PushResult(_Result):
2141
 
    """Result of a Branch.push operation.
2142
 
 
2143
 
    :ivar old_revno: Revision number before push.
2144
 
    :ivar new_revno: Revision number after push.
2145
 
    :ivar old_revid: Tip revision id before push.
2146
 
    :ivar new_revid: Tip revision id after push.
2147
 
    :ivar source_branch: Source branch object.
2148
 
    :ivar master_branch: Master branch of the target, or None.
2149
 
    :ivar target_branch: Target/destination branch object.
2150
 
    """
2151
 
 
2152
 
    def __int__(self):
2153
 
        # DEPRECATED: push used to return the change in revno
2154
 
        return self.new_revno - self.old_revno
2155
 
 
2156
 
    def report(self, to_file):
2157
 
        """Write a human-readable description of the result."""
2158
 
        if self.old_revid == self.new_revid:
2159
 
            to_file.write('No new revisions to push.\n')
2160
 
        else:
2161
 
            to_file.write('Pushed up to revision %d.\n' % self.new_revno)
2162
 
        self._show_tag_conficts(to_file)
2163
 
 
2164
 
 
2165
 
class BranchCheckResult(object):
2166
 
    """Results of checking branch consistency.
2167
 
 
2168
 
    :see: Branch.check
2169
 
    """
2170
 
 
2171
 
    def __init__(self, branch):
2172
 
        self.branch = branch
2173
 
 
2174
 
    def report_results(self, verbose):
2175
 
        """Report the check results via trace.note.
2176
 
        
2177
 
        :param verbose: Requests more detailed display of what was checked,
2178
 
            if any.
2179
 
        """
2180
 
        note('checked branch %s format %s',
2181
 
             self.branch.base,
2182
 
             self.branch._format)
2183
 
 
2184
 
 
2185
 
class Converter5to6(object):
2186
 
    """Perform an in-place upgrade of format 5 to format 6"""
2187
 
 
2188
 
    def convert(self, branch):
2189
 
        # Data for 5 and 6 can peacefully coexist.
2190
 
        format = BzrBranchFormat6()
2191
 
        new_branch = format.open(branch.bzrdir, _found=True)
2192
 
 
2193
 
        # Copy source data into target
2194
 
        new_branch.set_last_revision_info(*branch.last_revision_info())
2195
 
        new_branch.set_parent(branch.get_parent())
2196
 
        new_branch.set_bound_location(branch.get_bound_location())
2197
 
        new_branch.set_push_location(branch.get_push_location())
2198
 
 
2199
 
        # New branch has no tags by default
2200
 
        new_branch.tags._set_tag_dict({})
2201
 
 
2202
 
        # Copying done; now update target format
2203
 
        new_branch.control_files.put_utf8('format',
2204
 
            format.get_format_string())
2205
 
 
2206
 
        # Clean up old files
2207
 
        new_branch.control_files._transport.delete('revision-history')
2208
 
        try:
2209
 
            branch.set_parent(None)
2210
 
        except NoSuchFile:
2211
 
            pass
2212
 
        branch.set_bound_location(None)
 
1031
        if filename == head:
 
1032
            break
 
1033
        filename = head
 
1034
    return False
 
1035
 
 
1036
 
 
1037
 
 
1038
def gen_file_id(name):
 
1039
    """Return new file id.
 
1040
 
 
1041
    This should probably generate proper UUIDs, but for the moment we
 
1042
    cope with just randomness because running uuidgen every time is
 
1043
    slow."""
 
1044
    import re
 
1045
 
 
1046
    # get last component
 
1047
    idx = name.rfind('/')
 
1048
    if idx != -1:
 
1049
        name = name[idx+1 : ]
 
1050
    idx = name.rfind('\\')
 
1051
    if idx != -1:
 
1052
        name = name[idx+1 : ]
 
1053
 
 
1054
    # make it not a hidden file
 
1055
    name = name.lstrip('.')
 
1056
 
 
1057
    # remove any wierd characters; we don't escape them but rather
 
1058
    # just pull them out
 
1059
    name = re.sub(r'[^\w.]', '', name)
 
1060
 
 
1061
    s = hexlify(rand_bytes(8))
 
1062
    return '-'.join((name, compact_date(time.time()), s))