/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-05-31 03:03:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050531030354-561dbe9ec2862d46
doc

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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 copy import deepcopy
19
 
from cStringIO import StringIO
20
 
import errno
21
 
import os
22
 
import shutil
23
 
import sys
24
 
from unittest import TestSuite
25
 
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
26
21
 
27
22
import bzrlib
28
 
import bzrlib.bzrdir as bzrdir
29
 
from bzrlib.config import TreeConfig
30
 
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
 
from bzrlib.delta import compare_trees
32
 
import bzrlib.errors as errors
33
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
 
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
 
                           DivergedBranches, LockError,
36
 
                           UninitializableFormat,
37
 
                           UnlistableStore,
38
 
                           UnlistableBranch, NoSuchFile, NotVersionedError,
39
 
                           NoWorkingTree)
40
 
import bzrlib.inventory as inventory
41
 
from bzrlib.inventory import Inventory
42
 
from bzrlib.lockable_files import LockableFiles, TransportLock
43
 
from bzrlib.lockdir import LockDir
44
 
from bzrlib.osutils import (isdir, quotefn,
45
 
                            rename, splitpath, sha_file,
46
 
                            file_kind, abspath, normpath, pathjoin,
47
 
                            safe_unicode,
48
 
                            )
49
 
from bzrlib.textui import show_status
50
 
from bzrlib.trace import mutter, note
51
 
from bzrlib.tree import EmptyTree, RevisionTree
52
 
from bzrlib.repository import Repository
53
 
from bzrlib.revision import (
54
 
                             get_intervening_revisions,
55
 
                             is_ancestor,
56
 
                             NULL_REVISION,
57
 
                             Revision,
58
 
                             )
59
 
from bzrlib.store import copy_all
60
 
from bzrlib.symbol_versioning import *
61
 
import bzrlib.transactions as transactions
62
 
from bzrlib.transport import Transport, get_transport
63
 
from bzrlib.tree import EmptyTree, RevisionTree
64
 
import bzrlib.ui
65
 
import bzrlib.xml5
66
 
 
67
 
 
68
 
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
69
 
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
70
 
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
71
 
 
72
 
 
73
 
# TODO: Maybe include checks for common corruption of newlines, etc?
74
 
 
75
 
# TODO: Some operations like log might retrieve the same revisions
76
 
# repeatedly to calculate deltas.  We could perhaps have a weakref
77
 
# cache in memory to make this faster.  In general anything can be
78
 
# cached in memory between lock and unlock operations. .. nb thats
79
 
# 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_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 with_writelock(method):
 
50
    """Method decorator for functions run with the branch locked."""
 
51
    def d(self, *a, **k):
 
52
        # called with self set to the branch
 
53
        self.lock('w')
 
54
        try:
 
55
            return method(self, *a, **k)
 
56
        finally:
 
57
            self.unlock()
 
58
    return d
 
59
 
 
60
 
 
61
def with_readlock(method):
 
62
    def d(self, *a, **k):
 
63
        self.lock('r')
 
64
        try:
 
65
            return method(self, *a, **k)
 
66
        finally:
 
67
            self.unlock()
 
68
    return d
 
69
 
 
70
 
 
71
def _relpath(base, path):
 
72
    """Return path relative to base, or raise exception.
 
73
 
 
74
    The path may be either an absolute path or a path relative to the
 
75
    current working directory.
 
76
 
 
77
    Lifted out of Branch.relpath for ease of testing.
 
78
 
 
79
    os.path.commonprefix (python2.4) has a bad bug that it works just
 
80
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
 
81
    avoids that problem."""
 
82
    rp = os.path.abspath(path)
 
83
 
 
84
    s = []
 
85
    head = rp
 
86
    while len(head) >= len(base):
 
87
        if head == base:
 
88
            break
 
89
        head, tail = os.path.split(head)
 
90
        if tail:
 
91
            s.insert(0, tail)
 
92
    else:
 
93
        from errors import NotBranchError
 
94
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
 
95
 
 
96
    return os.sep.join(s)
 
97
        
 
98
 
 
99
def find_branch_root(f=None):
 
100
    """Find the branch root enclosing f, or pwd.
 
101
 
 
102
    f may be a filename or a URL.
 
103
 
 
104
    It is not necessary that f exists.
 
105
 
 
106
    Basically we keep looking up until we find the control directory or
 
107
    run into the root."""
 
108
    if f == None:
 
109
        f = os.getcwd()
 
110
    elif hasattr(os.path, 'realpath'):
 
111
        f = os.path.realpath(f)
 
112
    else:
 
113
        f = os.path.abspath(f)
 
114
    if not os.path.exists(f):
 
115
        raise BzrError('%r does not exist' % f)
 
116
        
 
117
 
 
118
    orig_f = f
 
119
 
 
120
    while True:
 
121
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
 
122
            return f
 
123
        head, tail = os.path.split(f)
 
124
        if head == f:
 
125
            # reached the root, whatever that may be
 
126
            raise BzrError('%r is not in a branch' % orig_f)
 
127
        f = head
 
128
    
80
129
 
81
130
 
82
131
######################################################################
86
135
    """Branch holding a history of revisions.
87
136
 
88
137
    base
89
 
        Base directory/url of the branch.
 
138
        Base directory of the branch.
 
139
 
 
140
    _lock_mode
 
141
        None, or 'r' or 'w'
 
142
 
 
143
    _lock_count
 
144
        If _lock_mode is true, a positive count of the number of times the
 
145
        lock has been taken.
 
146
 
 
147
    _lockfile
 
148
        Open file used for locking.
90
149
    """
91
 
    # this is really an instance variable - FIXME move it there
92
 
    # - RBC 20060112
93
150
    base = None
94
 
 
95
 
    def __init__(self, *ignored, **ignored_too):
96
 
        raise NotImplementedError('The Branch class is abstract')
97
 
 
98
 
    @staticmethod
99
 
    @deprecated_method(zero_eight)
100
 
    def open_downlevel(base):
101
 
        """Open a branch which may be of an old format."""
102
 
        return Branch.open(base, _unsupported=True)
103
 
        
104
 
    @staticmethod
105
 
    def open(base, _unsupported=False):
106
 
        """Open the repository rooted at base.
107
 
 
108
 
        For instance, if the repository is at URL/.bzr/repository,
109
 
        Repository.open(URL) -> a Repository instance.
110
 
        """
111
 
        control = bzrdir.BzrDir.open(base, _unsupported)
112
 
        return control.open_branch(_unsupported)
113
 
 
114
 
    @staticmethod
115
 
    def open_containing(url):
116
 
        """Open an existing branch which contains url.
117
 
        
118
 
        This probes for a branch at url, and searches upwards from there.
119
 
 
120
 
        Basically we keep looking up until we find the control directory or
121
 
        run into the root.  If there isn't one, raises NotBranchError.
122
 
        If there is one and it is either an unrecognised format or an unsupported 
123
 
        format, UnknownFormatError or UnsupportedFormatError are raised.
124
 
        If there is one, it is returned, along with the unused portion of url.
125
 
        """
126
 
        control, relpath = bzrdir.BzrDir.open_containing(url)
127
 
        return control.open_branch(), relpath
128
 
 
129
 
    @staticmethod
130
 
    @deprecated_function(zero_eight)
131
 
    def initialize(base):
132
 
        """Create a new working tree and branch, rooted at 'base' (url)
133
 
 
134
 
        NOTE: This will soon be deprecated in favour of creation
135
 
        through a BzrDir.
136
 
        """
137
 
        return bzrdir.BzrDir.create_standalone_workingtree(base).branch
138
 
 
139
 
    def setup_caching(self, cache_root):
140
 
        """Subclasses that care about caching should override this, and set
141
 
        up cached stores located under cache_root.
142
 
        """
143
 
        # seems to be unused, 2006-01-13 mbp
144
 
        warn('%s is deprecated' % self.setup_caching)
145
 
        self.cache_root = cache_root
146
 
 
147
 
    def _get_nick(self):
148
 
        cfg = self.tree_config()
149
 
        return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
150
 
 
151
 
    def _set_nick(self, nick):
152
 
        cfg = self.tree_config()
153
 
        cfg.set_option(nick, "nickname")
154
 
        assert cfg.get_option("nickname") == nick
155
 
 
156
 
    nick = property(_get_nick, _set_nick)
157
 
        
158
 
    def lock_write(self):
159
 
        raise NotImplementedError('lock_write is abstract')
160
 
        
161
 
    def lock_read(self):
162
 
        raise NotImplementedError('lock_read is abstract')
 
151
    _lock_mode = None
 
152
    _lock_count = None
 
153
    
 
154
    def __init__(self, base, init=False, find_root=True):
 
155
        """Create new branch object at a particular location.
 
156
 
 
157
        base -- Base directory for the branch.
 
158
        
 
159
        init -- If True, create new control files in a previously
 
160
             unversioned directory.  If False, the branch must already
 
161
             be versioned.
 
162
 
 
163
        find_root -- If true and init is false, find the root of the
 
164
             existing branch containing base.
 
165
 
 
166
        In the test suite, creation of new trees is tested using the
 
167
        `ScratchBranch` class.
 
168
        """
 
169
        if init:
 
170
            self.base = os.path.realpath(base)
 
171
            self._make_control()
 
172
        elif find_root:
 
173
            self.base = find_branch_root(base)
 
174
        else:
 
175
            self.base = os.path.realpath(base)
 
176
            if not isdir(self.controlfilename('.')):
 
177
                from errors import NotBranchError
 
178
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
 
179
                                     ['use "bzr init" to initialize a new working tree',
 
180
                                      'current bzr can only operate from top-of-tree'])
 
181
        self._check_format()
 
182
        self._lockfile = self.controlfile('branch-lock', 'wb')
 
183
 
 
184
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
 
185
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
 
186
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
 
187
 
 
188
 
 
189
    def __str__(self):
 
190
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
191
 
 
192
 
 
193
    __repr__ = __str__
 
194
 
 
195
 
 
196
    def __del__(self):
 
197
        if self._lock_mode:
 
198
            from warnings import warn
 
199
            warn("branch %r was not explicitly unlocked" % self)
 
200
            self.unlock()
 
201
 
 
202
 
 
203
    def lock(self, mode):
 
204
        if self._lock_mode:
 
205
            if mode == 'w' and cur_lm == 'r':
 
206
                raise BzrError("can't upgrade to a write lock")
 
207
            
 
208
            assert self._lock_count >= 1
 
209
            self._lock_count += 1
 
210
        else:
 
211
            from bzrlib.lock import lock, LOCK_SH, LOCK_EX
 
212
            if mode == 'r':
 
213
                m = LOCK_SH
 
214
            elif mode == 'w':
 
215
                m = LOCK_EX
 
216
            else:
 
217
                raise ValueError('invalid lock mode %r' % mode)
 
218
 
 
219
            lock(self._lockfile, m)
 
220
            self._lock_mode = mode
 
221
            self._lock_count = 1
 
222
 
163
223
 
164
224
    def unlock(self):
165
 
        raise NotImplementedError('unlock is abstract')
166
 
 
167
 
    def peek_lock_mode(self):
168
 
        """Return lock mode for the Branch: 'r', 'w' or None"""
169
 
        raise NotImplementedError(self.peek_lock_mode)
 
225
        if not self._lock_mode:
 
226
            raise BzrError('branch %r is not locked' % (self))
 
227
 
 
228
        if self._lock_count > 1:
 
229
            self._lock_count -= 1
 
230
        else:
 
231
            assert self._lock_count == 1
 
232
            from bzrlib.lock import unlock
 
233
            unlock(self._lockfile)
 
234
            self._lock_mode = self._lock_count = None
 
235
 
170
236
 
171
237
    def abspath(self, name):
172
 
        """Return absolute filename for something in the branch
173
 
        
174
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
175
 
        method and not a tree method.
176
 
        """
177
 
        raise NotImplementedError('abspath is abstract')
178
 
 
179
 
    def bind(self, other):
180
 
        """Bind the local branch the other branch.
181
 
 
182
 
        :param other: The branch to bind to
183
 
        :type other: Branch
184
 
        """
185
 
        raise errors.UpgradeRequired(self.base)
186
 
 
187
 
    @needs_write_lock
188
 
    def fetch(self, from_branch, last_revision=None, pb=None):
189
 
        """Copy revisions from from_branch into this branch.
190
 
 
191
 
        :param from_branch: Where to copy from.
192
 
        :param last_revision: What revision to stop at (None for at the end
193
 
                              of the branch.
194
 
        :param pb: An optional progress bar to use.
195
 
 
196
 
        Returns the copied revision count and the failed revisions in a tuple:
197
 
        (copied, failures).
198
 
        """
199
 
        if self.base == from_branch.base:
200
 
            return (0, [])
201
 
        if pb is None:
202
 
            nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
203
 
            pb = nested_pb
204
 
        else:
205
 
            nested_pb = None
206
 
 
207
 
        from_branch.lock_read()
208
 
        try:
209
 
            if last_revision is None:
210
 
                pb.update('get source history')
211
 
                from_history = from_branch.revision_history()
212
 
                if from_history:
213
 
                    last_revision = from_history[-1]
214
 
                else:
215
 
                    # no history in the source branch
216
 
                    last_revision = NULL_REVISION
217
 
            return self.repository.fetch(from_branch.repository,
218
 
                                         revision_id=last_revision,
219
 
                                         pb=nested_pb)
220
 
        finally:
221
 
            if nested_pb is not None:
222
 
                nested_pb.finished()
223
 
            from_branch.unlock()
224
 
 
225
 
    def get_bound_location(self):
226
 
        """Return the URL of the branch we are bound to.
227
 
 
228
 
        Older format branches cannot bind, please be sure to use a metadir
229
 
        branch.
230
 
        """
231
 
        return None
232
 
 
233
 
    def get_master_branch(self):
234
 
        """Return the branch we are bound to.
235
 
        
236
 
        :return: Either a Branch, or None
237
 
        """
238
 
        return None
239
 
 
240
 
    def get_root_id(self):
241
 
        """Return the id of this branches root"""
242
 
        raise NotImplementedError('get_root_id is abstract')
243
 
 
244
 
    def print_file(self, file, revision_id):
 
238
        """Return absolute filename for something in the branch"""
 
239
        return os.path.join(self.base, name)
 
240
 
 
241
 
 
242
    def relpath(self, path):
 
243
        """Return path relative to this branch of something inside it.
 
244
 
 
245
        Raises an error if path is not in this branch."""
 
246
        return _relpath(self.base, path)
 
247
 
 
248
 
 
249
    def controlfilename(self, file_or_path):
 
250
        """Return location relative to branch."""
 
251
        if isinstance(file_or_path, types.StringTypes):
 
252
            file_or_path = [file_or_path]
 
253
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
254
 
 
255
 
 
256
    def controlfile(self, file_or_path, mode='r'):
 
257
        """Open a control file for this branch.
 
258
 
 
259
        There are two classes of file in the control directory: text
 
260
        and binary.  binary files are untranslated byte streams.  Text
 
261
        control files are stored with Unix newlines and in UTF-8, even
 
262
        if the platform or locale defaults are different.
 
263
 
 
264
        Controlfiles should almost never be opened in write mode but
 
265
        rather should be atomically copied and replaced using atomicfile.
 
266
        """
 
267
 
 
268
        fn = self.controlfilename(file_or_path)
 
269
 
 
270
        if mode == 'rb' or mode == 'wb':
 
271
            return file(fn, mode)
 
272
        elif mode == 'r' or mode == 'w':
 
273
            # open in binary mode anyhow so there's no newline translation;
 
274
            # codecs uses line buffering by default; don't want that.
 
275
            import codecs
 
276
            return codecs.open(fn, mode + 'b', 'utf-8',
 
277
                               buffering=60000)
 
278
        else:
 
279
            raise BzrError("invalid controlfile mode %r" % mode)
 
280
 
 
281
 
 
282
 
 
283
    def _make_control(self):
 
284
        os.mkdir(self.controlfilename([]))
 
285
        self.controlfile('README', 'w').write(
 
286
            "This is a Bazaar-NG control directory.\n"
 
287
            "Do not change any files in this directory.")
 
288
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
 
289
        for d in ('text-store', 'inventory-store', 'revision-store'):
 
290
            os.mkdir(self.controlfilename(d))
 
291
        for f in ('revision-history', 'merged-patches',
 
292
                  'pending-merged-patches', 'branch-name',
 
293
                  'branch-lock'):
 
294
            self.controlfile(f, 'w').write('')
 
295
        mutter('created control directory in ' + self.base)
 
296
        Inventory().write_xml(self.controlfile('inventory','w'))
 
297
 
 
298
 
 
299
    def _check_format(self):
 
300
        """Check this branch format is supported.
 
301
 
 
302
        The current tool only supports the current unstable format.
 
303
 
 
304
        In the future, we might need different in-memory Branch
 
305
        classes to support downlevel branches.  But not yet.
 
306
        """
 
307
        # This ignores newlines so that we can open branches created
 
308
        # on Windows from Linux and so on.  I think it might be better
 
309
        # to always make all internal files in unix format.
 
310
        fmt = self.controlfile('branch-format', 'r').read()
 
311
        fmt.replace('\r\n', '')
 
312
        if fmt != BZR_BRANCH_FORMAT:
 
313
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
314
                           ['use a different bzr version',
 
315
                            'or remove the .bzr directory and "bzr init" again'])
 
316
 
 
317
 
 
318
 
 
319
    @with_readlock
 
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
        inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
 
326
        mutter("loaded inventory of %d items in %f"
 
327
               % (len(inv), time.time() - before))
 
328
        return inv
 
329
            
 
330
 
 
331
    def _write_inventory(self, inv):
 
332
        """Update the working inventory.
 
333
 
 
334
        That is to say, the inventory describing changes underway, that
 
335
        will be committed to the next revision.
 
336
        """
 
337
        ## TODO: factor out to atomicfile?  is rename safe on windows?
 
338
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
 
339
        tmpfname = self.controlfilename('inventory.tmp')
 
340
        tmpf = file(tmpfname, 'wb')
 
341
        inv.write_xml(tmpf)
 
342
        tmpf.close()
 
343
        inv_fname = self.controlfilename('inventory')
 
344
        if sys.platform == 'win32':
 
345
            os.remove(inv_fname)
 
346
        os.rename(tmpfname, inv_fname)
 
347
        mutter('wrote working inventory')
 
348
            
 
349
 
 
350
    inventory = property(read_working_inventory, _write_inventory, None,
 
351
                         """Inventory for the working copy.""")
 
352
 
 
353
 
 
354
    @with_writelock
 
355
    def add(self, files, verbose=False, ids=None):
 
356
        """Make files versioned.
 
357
 
 
358
        Note that the command line normally calls smart_add instead.
 
359
 
 
360
        This puts the files in the Added state, so that they will be
 
361
        recorded by the next commit.
 
362
 
 
363
        files
 
364
            List of paths to add, relative to the base of the tree.
 
365
 
 
366
        ids
 
367
            If set, use these instead of automatically generated ids.
 
368
            Must be the same length as the list of files, but may
 
369
            contain None for ids that are to be autogenerated.
 
370
 
 
371
        TODO: Perhaps have an option to add the ids even if the files do
 
372
              not (yet) exist.
 
373
 
 
374
        TODO: Perhaps return the ids of the files?  But then again it
 
375
              is easy to retrieve them if they're needed.
 
376
 
 
377
        TODO: Adding a directory should optionally recurse down and
 
378
              add all non-ignored children.  Perhaps do that in a
 
379
              higher-level method.
 
380
        """
 
381
        # TODO: Re-adding a file that is removed in the working copy
 
382
        # should probably put it back with the previous ID.
 
383
        if isinstance(files, types.StringTypes):
 
384
            assert(ids is None or isinstance(ids, types.StringTypes))
 
385
            files = [files]
 
386
            if ids is not None:
 
387
                ids = [ids]
 
388
 
 
389
        if ids is None:
 
390
            ids = [None] * len(files)
 
391
        else:
 
392
            assert(len(ids) == len(files))
 
393
 
 
394
        inv = self.read_working_inventory()
 
395
        for f,file_id in zip(files, ids):
 
396
            if is_control_file(f):
 
397
                raise BzrError("cannot add control file %s" % quotefn(f))
 
398
 
 
399
            fp = splitpath(f)
 
400
 
 
401
            if len(fp) == 0:
 
402
                raise BzrError("cannot add top-level %r" % f)
 
403
 
 
404
            fullpath = os.path.normpath(self.abspath(f))
 
405
 
 
406
            try:
 
407
                kind = file_kind(fullpath)
 
408
            except OSError:
 
409
                # maybe something better?
 
410
                raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
411
 
 
412
            if kind != 'file' and kind != 'directory':
 
413
                raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
414
 
 
415
            if file_id is None:
 
416
                file_id = gen_file_id(f)
 
417
            inv.add_path(f, kind=kind, file_id=file_id)
 
418
 
 
419
            if verbose:
 
420
                show_status('A', kind, quotefn(f))
 
421
 
 
422
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
423
 
 
424
        self._write_inventory(inv)
 
425
            
 
426
 
 
427
    def print_file(self, file, revno):
245
428
        """Print `file` to stdout."""
246
 
        raise NotImplementedError('print_file is abstract')
247
 
 
248
 
    def append_revision(self, *revision_ids):
249
 
        raise NotImplementedError('append_revision is abstract')
250
 
 
251
 
    def set_revision_history(self, rev_history):
252
 
        raise NotImplementedError('set_revision_history is abstract')
253
 
 
 
429
        tree = self.revision_tree(self.lookup_revision(revno))
 
430
        # use inventory as it was in that revision
 
431
        file_id = tree.inventory.path2id(file)
 
432
        if not file_id:
 
433
            raise BzrError("%r is not present in revision %d" % (file, revno))
 
434
        tree.print_file(file_id)
 
435
 
 
436
 
 
437
    @with_writelock
 
438
    def remove(self, files, verbose=False):
 
439
        """Mark nominated files for removal from the inventory.
 
440
 
 
441
        This does not remove their text.  This does not run on 
 
442
 
 
443
        TODO: Refuse to remove modified files unless --force is given?
 
444
 
 
445
        TODO: Do something useful with directories.
 
446
 
 
447
        TODO: Should this remove the text or not?  Tough call; not
 
448
        removing may be useful and the user can just use use rm, and
 
449
        is the opposite of add.  Removing it is consistent with most
 
450
        other tools.  Maybe an option.
 
451
        """
 
452
        ## TODO: Normalize names
 
453
        ## TODO: Remove nested loops; better scalability
 
454
        if isinstance(files, types.StringTypes):
 
455
            files = [files]
 
456
 
 
457
        tree = self.working_tree()
 
458
        inv = tree.inventory
 
459
 
 
460
        # do this before any modifications
 
461
        for f in files:
 
462
            fid = inv.path2id(f)
 
463
            if not fid:
 
464
                raise BzrError("cannot remove unversioned file %s" % quotefn(f))
 
465
            mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
 
466
            if verbose:
 
467
                # having remove it, it must be either ignored or unknown
 
468
                if tree.is_ignored(f):
 
469
                    new_status = 'I'
 
470
                else:
 
471
                    new_status = '?'
 
472
                show_status(new_status, inv[fid].kind, quotefn(f))
 
473
            del inv[fid]
 
474
 
 
475
        self._write_inventory(inv)
 
476
 
 
477
 
 
478
    def set_inventory(self, new_inventory_list):
 
479
        inv = Inventory()
 
480
        for path, file_id, parent, kind in new_inventory_list:
 
481
            name = os.path.basename(path)
 
482
            if name == "":
 
483
                continue
 
484
            inv.add(InventoryEntry(file_id, name, kind, parent))
 
485
        self._write_inventory(inv)
 
486
 
 
487
 
 
488
    def unknowns(self):
 
489
        """Return all unknown files.
 
490
 
 
491
        These are files in the working directory that are not versioned or
 
492
        control files or ignored.
 
493
        
 
494
        >>> b = ScratchBranch(files=['foo', 'foo~'])
 
495
        >>> list(b.unknowns())
 
496
        ['foo']
 
497
        >>> b.add('foo')
 
498
        >>> list(b.unknowns())
 
499
        []
 
500
        >>> b.remove('foo')
 
501
        >>> list(b.unknowns())
 
502
        ['foo']
 
503
        """
 
504
        return self.working_tree().unknowns()
 
505
 
 
506
 
 
507
    def append_revision(self, revision_id):
 
508
        mutter("add {%s} to revision-history" % revision_id)
 
509
        rev_history = self.revision_history()
 
510
 
 
511
        tmprhname = self.controlfilename('revision-history.tmp')
 
512
        rhname = self.controlfilename('revision-history')
 
513
        
 
514
        f = file(tmprhname, 'wt')
 
515
        rev_history.append(revision_id)
 
516
        f.write('\n'.join(rev_history))
 
517
        f.write('\n')
 
518
        f.close()
 
519
 
 
520
        if sys.platform == 'win32':
 
521
            os.remove(rhname)
 
522
        os.rename(tmprhname, rhname)
 
523
        
 
524
 
 
525
 
 
526
    def get_revision(self, revision_id):
 
527
        """Return the Revision object for a named revision"""
 
528
        r = Revision.read_xml(self.revision_store[revision_id])
 
529
        assert r.revision_id == revision_id
 
530
        return r
 
531
 
 
532
 
 
533
    def get_inventory(self, inventory_id):
 
534
        """Get Inventory object by hash.
 
535
 
 
536
        TODO: Perhaps for this and similar methods, take a revision
 
537
               parameter which can be either an integer revno or a
 
538
               string hash."""
 
539
        i = Inventory.read_xml(self.inventory_store[inventory_id])
 
540
        return i
 
541
 
 
542
 
 
543
    def get_revision_inventory(self, revision_id):
 
544
        """Return inventory of a past revision."""
 
545
        if revision_id == None:
 
546
            return Inventory()
 
547
        else:
 
548
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
 
549
 
 
550
 
 
551
    @with_readlock
254
552
    def revision_history(self):
255
 
        """Return sequence of revision hashes on to this branch."""
256
 
        raise NotImplementedError('revision_history is abstract')
 
553
        """Return sequence of revision hashes on to this branch.
 
554
 
 
555
        >>> ScratchBranch().revision_history()
 
556
        []
 
557
        """
 
558
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
 
559
 
 
560
 
 
561
    def enum_history(self, direction):
 
562
        """Return (revno, revision_id) for history of branch.
 
563
 
 
564
        direction
 
565
            'forward' is from earliest to latest
 
566
            'reverse' is from latest to earliest
 
567
        """
 
568
        rh = self.revision_history()
 
569
        if direction == 'forward':
 
570
            i = 1
 
571
            for rid in rh:
 
572
                yield i, rid
 
573
                i += 1
 
574
        elif direction == 'reverse':
 
575
            i = len(rh)
 
576
            while i > 0:
 
577
                yield i, rh[i-1]
 
578
                i -= 1
 
579
        else:
 
580
            raise ValueError('invalid history direction', direction)
 
581
 
257
582
 
258
583
    def revno(self):
259
584
        """Return current revision number for this branch.
263
588
        """
264
589
        return len(self.revision_history())
265
590
 
266
 
    def unbind(self):
267
 
        """Older format branches cannot bind or unbind."""
268
 
        raise errors.UpgradeRequired(self.base)
269
591
 
270
 
    def last_revision(self):
271
 
        """Return last patch hash, or None if no history."""
 
592
    def last_patch(self):
 
593
        """Return last patch hash, or None if no history.
 
594
        """
272
595
        ph = self.revision_history()
273
596
        if ph:
274
597
            return ph[-1]
275
598
        else:
276
599
            return None
277
600
 
278
 
    def missing_revisions(self, other, stop_revision=None):
279
 
        """Return a list of new revisions that would perfectly fit.
 
601
 
 
602
    def commit(self, *args, **kw):
 
603
        """Deprecated"""
 
604
        from bzrlib.commit import commit
 
605
        commit(self, *args, **kw)
280
606
        
281
 
        If self and other have not diverged, return a list of the revisions
282
 
        present in other, but missing from self.
283
 
 
284
 
        >>> from bzrlib.workingtree import WorkingTree
285
 
        >>> bzrlib.trace.silent = True
286
 
        >>> d1 = bzrdir.ScratchDir()
287
 
        >>> br1 = d1.open_branch()
288
 
        >>> wt1 = d1.open_workingtree()
289
 
        >>> d2 = bzrdir.ScratchDir()
290
 
        >>> br2 = d2.open_branch()
291
 
        >>> wt2 = d2.open_workingtree()
292
 
        >>> br1.missing_revisions(br2)
293
 
        []
294
 
        >>> wt2.commit("lala!", rev_id="REVISION-ID-1")
295
 
        >>> br1.missing_revisions(br2)
296
 
        [u'REVISION-ID-1']
297
 
        >>> br2.missing_revisions(br1)
298
 
        []
299
 
        >>> wt1.commit("lala!", rev_id="REVISION-ID-1")
300
 
        >>> br1.missing_revisions(br2)
301
 
        []
302
 
        >>> wt2.commit("lala!", rev_id="REVISION-ID-2A")
303
 
        >>> br1.missing_revisions(br2)
304
 
        [u'REVISION-ID-2A']
305
 
        >>> wt1.commit("lala!", rev_id="REVISION-ID-2B")
306
 
        >>> br1.missing_revisions(br2)
307
 
        Traceback (most recent call last):
308
 
        DivergedBranches: These branches have diverged.  Try merge.
309
 
        """
310
 
        self_history = self.revision_history()
311
 
        self_len = len(self_history)
312
 
        other_history = other.revision_history()
313
 
        other_len = len(other_history)
314
 
        common_index = min(self_len, other_len) -1
315
 
        if common_index >= 0 and \
316
 
            self_history[common_index] != other_history[common_index]:
317
 
            raise DivergedBranches(self, other)
318
 
 
319
 
        if stop_revision is None:
320
 
            stop_revision = other_len
321
 
        else:
322
 
            assert isinstance(stop_revision, int)
323
 
            if stop_revision > other_len:
324
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
325
 
        return other_history[self_len:stop_revision]
326
 
 
327
 
    def update_revisions(self, other, stop_revision=None):
328
 
        """Pull in new perfect-fit revisions.
329
 
 
330
 
        :param other: Another Branch to pull from
331
 
        :param stop_revision: Updated until the given revision
332
 
        :return: None
333
 
        """
334
 
        raise NotImplementedError('update_revisions is abstract')
335
 
 
336
 
    def revision_id_to_revno(self, revision_id):
337
 
        """Given a revision id, return its revno"""
338
 
        if revision_id is None:
339
 
            return 0
340
 
        history = self.revision_history()
341
 
        try:
342
 
            return history.index(revision_id) + 1
343
 
        except ValueError:
344
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
345
 
 
346
 
    def get_rev_id(self, revno, history=None):
347
 
        """Find the revision id of the specified revno."""
 
607
 
 
608
    def lookup_revision(self, revno):
 
609
        """Return revision hash for revision number."""
348
610
        if revno == 0:
349
611
            return None
350
 
        if history is None:
351
 
            history = self.revision_history()
352
 
        elif revno <= 0 or revno > len(history):
353
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
354
 
        return history[revno - 1]
355
 
 
356
 
    def pull(self, source, overwrite=False, stop_revision=None):
357
 
        raise NotImplementedError('pull is abstract')
 
612
 
 
613
        try:
 
614
            # list is 0-based; revisions are 1-based
 
615
            return self.revision_history()[revno-1]
 
616
        except IndexError:
 
617
            raise BzrError("no such revision %s" % revno)
 
618
 
 
619
 
 
620
    def revision_tree(self, revision_id):
 
621
        """Return Tree for a revision on this branch.
 
622
 
 
623
        `revision_id` may be None for the null revision, in which case
 
624
        an `EmptyTree` is returned."""
 
625
        # TODO: refactor this to use an existing revision object
 
626
        # so we don't need to read it in twice.
 
627
        if revision_id == None:
 
628
            return EmptyTree()
 
629
        else:
 
630
            inv = self.get_revision_inventory(revision_id)
 
631
            return RevisionTree(self.text_store, inv)
 
632
 
 
633
 
 
634
    def working_tree(self):
 
635
        """Return a `Tree` for the working copy."""
 
636
        from workingtree import WorkingTree
 
637
        return WorkingTree(self.base, self.read_working_inventory())
 
638
 
358
639
 
359
640
    def basis_tree(self):
360
641
        """Return `Tree` object for last revision.
361
642
 
362
643
        If there are no revisions yet, return an `EmptyTree`.
363
644
        """
364
 
        return self.repository.revision_tree(self.last_revision())
365
 
 
 
645
        r = self.last_patch()
 
646
        if r == None:
 
647
            return EmptyTree()
 
648
        else:
 
649
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
 
650
 
 
651
 
 
652
 
 
653
    @with_writelock
366
654
    def rename_one(self, from_rel, to_rel):
367
655
        """Rename one file.
368
656
 
369
657
        This can change the directory or the filename or both.
370
658
        """
371
 
        raise NotImplementedError('rename_one is abstract')
372
 
 
 
659
        tree = self.working_tree()
 
660
        inv = tree.inventory
 
661
        if not tree.has_filename(from_rel):
 
662
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
 
663
        if tree.has_filename(to_rel):
 
664
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
665
 
 
666
        file_id = inv.path2id(from_rel)
 
667
        if file_id == None:
 
668
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
 
669
 
 
670
        if inv.path2id(to_rel):
 
671
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
 
672
 
 
673
        to_dir, to_tail = os.path.split(to_rel)
 
674
        to_dir_id = inv.path2id(to_dir)
 
675
        if to_dir_id == None and to_dir != '':
 
676
            raise BzrError("can't determine destination directory id for %r" % to_dir)
 
677
 
 
678
        mutter("rename_one:")
 
679
        mutter("  file_id    {%s}" % file_id)
 
680
        mutter("  from_rel   %r" % from_rel)
 
681
        mutter("  to_rel     %r" % to_rel)
 
682
        mutter("  to_dir     %r" % to_dir)
 
683
        mutter("  to_dir_id  {%s}" % to_dir_id)
 
684
 
 
685
        inv.rename(file_id, to_dir_id, to_tail)
 
686
 
 
687
        print "%s => %s" % (from_rel, to_rel)
 
688
 
 
689
        from_abs = self.abspath(from_rel)
 
690
        to_abs = self.abspath(to_rel)
 
691
        try:
 
692
            os.rename(from_abs, to_abs)
 
693
        except OSError, e:
 
694
            raise BzrError("failed to rename %r to %r: %s"
 
695
                    % (from_abs, to_abs, e[1]),
 
696
                    ["rename rolled back"])
 
697
 
 
698
        self._write_inventory(inv)
 
699
 
 
700
 
 
701
 
 
702
    @with_writelock
373
703
    def move(self, from_paths, to_name):
374
704
        """Rename files.
375
705
 
380
710
 
381
711
        Note that to_name is only the last component of the new name;
382
712
        this doesn't change the directory.
383
 
 
384
 
        This returns a list of (from_path, to_path) pairs for each
385
 
        entry that is moved.
386
 
        """
387
 
        raise NotImplementedError('move is abstract')
388
 
 
389
 
    def get_parent(self):
390
 
        """Return the parent location of the branch.
391
 
 
392
 
        This is the default location for push/pull/missing.  The usual
393
 
        pattern is that the user can override it by specifying a
394
 
        location.
395
 
        """
396
 
        raise NotImplementedError('get_parent is abstract')
397
 
 
398
 
    def get_push_location(self):
399
 
        """Return the None or the location to push this branch to."""
400
 
        raise NotImplementedError('get_push_location is abstract')
401
 
 
402
 
    def set_push_location(self, location):
403
 
        """Set a new push location for this branch."""
404
 
        raise NotImplementedError('set_push_location is abstract')
405
 
 
406
 
    def set_parent(self, url):
407
 
        raise NotImplementedError('set_parent is abstract')
408
 
 
409
 
    @needs_write_lock
410
 
    def update(self):
411
 
        """Synchronise this branch with the master branch if any. 
412
 
 
413
 
        :return: None or the last_revision pivoted out during the update.
414
 
        """
415
 
        return None
416
 
 
417
 
    def check_revno(self, revno):
418
 
        """\
419
 
        Check whether a revno corresponds to any revision.
420
 
        Zero (the NULL revision) is considered valid.
421
 
        """
422
 
        if revno != 0:
423
 
            self.check_real_revno(revno)
 
713
        """
 
714
        ## TODO: Option to move IDs only
 
715
        assert not isinstance(from_paths, basestring)
 
716
        tree = self.working_tree()
 
717
        inv = tree.inventory
 
718
        to_abs = self.abspath(to_name)
 
719
        if not isdir(to_abs):
 
720
            raise BzrError("destination %r is not a directory" % to_abs)
 
721
        if not tree.has_filename(to_name):
 
722
            raise BzrError("destination %r not in working directory" % to_abs)
 
723
        to_dir_id = inv.path2id(to_name)
 
724
        if to_dir_id == None and to_name != '':
 
725
            raise BzrError("destination %r is not a versioned directory" % to_name)
 
726
        to_dir_ie = inv[to_dir_id]
 
727
        if to_dir_ie.kind not in ('directory', 'root_directory'):
 
728
            raise BzrError("destination %r is not a directory" % to_abs)
 
729
 
 
730
        to_idpath = inv.get_idpath(to_dir_id)
 
731
 
 
732
        for f in from_paths:
 
733
            if not tree.has_filename(f):
 
734
                raise BzrError("%r does not exist in working tree" % f)
 
735
            f_id = inv.path2id(f)
 
736
            if f_id == None:
 
737
                raise BzrError("%r is not versioned" % f)
 
738
            name_tail = splitpath(f)[-1]
 
739
            dest_path = appendpath(to_name, name_tail)
 
740
            if tree.has_filename(dest_path):
 
741
                raise BzrError("destination %r already exists" % dest_path)
 
742
            if f_id in to_idpath:
 
743
                raise BzrError("can't move %r to a subdirectory of itself" % f)
 
744
 
 
745
        # OK, so there's a race here, it's possible that someone will
 
746
        # create a file in this interval and then the rename might be
 
747
        # left half-done.  But we should have caught most problems.
 
748
 
 
749
        for f in from_paths:
 
750
            name_tail = splitpath(f)[-1]
 
751
            dest_path = appendpath(to_name, name_tail)
 
752
            print "%s => %s" % (f, dest_path)
 
753
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
 
754
            try:
 
755
                os.rename(self.abspath(f), self.abspath(dest_path))
 
756
            except OSError, e:
 
757
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
 
758
                        ["rename rolled back"])
 
759
 
 
760
        self._write_inventory(inv)
 
761
 
 
762
 
 
763
 
 
764
 
 
765
class ScratchBranch(Branch):
 
766
    """Special test class: a branch that cleans up after itself.
 
767
 
 
768
    >>> b = ScratchBranch()
 
769
    >>> isdir(b.base)
 
770
    True
 
771
    >>> bd = b.base
 
772
    >>> b.destroy()
 
773
    >>> isdir(bd)
 
774
    False
 
775
    """
 
776
    def __init__(self, files=[], dirs=[]):
 
777
        """Make a test branch.
 
778
 
 
779
        This creates a temporary directory and runs init-tree in it.
 
780
 
 
781
        If any files are listed, they are created in the working copy.
 
782
        """
 
783
        Branch.__init__(self, tempfile.mkdtemp(), init=True)
 
784
        for d in dirs:
 
785
            os.mkdir(self.abspath(d))
424
786
            
425
 
    def check_real_revno(self, revno):
426
 
        """\
427
 
        Check whether a revno corresponds to a real revision.
428
 
        Zero (the NULL revision) is considered invalid
429
 
        """
430
 
        if revno < 1 or revno > self.revno():
431
 
            raise InvalidRevisionNumber(revno)
432
 
 
433
 
    @needs_read_lock
434
 
    def clone(self, *args, **kwargs):
435
 
        """Clone this branch into to_bzrdir preserving all semantic values.
436
 
        
437
 
        revision_id: if not None, the revision history in the new branch will
438
 
                     be truncated to end with revision_id.
439
 
        """
440
 
        # for API compatability, until 0.8 releases we provide the old api:
441
 
        # def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
442
 
        # after 0.8 releases, the *args and **kwargs should be changed:
443
 
        # def clone(self, to_bzrdir, revision_id=None):
444
 
        if (kwargs.get('to_location', None) or
445
 
            kwargs.get('revision', None) or
446
 
            kwargs.get('basis_branch', None) or
447
 
            (len(args) and isinstance(args[0], basestring))):
448
 
            # backwards compatability api:
449
 
            warn("Branch.clone() has been deprecated for BzrDir.clone() from"
450
 
                 " bzrlib 0.8.", DeprecationWarning, stacklevel=3)
451
 
            # get basis_branch
452
 
            if len(args) > 2:
453
 
                basis_branch = args[2]
454
 
            else:
455
 
                basis_branch = kwargs.get('basis_branch', None)
456
 
            if basis_branch:
457
 
                basis = basis_branch.bzrdir
458
 
            else:
459
 
                basis = None
460
 
            # get revision
461
 
            if len(args) > 1:
462
 
                revision_id = args[1]
463
 
            else:
464
 
                revision_id = kwargs.get('revision', None)
465
 
            # get location
466
 
            if len(args):
467
 
                url = args[0]
468
 
            else:
469
 
                # no default to raise if not provided.
470
 
                url = kwargs.get('to_location')
471
 
            return self.bzrdir.clone(url,
472
 
                                     revision_id=revision_id,
473
 
                                     basis=basis).open_branch()
474
 
        # new cleaner api.
475
 
        # generate args by hand 
476
 
        if len(args) > 1:
477
 
            revision_id = args[1]
478
 
        else:
479
 
            revision_id = kwargs.get('revision_id', None)
480
 
        if len(args):
481
 
            to_bzrdir = args[0]
482
 
        else:
483
 
            # no default to raise if not provided.
484
 
            to_bzrdir = kwargs.get('to_bzrdir')
485
 
        result = self._format.initialize(to_bzrdir)
486
 
        self.copy_content_into(result, revision_id=revision_id)
487
 
        return  result
488
 
 
489
 
    @needs_read_lock
490
 
    def sprout(self, to_bzrdir, revision_id=None):
491
 
        """Create a new line of development from the branch, into to_bzrdir.
492
 
        
493
 
        revision_id: if not None, the revision history in the new branch will
494
 
                     be truncated to end with revision_id.
495
 
        """
496
 
        result = self._format.initialize(to_bzrdir)
497
 
        self.copy_content_into(result, revision_id=revision_id)
498
 
        result.set_parent(self.bzrdir.root_transport.base)
499
 
        return result
500
 
 
501
 
    @needs_read_lock
502
 
    def copy_content_into(self, destination, revision_id=None):
503
 
        """Copy the content of self into destination.
504
 
 
505
 
        revision_id: if not None, the revision history in the new branch will
506
 
                     be truncated to end with revision_id.
507
 
        """
508
 
        new_history = self.revision_history()
509
 
        if revision_id is not None:
510
 
            try:
511
 
                new_history = new_history[:new_history.index(revision_id) + 1]
512
 
            except ValueError:
513
 
                rev = self.repository.get_revision(revision_id)
514
 
                new_history = rev.get_history(self.repository)[1:]
515
 
        destination.set_revision_history(new_history)
516
 
        parent = self.get_parent()
517
 
        if parent:
518
 
            destination.set_parent(parent)
519
 
 
520
 
 
521
 
class BranchFormat(object):
522
 
    """An encapsulation of the initialization and open routines for a format.
523
 
 
524
 
    Formats provide three things:
525
 
     * An initialization routine,
526
 
     * a format string,
527
 
     * an open routine.
528
 
 
529
 
    Formats are placed in an dict by their format string for reference 
530
 
    during branch opening. Its not required that these be instances, they
531
 
    can be classes themselves with class methods - it simply depends on 
532
 
    whether state is needed for a given format or not.
533
 
 
534
 
    Once a format is deprecated, just deprecate the initialize and open
535
 
    methods on the format class. Do not deprecate the object, as the 
536
 
    object will be created every time regardless.
537
 
    """
538
 
 
539
 
    _default_format = None
540
 
    """The default format used for new branches."""
541
 
 
542
 
    _formats = {}
543
 
    """The known formats."""
544
 
 
545
 
    @classmethod
546
 
    def find_format(klass, a_bzrdir):
547
 
        """Return the format for the branch object in a_bzrdir."""
548
 
        try:
549
 
            transport = a_bzrdir.get_branch_transport(None)
550
 
            format_string = transport.get("format").read()
551
 
            return klass._formats[format_string]
552
 
        except NoSuchFile:
553
 
            raise NotBranchError(path=transport.base)
554
 
        except KeyError:
555
 
            raise errors.UnknownFormatError(format_string)
556
 
 
557
 
    @classmethod
558
 
    def get_default_format(klass):
559
 
        """Return the current default format."""
560
 
        return klass._default_format
561
 
 
562
 
    def get_format_string(self):
563
 
        """Return the ASCII format string that identifies this format."""
564
 
        raise NotImplementedError(self.get_format_string)
565
 
 
566
 
    def get_format_description(self):
567
 
        """Return the short format description for this format."""
568
 
        raise NotImplementedError(self.get_format_string)
569
 
 
570
 
    def initialize(self, a_bzrdir):
571
 
        """Create a branch of this format in a_bzrdir."""
572
 
        raise NotImplementedError(self.initialized)
573
 
 
574
 
    def is_supported(self):
575
 
        """Is this format supported?
576
 
 
577
 
        Supported formats can be initialized and opened.
578
 
        Unsupported formats may not support initialization or committing or 
579
 
        some other features depending on the reason for not being supported.
580
 
        """
581
 
        return True
582
 
 
583
 
    def open(self, a_bzrdir, _found=False):
584
 
        """Return the branch object for a_bzrdir
585
 
 
586
 
        _found is a private parameter, do not use it. It is used to indicate
587
 
               if format probing has already be done.
588
 
        """
589
 
        raise NotImplementedError(self.open)
590
 
 
591
 
    @classmethod
592
 
    def register_format(klass, format):
593
 
        klass._formats[format.get_format_string()] = format
594
 
 
595
 
    @classmethod
596
 
    def set_default_format(klass, format):
597
 
        klass._default_format = format
598
 
 
599
 
    @classmethod
600
 
    def unregister_format(klass, format):
601
 
        assert klass._formats[format.get_format_string()] is format
602
 
        del klass._formats[format.get_format_string()]
603
 
 
604
 
    def __str__(self):
605
 
        return self.get_format_string().rstrip()
606
 
 
607
 
 
608
 
class BzrBranchFormat4(BranchFormat):
609
 
    """Bzr branch format 4.
610
 
 
611
 
    This format has:
612
 
     - a revision-history file.
613
 
     - a branch-lock lock file [ to be shared with the bzrdir ]
614
 
    """
615
 
 
616
 
    def get_format_description(self):
617
 
        """See BranchFormat.get_format_description()."""
618
 
        return "Branch format 4"
619
 
 
620
 
    def initialize(self, a_bzrdir):
621
 
        """Create a branch of this format in a_bzrdir."""
622
 
        mutter('creating branch in %s', a_bzrdir.transport.base)
623
 
        branch_transport = a_bzrdir.get_branch_transport(self)
624
 
        utf8_files = [('revision-history', ''),
625
 
                      ('branch-name', ''),
626
 
                      ]
627
 
        control_files = LockableFiles(branch_transport, 'branch-lock',
628
 
                                      TransportLock)
629
 
        control_files.create_lock()
630
 
        control_files.lock_write()
631
 
        try:
632
 
            for file, content in utf8_files:
633
 
                control_files.put_utf8(file, content)
634
 
        finally:
635
 
            control_files.unlock()
636
 
        return self.open(a_bzrdir, _found=True)
637
 
 
638
 
    def __init__(self):
639
 
        super(BzrBranchFormat4, self).__init__()
640
 
        self._matchingbzrdir = bzrdir.BzrDirFormat6()
641
 
 
642
 
    def open(self, a_bzrdir, _found=False):
643
 
        """Return the branch object for a_bzrdir
644
 
 
645
 
        _found is a private parameter, do not use it. It is used to indicate
646
 
               if format probing has already be done.
647
 
        """
648
 
        if not _found:
649
 
            # we are being called directly and must probe.
650
 
            raise NotImplementedError
651
 
        return BzrBranch(_format=self,
652
 
                         _control_files=a_bzrdir._control_files,
653
 
                         a_bzrdir=a_bzrdir,
654
 
                         _repository=a_bzrdir.open_repository())
655
 
 
656
 
    def __str__(self):
657
 
        return "Bazaar-NG branch format 4"
658
 
 
659
 
 
660
 
class BzrBranchFormat5(BranchFormat):
661
 
    """Bzr branch format 5.
662
 
 
663
 
    This format has:
664
 
     - a revision-history file.
665
 
     - a format string
666
 
     - a lock dir guarding the branch itself
667
 
     - all of this stored in a branch/ subdirectory
668
 
     - works with shared repositories.
669
 
 
670
 
    This format is new in bzr 0.8.
671
 
    """
672
 
 
673
 
    def get_format_string(self):
674
 
        """See BranchFormat.get_format_string()."""
675
 
        return "Bazaar-NG branch format 5\n"
676
 
 
677
 
    def get_format_description(self):
678
 
        """See BranchFormat.get_format_description()."""
679
 
        return "Branch format 5"
680
 
        
681
 
    def initialize(self, a_bzrdir):
682
 
        """Create a branch of this format in a_bzrdir."""
683
 
        mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
684
 
        branch_transport = a_bzrdir.get_branch_transport(self)
685
 
        utf8_files = [('revision-history', ''),
686
 
                      ('branch-name', ''),
687
 
                      ]
688
 
        control_files = LockableFiles(branch_transport, 'lock', LockDir)
689
 
        control_files.create_lock()
690
 
        control_files.lock_write()
691
 
        control_files.put_utf8('format', self.get_format_string())
692
 
        try:
693
 
            for file, content in utf8_files:
694
 
                control_files.put_utf8(file, content)
695
 
        finally:
696
 
            control_files.unlock()
697
 
        return self.open(a_bzrdir, _found=True, )
698
 
 
699
 
    def __init__(self):
700
 
        super(BzrBranchFormat5, self).__init__()
701
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
702
 
 
703
 
    def open(self, a_bzrdir, _found=False):
704
 
        """Return the branch object for a_bzrdir
705
 
 
706
 
        _found is a private parameter, do not use it. It is used to indicate
707
 
               if format probing has already be done.
708
 
        """
709
 
        if not _found:
710
 
            format = BranchFormat.find_format(a_bzrdir)
711
 
            assert format.__class__ == self.__class__
712
 
        transport = a_bzrdir.get_branch_transport(None)
713
 
        control_files = LockableFiles(transport, 'lock', LockDir)
714
 
        return BzrBranch5(_format=self,
715
 
                          _control_files=control_files,
716
 
                          a_bzrdir=a_bzrdir,
717
 
                          _repository=a_bzrdir.find_repository())
718
 
 
719
 
    def __str__(self):
720
 
        return "Bazaar-NG Metadir branch format 5"
721
 
 
722
 
 
723
 
class BranchReferenceFormat(BranchFormat):
724
 
    """Bzr branch reference format.
725
 
 
726
 
    Branch references are used in implementing checkouts, they
727
 
    act as an alias to the real branch which is at some other url.
728
 
 
729
 
    This format has:
730
 
     - A location file
731
 
     - a format string
732
 
    """
733
 
 
734
 
    def get_format_string(self):
735
 
        """See BranchFormat.get_format_string()."""
736
 
        return "Bazaar-NG Branch Reference Format 1\n"
737
 
 
738
 
    def get_format_description(self):
739
 
        """See BranchFormat.get_format_description()."""
740
 
        return "Checkout reference format 1"
741
 
        
742
 
    def initialize(self, a_bzrdir, target_branch=None):
743
 
        """Create a branch of this format in a_bzrdir."""
744
 
        if target_branch is None:
745
 
            # this format does not implement branch itself, thus the implicit
746
 
            # creation contract must see it as uninitializable
747
 
            raise errors.UninitializableFormat(self)
748
 
        mutter('creating branch reference in %s', a_bzrdir.transport.base)
749
 
        branch_transport = a_bzrdir.get_branch_transport(self)
750
 
        # FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
751
 
        branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
752
 
        branch_transport.put('format', StringIO(self.get_format_string()))
753
 
        return self.open(a_bzrdir, _found=True)
754
 
 
755
 
    def __init__(self):
756
 
        super(BranchReferenceFormat, self).__init__()
757
 
        self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
758
 
 
759
 
    def _make_reference_clone_function(format, a_branch):
760
 
        """Create a clone() routine for a branch dynamically."""
761
 
        def clone(to_bzrdir, revision_id=None):
762
 
            """See Branch.clone()."""
763
 
            return format.initialize(to_bzrdir, a_branch)
764
 
            # cannot obey revision_id limits when cloning a reference ...
765
 
            # FIXME RBC 20060210 either nuke revision_id for clone, or
766
 
            # emit some sort of warning/error to the caller ?!
767
 
        return clone
768
 
 
769
 
    def open(self, a_bzrdir, _found=False):
770
 
        """Return the branch that the branch reference in a_bzrdir points at.
771
 
 
772
 
        _found is a private parameter, do not use it. It is used to indicate
773
 
               if format probing has already be done.
774
 
        """
775
 
        if not _found:
776
 
            format = BranchFormat.find_format(a_bzrdir)
777
 
            assert format.__class__ == self.__class__
778
 
        transport = a_bzrdir.get_branch_transport(None)
779
 
        real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
780
 
        result = real_bzrdir.open_branch()
781
 
        # this changes the behaviour of result.clone to create a new reference
782
 
        # rather than a copy of the content of the branch.
783
 
        # I did not use a proxy object because that needs much more extensive
784
 
        # testing, and we are only changing one behaviour at the moment.
785
 
        # If we decide to alter more behaviours - i.e. the implicit nickname
786
 
        # then this should be refactored to introduce a tested proxy branch
787
 
        # and a subclass of that for use in overriding clone() and ....
788
 
        # - RBC 20060210
789
 
        result.clone = self._make_reference_clone_function(result)
790
 
        return result
791
 
 
792
 
 
793
 
# formats which have no format string are not discoverable
794
 
# and not independently creatable, so are not registered.
795
 
__default_format = BzrBranchFormat5()
796
 
BranchFormat.register_format(__default_format)
797
 
BranchFormat.register_format(BranchReferenceFormat())
798
 
BranchFormat.set_default_format(__default_format)
799
 
_legacy_formats = [BzrBranchFormat4(),
800
 
                   ]
801
 
 
802
 
class BzrBranch(Branch):
803
 
    """A branch stored in the actual filesystem.
804
 
 
805
 
    Note that it's "local" in the context of the filesystem; it doesn't
806
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
807
 
    it's writable, and can be accessed via the normal filesystem API.
808
 
    """
 
787
        for f in files:
 
788
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
789
 
 
790
 
 
791
    def __del__(self):
 
792
        self.destroy()
 
793
 
 
794
    def destroy(self):
 
795
        """Destroy the test branch, removing the scratch directory."""
 
796
        try:
 
797
            mutter("delete ScratchBranch %s" % self.base)
 
798
            shutil.rmtree(self.base)
 
799
        except OSError, e:
 
800
            # Work around for shutil.rmtree failing on Windows when
 
801
            # readonly files are encountered
 
802
            mutter("hit exception in destroying ScratchBranch: %s" % e)
 
803
            for root, dirs, files in os.walk(self.base, topdown=False):
 
804
                for name in files:
 
805
                    os.chmod(os.path.join(root, name), 0700)
 
806
            shutil.rmtree(self.base)
 
807
        self.base = None
 
808
 
809
809
    
810
 
    def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
811
 
                 relax_version_check=DEPRECATED_PARAMETER, _format=None,
812
 
                 _control_files=None, a_bzrdir=None, _repository=None):
813
 
        """Create new branch object at a particular location.
814
 
 
815
 
        transport -- A Transport object, defining how to access files.
816
 
        
817
 
        init -- If True, create new control files in a previously
818
 
             unversioned directory.  If False, the branch must already
819
 
             be versioned.
820
 
 
821
 
        relax_version_check -- If true, the usual check for the branch
822
 
            version is not applied.  This is intended only for
823
 
            upgrade/recovery type use; it's not guaranteed that
824
 
            all operations will work on old format branches.
825
 
        """
826
 
        if a_bzrdir is None:
827
 
            self.bzrdir = bzrdir.BzrDir.open(transport.base)
828
 
        else:
829
 
            self.bzrdir = a_bzrdir
830
 
        self._transport = self.bzrdir.transport.clone('..')
831
 
        self._base = self._transport.base
832
 
        self._format = _format
833
 
        if _control_files is None:
834
 
            raise BzrBadParameterMissing('_control_files')
835
 
        self.control_files = _control_files
836
 
        if deprecated_passed(init):
837
 
            warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
838
 
                 "deprecated as of bzr 0.8. Please use Branch.create().",
839
 
                 DeprecationWarning,
840
 
                 stacklevel=2)
841
 
            if init:
842
 
                # this is slower than before deprecation, oh well never mind.
843
 
                # -> its deprecated.
844
 
                self._initialize(transport.base)
845
 
        self._check_format(_format)
846
 
        if deprecated_passed(relax_version_check):
847
 
            warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
848
 
                 "relax_version_check parameter is deprecated as of bzr 0.8. "
849
 
                 "Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
850
 
                 "open() method.",
851
 
                 DeprecationWarning,
852
 
                 stacklevel=2)
853
 
            if (not relax_version_check
854
 
                and not self._format.is_supported()):
855
 
                raise errors.UnsupportedFormatError(
856
 
                        'sorry, branch format %r not supported' % fmt,
857
 
                        ['use a different bzr version',
858
 
                         'or remove the .bzr directory'
859
 
                         ' and "bzr init" again'])
860
 
        if deprecated_passed(transport):
861
 
            warn("BzrBranch.__init__(transport=XXX...): The transport "
862
 
                 "parameter is deprecated as of bzr 0.8. "
863
 
                 "Please use Branch.open, or bzrdir.open_branch().",
864
 
                 DeprecationWarning,
865
 
                 stacklevel=2)
866
 
        self.repository = _repository
867
 
 
868
 
    def __str__(self):
869
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
870
 
 
871
 
    __repr__ = __str__
872
 
 
873
 
    def __del__(self):
874
 
        # TODO: It might be best to do this somewhere else,
875
 
        # but it is nice for a Branch object to automatically
876
 
        # cache it's information.
877
 
        # Alternatively, we could have the Transport objects cache requests
878
 
        # See the earlier discussion about how major objects (like Branch)
879
 
        # should never expect their __del__ function to run.
880
 
        # XXX: cache_root seems to be unused, 2006-01-13 mbp
881
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
882
 
            try:
883
 
                shutil.rmtree(self.cache_root)
884
 
            except:
885
 
                pass
886
 
            self.cache_root = None
887
 
 
888
 
    def _get_base(self):
889
 
        return self._base
890
 
 
891
 
    base = property(_get_base, doc="The URL for the root of this branch.")
892
 
 
893
 
    def _finish_transaction(self):
894
 
        """Exit the current transaction."""
895
 
        return self.control_files._finish_transaction()
896
 
 
897
 
    def get_transaction(self):
898
 
        """Return the current active transaction.
899
 
 
900
 
        If no transaction is active, this returns a passthrough object
901
 
        for which all data is immediately flushed and no caching happens.
902
 
        """
903
 
        # this is an explicit function so that we can do tricky stuff
904
 
        # when the storage in rev_storage is elsewhere.
905
 
        # we probably need to hook the two 'lock a location' and 
906
 
        # 'have a transaction' together more delicately, so that
907
 
        # we can have two locks (branch and storage) and one transaction
908
 
        # ... and finishing the transaction unlocks both, but unlocking
909
 
        # does not. - RBC 20051121
910
 
        return self.control_files.get_transaction()
911
 
 
912
 
    def _set_transaction(self, transaction):
913
 
        """Set a new active transaction."""
914
 
        return self.control_files._set_transaction(transaction)
915
 
 
916
 
    def abspath(self, name):
917
 
        """See Branch.abspath."""
918
 
        return self.control_files._transport.abspath(name)
919
 
 
920
 
    def _check_format(self, format):
921
 
        """Identify the branch format if needed.
922
 
 
923
 
        The format is stored as a reference to the format object in
924
 
        self._format for code that needs to check it later.
925
 
 
926
 
        The format parameter is either None or the branch format class
927
 
        used to open this branch.
928
 
 
929
 
        FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
930
 
        """
931
 
        if format is None:
932
 
            format = BzrBranchFormat.find_format(self.bzrdir)
933
 
        self._format = format
934
 
        mutter("got branch format %s", self._format)
935
 
 
936
 
    @needs_read_lock
937
 
    def get_root_id(self):
938
 
        """See Branch.get_root_id."""
939
 
        tree = self.repository.revision_tree(self.last_revision())
940
 
        return tree.inventory.root.file_id
941
 
 
942
 
    def lock_write(self):
943
 
        # TODO: test for failed two phase locks. This is known broken.
944
 
        self.control_files.lock_write()
945
 
        self.repository.lock_write()
946
 
 
947
 
    def lock_read(self):
948
 
        # TODO: test for failed two phase locks. This is known broken.
949
 
        self.control_files.lock_read()
950
 
        self.repository.lock_read()
951
 
 
952
 
    def unlock(self):
953
 
        # TODO: test for failed two phase locks. This is known broken.
954
 
        self.repository.unlock()
955
 
        self.control_files.unlock()
956
 
        
957
 
    def peek_lock_mode(self):
958
 
        if self.control_files._lock_count == 0:
959
 
            return None
960
 
        else:
961
 
            return self.control_files._lock_mode
962
 
 
963
 
    @needs_read_lock
964
 
    def print_file(self, file, revision_id):
965
 
        """See Branch.print_file."""
966
 
        return self.repository.print_file(file, revision_id)
967
 
 
968
 
    @needs_write_lock
969
 
    def append_revision(self, *revision_ids):
970
 
        """See Branch.append_revision."""
971
 
        for revision_id in revision_ids:
972
 
            mutter("add {%s} to revision-history" % revision_id)
973
 
        rev_history = self.revision_history()
974
 
        rev_history.extend(revision_ids)
975
 
        self.set_revision_history(rev_history)
976
 
 
977
 
    @needs_write_lock
978
 
    def set_revision_history(self, rev_history):
979
 
        """See Branch.set_revision_history."""
980
 
        self.control_files.put_utf8(
981
 
            'revision-history', '\n'.join(rev_history))
982
 
        transaction = self.get_transaction()
983
 
        history = transaction.map.find_revision_history()
984
 
        if history is not None:
985
 
            # update the revision history in the identity map.
986
 
            history[:] = list(rev_history)
987
 
            # this call is disabled because revision_history is 
988
 
            # not really an object yet, and the transaction is for objects.
989
 
            # transaction.register_dirty(history)
990
 
        else:
991
 
            transaction.map.add_revision_history(rev_history)
992
 
            # this call is disabled because revision_history is 
993
 
            # not really an object yet, and the transaction is for objects.
994
 
            # transaction.register_clean(history)
995
 
 
996
 
    def get_revision_delta(self, revno):
997
 
        """Return the delta for one revision.
998
 
 
999
 
        The delta is relative to its mainline predecessor, or the
1000
 
        empty tree for revision 1.
1001
 
        """
1002
 
        assert isinstance(revno, int)
1003
 
        rh = self.revision_history()
1004
 
        if not (1 <= revno <= len(rh)):
1005
 
            raise InvalidRevisionNumber(revno)
1006
 
 
1007
 
        # revno is 1-based; list is 0-based
1008
 
 
1009
 
        new_tree = self.repository.revision_tree(rh[revno-1])
1010
 
        if revno == 1:
1011
 
            old_tree = EmptyTree()
1012
 
        else:
1013
 
            old_tree = self.repository.revision_tree(rh[revno-2])
1014
 
        return compare_trees(old_tree, new_tree)
1015
 
 
1016
 
    @needs_read_lock
1017
 
    def revision_history(self):
1018
 
        """See Branch.revision_history."""
1019
 
        transaction = self.get_transaction()
1020
 
        history = transaction.map.find_revision_history()
1021
 
        if history is not None:
1022
 
            mutter("cache hit for revision-history in %s", self)
1023
 
            return list(history)
1024
 
        history = [l.rstrip('\r\n') for l in
1025
 
                self.control_files.get_utf8('revision-history').readlines()]
1026
 
        transaction.map.add_revision_history(history)
1027
 
        # this call is disabled because revision_history is 
1028
 
        # not really an object yet, and the transaction is for objects.
1029
 
        # transaction.register_clean(history, precious=True)
1030
 
        return list(history)
1031
 
 
1032
 
    @needs_write_lock
1033
 
    def update_revisions(self, other, stop_revision=None):
1034
 
        """See Branch.update_revisions."""
1035
 
        other.lock_read()
1036
 
        try:
1037
 
            if stop_revision is None:
1038
 
                stop_revision = other.last_revision()
1039
 
                if stop_revision is None:
1040
 
                    # if there are no commits, we're done.
1041
 
                    return
1042
 
            # whats the current last revision, before we fetch [and change it
1043
 
            # possibly]
1044
 
            last_rev = self.last_revision()
1045
 
            # we fetch here regardless of whether we need to so that we pickup
1046
 
            # filled in ghosts.
1047
 
            self.fetch(other, stop_revision)
1048
 
            my_ancestry = self.repository.get_ancestry(last_rev)
1049
 
            if stop_revision in my_ancestry:
1050
 
                # last_revision is a descendant of stop_revision
1051
 
                return
1052
 
            # stop_revision must be a descendant of last_revision
1053
 
            stop_graph = self.repository.get_revision_graph(stop_revision)
1054
 
            if last_rev is not None and last_rev not in stop_graph:
1055
 
                # our previous tip is not merged into stop_revision
1056
 
                raise errors.DivergedBranches(self, other)
1057
 
            # make a new revision history from the graph
1058
 
            current_rev_id = stop_revision
1059
 
            new_history = []
1060
 
            while current_rev_id not in (None, NULL_REVISION):
1061
 
                new_history.append(current_rev_id)
1062
 
                current_rev_id_parents = stop_graph[current_rev_id]
1063
 
                try:
1064
 
                    current_rev_id = current_rev_id_parents[0]
1065
 
                except IndexError:
1066
 
                    current_rev_id = None
1067
 
            new_history.reverse()
1068
 
            self.set_revision_history(new_history)
1069
 
        finally:
1070
 
            other.unlock()
1071
 
 
1072
 
    @deprecated_method(zero_eight)
1073
 
    def pullable_revisions(self, other, stop_revision):
1074
 
        """Please use bzrlib.missing instead."""
1075
 
        other_revno = other.revision_id_to_revno(stop_revision)
1076
 
        try:
1077
 
            return self.missing_revisions(other, other_revno)
1078
 
        except DivergedBranches, e:
1079
 
            try:
1080
 
                pullable_revs = get_intervening_revisions(self.last_revision(),
1081
 
                                                          stop_revision, 
1082
 
                                                          self.repository)
1083
 
                assert self.last_revision() not in pullable_revs
1084
 
                return pullable_revs
1085
 
            except bzrlib.errors.NotAncestor:
1086
 
                if is_ancestor(self.last_revision(), stop_revision, self):
1087
 
                    return []
1088
 
                else:
1089
 
                    raise e
1090
 
        
1091
 
    def basis_tree(self):
1092
 
        """See Branch.basis_tree."""
1093
 
        return self.repository.revision_tree(self.last_revision())
1094
 
 
1095
 
    @deprecated_method(zero_eight)
1096
 
    def working_tree(self):
1097
 
        """Create a Working tree object for this branch."""
1098
 
        from bzrlib.workingtree import WorkingTree
1099
 
        from bzrlib.transport.local import LocalTransport
1100
 
        if (self.base.find('://') != -1 or 
1101
 
            not isinstance(self._transport, LocalTransport)):
1102
 
            raise NoWorkingTree(self.base)
1103
 
        return self.bzrdir.open_workingtree()
1104
 
 
1105
 
    @needs_write_lock
1106
 
    def pull(self, source, overwrite=False, stop_revision=None):
1107
 
        """See Branch.pull."""
1108
 
        source.lock_read()
1109
 
        try:
1110
 
            old_count = len(self.revision_history())
1111
 
            try:
1112
 
                self.update_revisions(source,stop_revision)
1113
 
            except DivergedBranches:
1114
 
                if not overwrite:
1115
 
                    raise
1116
 
            if overwrite:
1117
 
                self.set_revision_history(source.revision_history())
1118
 
            new_count = len(self.revision_history())
1119
 
            return new_count - old_count
1120
 
        finally:
1121
 
            source.unlock()
1122
 
 
1123
 
    def get_parent(self):
1124
 
        """See Branch.get_parent."""
1125
 
        import errno
1126
 
        _locs = ['parent', 'pull', 'x-pull']
1127
 
        for l in _locs:
1128
 
            try:
1129
 
                return self.control_files.get_utf8(l).read().strip('\n')
1130
 
            except NoSuchFile:
1131
 
                pass
1132
 
        return None
1133
 
 
1134
 
    def get_push_location(self):
1135
 
        """See Branch.get_push_location."""
1136
 
        config = bzrlib.config.BranchConfig(self)
1137
 
        push_loc = config.get_user_option('push_location')
1138
 
        return push_loc
1139
 
 
1140
 
    def set_push_location(self, location):
1141
 
        """See Branch.set_push_location."""
1142
 
        config = bzrlib.config.LocationConfig(self.base)
1143
 
        config.set_user_option('push_location', location)
1144
 
 
1145
 
    @needs_write_lock
1146
 
    def set_parent(self, url):
1147
 
        """See Branch.set_parent."""
1148
 
        # TODO: Maybe delete old location files?
1149
 
        # URLs should never be unicode, even on the local fs,
1150
 
        # FIXUP this and get_parent in a future branch format bump:
1151
 
        # read and rewrite the file, and have the new format code read
1152
 
        # using .get not .get_utf8. RBC 20060125
1153
 
        if url is None:
1154
 
            self.control_files._transport.delete('parent')
1155
 
        else:
1156
 
            self.control_files.put_utf8('parent', url + '\n')
1157
 
 
1158
 
    def tree_config(self):
1159
 
        return TreeConfig(self)
1160
 
 
1161
 
 
1162
 
class BzrBranch5(BzrBranch):
1163
 
    """A format 5 branch. This supports new features over plan branches.
1164
 
 
1165
 
    It has support for a master_branch which is the data for bound branches.
1166
 
    """
1167
 
 
1168
 
    def __init__(self,
1169
 
                 _format,
1170
 
                 _control_files,
1171
 
                 a_bzrdir,
1172
 
                 _repository):
1173
 
        super(BzrBranch5, self).__init__(_format=_format,
1174
 
                                         _control_files=_control_files,
1175
 
                                         a_bzrdir=a_bzrdir,
1176
 
                                         _repository=_repository)
1177
 
        
1178
 
    @needs_write_lock
1179
 
    def pull(self, source, overwrite=False, stop_revision=None):
1180
 
        """Updates branch.pull to be bound branch aware."""
1181
 
        bound_location = self.get_bound_location()
1182
 
        if source.base != bound_location:
1183
 
            # not pulling from master, so we need to update master.
1184
 
            master_branch = self.get_master_branch()
1185
 
            if master_branch:
1186
 
                master_branch.pull(source)
1187
 
                source = master_branch
1188
 
        return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1189
 
 
1190
 
    def get_bound_location(self):
1191
 
        try:
1192
 
            return self.control_files.get_utf8('bound').read()[:-1]
1193
 
        except errors.NoSuchFile:
1194
 
            return None
1195
 
 
1196
 
    @needs_read_lock
1197
 
    def get_master_branch(self):
1198
 
        """Return the branch we are bound to.
1199
 
        
1200
 
        :return: Either a Branch, or None
1201
 
 
1202
 
        This could memoise the branch, but if thats done
1203
 
        it must be revalidated on each new lock.
1204
 
        So for now we just dont memoise it.
1205
 
        # RBC 20060304 review this decision.
1206
 
        """
1207
 
        bound_loc = self.get_bound_location()
1208
 
        if not bound_loc:
1209
 
            return None
1210
 
        try:
1211
 
            return Branch.open(bound_loc)
1212
 
        except (errors.NotBranchError, errors.ConnectionError), e:
1213
 
            raise errors.BoundBranchConnectionFailure(
1214
 
                    self, bound_loc, e)
1215
 
 
1216
 
    @needs_write_lock
1217
 
    def set_bound_location(self, location):
1218
 
        """Set the target where this branch is bound to.
1219
 
 
1220
 
        :param location: URL to the target branch
1221
 
        """
1222
 
        if location:
1223
 
            self.control_files.put_utf8('bound', location+'\n')
1224
 
        else:
1225
 
            try:
1226
 
                self.control_files._transport.delete('bound')
1227
 
            except NoSuchFile:
1228
 
                return False
 
810
 
 
811
######################################################################
 
812
# predicates
 
813
 
 
814
 
 
815
def is_control_file(filename):
 
816
    ## FIXME: better check
 
817
    filename = os.path.normpath(filename)
 
818
    while filename != '':
 
819
        head, tail = os.path.split(filename)
 
820
        ## mutter('check %r for control file' % ((head, tail), ))
 
821
        if tail == bzrlib.BZRDIR:
1229
822
            return True
1230
 
 
1231
 
    @needs_write_lock
1232
 
    def bind(self, other):
1233
 
        """Bind the local branch the other branch.
1234
 
 
1235
 
        :param other: The branch to bind to
1236
 
        :type other: Branch
1237
 
        """
1238
 
        # TODO: jam 20051230 Consider checking if the target is bound
1239
 
        #       It is debatable whether you should be able to bind to
1240
 
        #       a branch which is itself bound.
1241
 
        #       Committing is obviously forbidden,
1242
 
        #       but binding itself may not be.
1243
 
        #       Since we *have* to check at commit time, we don't
1244
 
        #       *need* to check here
1245
 
        self.pull(other)
1246
 
 
1247
 
        # we are now equal to or a suffix of other.
1248
 
 
1249
 
        # Since we have 'pulled' from the remote location,
1250
 
        # now we should try to pull in the opposite direction
1251
 
        # in case the local tree has more revisions than the
1252
 
        # remote one.
1253
 
        # There may be a different check you could do here
1254
 
        # rather than actually trying to install revisions remotely.
1255
 
        # TODO: capture an exception which indicates the remote branch
1256
 
        #       is not writeable. 
1257
 
        #       If it is up-to-date, this probably should not be a failure
1258
 
        
1259
 
        # lock other for write so the revision-history syncing cannot race
1260
 
        other.lock_write()
1261
 
        try:
1262
 
            other.pull(self)
1263
 
            # if this does not error, other now has the same last rev we do
1264
 
            # it can only error if the pull from other was concurrent with
1265
 
            # a commit to other from someone else.
1266
 
 
1267
 
            # until we ditch revision-history, we need to sync them up:
1268
 
            self.set_revision_history(other.revision_history())
1269
 
            # now other and self are up to date with each other and have the
1270
 
            # same revision-history.
1271
 
        finally:
1272
 
            other.unlock()
1273
 
 
1274
 
        self.set_bound_location(other.base)
1275
 
 
1276
 
    @needs_write_lock
1277
 
    def unbind(self):
1278
 
        """If bound, unbind"""
1279
 
        return self.set_bound_location(None)
1280
 
 
1281
 
    @needs_write_lock
1282
 
    def update(self):
1283
 
        """Synchronise this branch with the master branch if any. 
1284
 
 
1285
 
        :return: None or the last_revision that was pivoted out during the
1286
 
                 update.
1287
 
        """
1288
 
        master = self.get_master_branch()
1289
 
        if master is not None:
1290
 
            old_tip = self.last_revision()
1291
 
            self.pull(master, overwrite=True)
1292
 
            if old_tip in self.repository.get_ancestry(self.last_revision()):
1293
 
                return None
1294
 
            return old_tip
1295
 
        return None
1296
 
 
1297
 
 
1298
 
class BranchTestProviderAdapter(object):
1299
 
    """A tool to generate a suite testing multiple branch formats at once.
1300
 
 
1301
 
    This is done by copying the test once for each transport and injecting
1302
 
    the transport_server, transport_readonly_server, and branch_format
1303
 
    classes into each copy. Each copy is also given a new id() to make it
1304
 
    easy to identify.
1305
 
    """
1306
 
 
1307
 
    def __init__(self, transport_server, transport_readonly_server, formats):
1308
 
        self._transport_server = transport_server
1309
 
        self._transport_readonly_server = transport_readonly_server
1310
 
        self._formats = formats
1311
 
    
1312
 
    def adapt(self, test):
1313
 
        result = TestSuite()
1314
 
        for branch_format, bzrdir_format in self._formats:
1315
 
            new_test = deepcopy(test)
1316
 
            new_test.transport_server = self._transport_server
1317
 
            new_test.transport_readonly_server = self._transport_readonly_server
1318
 
            new_test.bzrdir_format = bzrdir_format
1319
 
            new_test.branch_format = branch_format
1320
 
            def make_new_test_id():
1321
 
                new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1322
 
                return lambda: new_id
1323
 
            new_test.id = make_new_test_id()
1324
 
            result.addTest(new_test)
1325
 
        return result
1326
 
 
1327
 
 
1328
 
######################################################################
1329
 
# predicates
1330
 
 
1331
 
 
1332
 
@deprecated_function(zero_eight)
1333
 
def ScratchBranch(*args, **kwargs):
1334
 
    """See bzrlib.bzrdir.ScratchDir."""
1335
 
    d = ScratchDir(*args, **kwargs)
1336
 
    return d.open_branch()
1337
 
 
1338
 
 
1339
 
@deprecated_function(zero_eight)
1340
 
def is_control_file(*args, **kwargs):
1341
 
    """See bzrlib.workingtree.is_control_file."""
1342
 
    return bzrlib.workingtree.is_control_file(*args, **kwargs)
 
823
        if filename == head:
 
824
            break
 
825
        filename = head
 
826
    return False
 
827
 
 
828
 
 
829
 
 
830
def gen_file_id(name):
 
831
    """Return new file id.
 
832
 
 
833
    This should probably generate proper UUIDs, but for the moment we
 
834
    cope with just randomness because running uuidgen every time is
 
835
    slow."""
 
836
    import re
 
837
 
 
838
    # get last component
 
839
    idx = name.rfind('/')
 
840
    if idx != -1:
 
841
        name = name[idx+1 : ]
 
842
    idx = name.rfind('\\')
 
843
    if idx != -1:
 
844
        name = name[idx+1 : ]
 
845
 
 
846
    # make it not a hidden file
 
847
    name = name.lstrip('.')
 
848
 
 
849
    # remove any wierd characters; we don't escape them but rather
 
850
    # just pull them out
 
851
    name = re.sub(r'[^\w.]', '', name)
 
852
 
 
853
    s = hexlify(rand_bytes(8))
 
854
    return '-'.join((name, compact_date(time.time()), s))