~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

[merge] jam-integration 1495

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
 
18
import shutil
18
19
import sys
19
20
import os
 
21
import errno
 
22
from warnings import warn
 
23
from cStringIO import StringIO
 
24
 
20
25
 
21
26
import bzrlib
 
27
import bzrlib.inventory as inventory
22
28
from bzrlib.trace import mutter, note
23
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
 
     splitpath, \
25
 
     sha_file, appendpath, file_kind
26
 
 
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
 
     DivergedBranches, NotBranchError
 
29
from bzrlib.osutils import (isdir, quotefn,
 
30
                            rename, splitpath, sha_file,
 
31
                            file_kind, abspath, normpath, pathjoin)
 
32
import bzrlib.errors as errors
 
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
 
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
 
35
                           DivergedBranches, LockError, UnlistableStore,
 
36
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
37
                           NoWorkingTree)
29
38
from bzrlib.textui import show_status
30
 
from bzrlib.revision import Revision
 
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
 
40
                             NULL_REVISION)
 
41
 
31
42
from bzrlib.delta import compare_trees
32
43
from bzrlib.tree import EmptyTree, RevisionTree
33
 
import bzrlib.xml
 
44
from bzrlib.inventory import Inventory
 
45
from bzrlib.store import copy_all
 
46
from bzrlib.store.text import TextStore
 
47
from bzrlib.store.weave import WeaveStore
 
48
from bzrlib.testament import Testament
 
49
import bzrlib.transactions as transactions
 
50
from bzrlib.transport import Transport, get_transport
 
51
import bzrlib.xml5
34
52
import bzrlib.ui
35
 
 
36
 
 
37
 
 
38
 
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
 
53
from config import TreeConfig
 
54
 
 
55
 
 
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
 
57
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
58
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
39
59
## TODO: Maybe include checks for common corruption of newlines, etc?
40
60
 
41
61
 
42
62
# TODO: Some operations like log might retrieve the same revisions
43
63
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
 
# cache in memory to make this faster.
45
 
 
46
 
# TODO: please move the revision-string syntax stuff out of the branch
47
 
# object; it's clutter
48
 
 
49
 
 
50
 
def find_branch(f, **args):
51
 
    if f and (f.startswith('http://') or f.startswith('https://')):
52
 
        from bzrlib.remotebranch import RemoteBranch
53
 
        return RemoteBranch(f, **args)
54
 
    else:
55
 
        return LocalBranch(f, **args)
56
 
 
57
 
 
58
 
def find_cached_branch(f, cache_root, **args):
59
 
    from bzrlib.remotebranch import RemoteBranch
60
 
    br = find_branch(f, **args)
61
 
    def cacheify(br, store_name):
62
 
        from bzrlib.meta_store import CachedStore
63
 
        cache_path = os.path.join(cache_root, store_name)
64
 
        os.mkdir(cache_path)
65
 
        new_store = CachedStore(getattr(br, store_name), cache_path)
66
 
        setattr(br, store_name, new_store)
67
 
 
68
 
    if isinstance(br, RemoteBranch):
69
 
        cacheify(br, 'inventory_store')
70
 
        cacheify(br, 'text_store')
71
 
        cacheify(br, 'revision_store')
72
 
    return br
73
 
 
74
 
 
75
 
def _relpath(base, path):
76
 
    """Return path relative to base, or raise exception.
77
 
 
78
 
    The path may be either an absolute path or a path relative to the
79
 
    current working directory.
80
 
 
81
 
    Lifted out of Branch.relpath for ease of testing.
82
 
 
83
 
    os.path.commonprefix (python2.4) has a bad bug that it works just
84
 
    on string prefixes, assuming that '/u' is a prefix of '/u2'.  This
85
 
    avoids that problem."""
86
 
    rp = os.path.abspath(path)
87
 
 
88
 
    s = []
89
 
    head = rp
90
 
    while len(head) >= len(base):
91
 
        if head == base:
92
 
            break
93
 
        head, tail = os.path.split(head)
94
 
        if tail:
95
 
            s.insert(0, tail)
96
 
    else:
97
 
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
98
 
 
99
 
    return os.sep.join(s)
100
 
        
101
 
 
102
 
def find_branch_root(f=None):
103
 
    """Find the branch root enclosing f, or pwd.
104
 
 
105
 
    f may be a filename or a URL.
106
 
 
107
 
    It is not necessary that f exists.
108
 
 
109
 
    Basically we keep looking up until we find the control directory or
110
 
    run into the root.  If there isn't one, raises NotBranchError.
111
 
    """
112
 
    if f == None:
113
 
        f = os.getcwd()
114
 
    elif hasattr(os.path, 'realpath'):
115
 
        f = os.path.realpath(f)
116
 
    else:
117
 
        f = os.path.abspath(f)
118
 
    if not os.path.exists(f):
119
 
        raise BzrError('%r does not exist' % f)
120
 
        
121
 
 
122
 
    orig_f = f
123
 
 
124
 
    while True:
125
 
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
126
 
            return f
127
 
        head, tail = os.path.split(f)
128
 
        if head == f:
129
 
            # reached the root, whatever that may be
130
 
            raise NotBranchError('%s is not in a branch' % orig_f)
131
 
        f = head
132
 
 
133
 
 
134
 
 
 
64
# cache in memory to make this faster.  In general anything can be
 
65
# cached in memory between lock and unlock operations.
 
66
 
 
67
def find_branch(*ignored, **ignored_too):
 
68
    # XXX: leave this here for about one release, then remove it
 
69
    raise NotImplementedError('find_branch() is not supported anymore, '
 
70
                              'please use one of the new branch constructors')
 
71
 
 
72
 
 
73
def needs_read_lock(unbound):
 
74
    """Decorate unbound to take out and release a read lock."""
 
75
    def decorated(self, *args, **kwargs):
 
76
        self.lock_read()
 
77
        try:
 
78
            return unbound(self, *args, **kwargs)
 
79
        finally:
 
80
            self.unlock()
 
81
    return decorated
 
82
 
 
83
 
 
84
def needs_write_lock(unbound):
 
85
    """Decorate unbound to take out and release a write lock."""
 
86
    def decorated(self, *args, **kwargs):
 
87
        self.lock_write()
 
88
        try:
 
89
            return unbound(self, *args, **kwargs)
 
90
        finally:
 
91
            self.unlock()
 
92
    return decorated
135
93
 
136
94
######################################################################
137
95
# branch objects
144
102
    """
145
103
    base = None
146
104
 
147
 
    def __new__(cls, *a, **kw):
148
 
        """this is temporary, till we get rid of all code that does
149
 
        b = Branch()
150
 
        """
151
 
        # XXX: AAARGH!  MY EYES!  UUUUGLY!!!
152
 
        if cls == Branch:
153
 
            cls = LocalBranch
154
 
        b = object.__new__(cls)
155
 
        return b
156
 
 
157
 
 
158
 
class LocalBranch(Branch):
159
 
    """A branch stored in the actual filesystem.
160
 
 
161
 
    Note that it's "local" in the context of the filesystem; it doesn't
162
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
163
 
    it's writable, and can be accessed via the normal filesystem API.
164
 
 
165
 
    _lock_mode
166
 
        None, or 'r' or 'w'
167
 
 
168
 
    _lock_count
169
 
        If _lock_mode is true, a positive count of the number of times the
170
 
        lock has been taken.
171
 
 
172
 
    _lock
173
 
        Lock object from bzrlib.lock.
174
 
    """
175
 
    # We actually expect this class to be somewhat short-lived; part of its
176
 
    # purpose is to try to isolate what bits of the branch logic are tied to
177
 
    # filesystem access, so that in a later step, we can extricate them to
178
 
    # a separarte ("storage") class.
179
 
    _lock_mode = None
180
 
    _lock_count = None
181
 
    _lock = None
182
 
 
183
 
    def __init__(self, base, init=False, find_root=True):
184
 
        """Create new branch object at a particular location.
185
 
 
186
 
        base -- Base directory for the branch.
187
 
        
188
 
        init -- If True, create new control files in a previously
189
 
             unversioned directory.  If False, the branch must already
190
 
             be versioned.
191
 
 
192
 
        find_root -- If true and init is false, find the root of the
193
 
             existing branch containing base.
194
 
 
195
 
        In the test suite, creation of new trees is tested using the
196
 
        `ScratchBranch` class.
197
 
        """
198
 
        from bzrlib.store import ImmutableStore
199
 
        if init:
200
 
            self.base = os.path.realpath(base)
201
 
            self._make_control()
202
 
        elif find_root:
203
 
            self.base = find_branch_root(base)
204
 
        else:
205
 
            self.base = os.path.realpath(base)
206
 
            if not isdir(self.controlfilename('.')):
207
 
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
208
 
                                     ['use "bzr init" to initialize a new working tree',
209
 
                                      'current bzr can only operate from top-of-tree'])
210
 
        self._check_format()
211
 
 
212
 
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
213
 
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
214
 
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
215
 
 
216
 
 
217
 
    def __str__(self):
218
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
219
 
 
220
 
 
221
 
    __repr__ = __str__
222
 
 
223
 
 
224
 
    def __del__(self):
225
 
        if self._lock_mode or self._lock:
226
 
            from bzrlib.warnings import warn
227
 
            warn("branch %r was not explicitly unlocked" % self)
228
 
            self._lock.unlock()
229
 
 
 
105
    def __init__(self, *ignored, **ignored_too):
 
106
        raise NotImplementedError('The Branch class is abstract')
 
107
 
 
108
    @staticmethod
 
109
    def open_downlevel(base):
 
110
        """Open a branch which may be of an old format.
 
111
        
 
112
        Only local branches are supported."""
 
113
        return BzrBranch(get_transport(base), relax_version_check=True)
 
114
        
 
115
    @staticmethod
 
116
    def open(base):
 
117
        """Open an existing branch, rooted at 'base' (url)"""
 
118
        t = get_transport(base)
 
119
        mutter("trying to open %r with transport %r", base, t)
 
120
        return BzrBranch(t)
 
121
 
 
122
    @staticmethod
 
123
    def open_containing(url):
 
124
        """Open an existing branch which contains url.
 
125
        
 
126
        This probes for a branch at url, and searches upwards from there.
 
127
 
 
128
        Basically we keep looking up until we find the control directory or
 
129
        run into the root.  If there isn't one, raises NotBranchError.
 
130
        If there is one, it is returned, along with the unused portion of url.
 
131
        """
 
132
        t = get_transport(url)
 
133
        while True:
 
134
            try:
 
135
                return BzrBranch(t), t.relpath(url)
 
136
            except NotBranchError, e:
 
137
                mutter('not a branch in: %r %s', t.base, e)
 
138
            new_t = t.clone('..')
 
139
            if new_t.base == t.base:
 
140
                # reached the root, whatever that may be
 
141
                raise NotBranchError(path=url)
 
142
            t = new_t
 
143
 
 
144
    @staticmethod
 
145
    def initialize(base):
 
146
        """Create a new branch, rooted at 'base' (url)"""
 
147
        t = get_transport(base)
 
148
        return BzrBranch(t, init=True)
 
149
 
 
150
    def setup_caching(self, cache_root):
 
151
        """Subclasses that care about caching should override this, and set
 
152
        up cached stores located under cache_root.
 
153
        """
 
154
        self.cache_root = cache_root
 
155
 
 
156
    def _get_nick(self):
 
157
        cfg = self.tree_config()
 
158
        return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
 
159
 
 
160
    def _set_nick(self, nick):
 
161
        cfg = self.tree_config()
 
162
        cfg.set_option(nick, "nickname")
 
163
        assert cfg.get_option("nickname") == nick
 
164
 
 
165
    nick = property(_get_nick, _set_nick)
 
166
        
 
167
    def push_stores(self, branch_to):
 
168
        """Copy the content of this branches store to branch_to."""
 
169
        raise NotImplementedError('push_stores is abstract')
 
170
 
 
171
    def get_transaction(self):
 
172
        """Return the current active transaction.
 
173
 
 
174
        If no transaction is active, this returns a passthrough object
 
175
        for which all data is immediately flushed and no caching happens.
 
176
        """
 
177
        raise NotImplementedError('get_transaction is abstract')
230
178
 
231
179
    def lock_write(self):
232
 
        if self._lock_mode:
233
 
            if self._lock_mode != 'w':
234
 
                from bzrlib.errors import LockError
235
 
                raise LockError("can't upgrade to a write lock from %r" %
236
 
                                self._lock_mode)
237
 
            self._lock_count += 1
238
 
        else:
239
 
            from bzrlib.lock import WriteLock
240
 
 
241
 
            self._lock = WriteLock(self.controlfilename('branch-lock'))
242
 
            self._lock_mode = 'w'
243
 
            self._lock_count = 1
244
 
 
245
 
 
 
180
        raise NotImplementedError('lock_write is abstract')
 
181
        
246
182
    def lock_read(self):
247
 
        if self._lock_mode:
248
 
            assert self._lock_mode in ('r', 'w'), \
249
 
                   "invalid lock mode %r" % self._lock_mode
250
 
            self._lock_count += 1
251
 
        else:
252
 
            from bzrlib.lock import ReadLock
 
183
        raise NotImplementedError('lock_read is abstract')
253
184
 
254
 
            self._lock = ReadLock(self.controlfilename('branch-lock'))
255
 
            self._lock_mode = 'r'
256
 
            self._lock_count = 1
257
 
                        
258
185
    def unlock(self):
259
 
        if not self._lock_mode:
260
 
            from bzrlib.errors import LockError
261
 
            raise LockError('branch %r is not locked' % (self))
262
 
 
263
 
        if self._lock_count > 1:
264
 
            self._lock_count -= 1
265
 
        else:
266
 
            self._lock.unlock()
267
 
            self._lock = None
268
 
            self._lock_mode = self._lock_count = None
 
186
        raise NotImplementedError('unlock is abstract')
269
187
 
270
188
    def abspath(self, name):
271
 
        """Return absolute filename for something in the branch"""
272
 
        return os.path.join(self.base, name)
273
 
 
274
 
    def relpath(self, path):
275
 
        """Return path relative to this branch of something inside it.
276
 
 
277
 
        Raises an error if path is not in this branch."""
278
 
        return _relpath(self.base, path)
 
189
        """Return absolute filename for something in the branch
 
190
        
 
191
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
 
192
        method and not a tree method.
 
193
        """
 
194
        raise NotImplementedError('abspath is abstract')
279
195
 
280
196
    def controlfilename(self, file_or_path):
281
197
        """Return location relative to branch."""
282
 
        if isinstance(file_or_path, basestring):
283
 
            file_or_path = [file_or_path]
284
 
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
285
 
 
 
198
        raise NotImplementedError('controlfilename is abstract')
286
199
 
287
200
    def controlfile(self, file_or_path, mode='r'):
288
201
        """Open a control file for this branch.
295
208
        Controlfiles should almost never be opened in write mode but
296
209
        rather should be atomically copied and replaced using atomicfile.
297
210
        """
298
 
 
299
 
        fn = self.controlfilename(file_or_path)
300
 
 
301
 
        if mode == 'rb' or mode == 'wb':
302
 
            return file(fn, mode)
303
 
        elif mode == 'r' or mode == 'w':
304
 
            # open in binary mode anyhow so there's no newline translation;
305
 
            # codecs uses line buffering by default; don't want that.
306
 
            import codecs
307
 
            return codecs.open(fn, mode + 'b', 'utf-8',
308
 
                               buffering=60000)
309
 
        else:
310
 
            raise BzrError("invalid controlfile mode %r" % mode)
311
 
 
312
 
    def _make_control(self):
313
 
        from bzrlib.inventory import Inventory
314
 
        
315
 
        os.mkdir(self.controlfilename([]))
316
 
        self.controlfile('README', 'w').write(
317
 
            "This is a Bazaar-NG control directory.\n"
318
 
            "Do not change any files in this directory.\n")
319
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
320
 
        for d in ('text-store', 'inventory-store', 'revision-store'):
321
 
            os.mkdir(self.controlfilename(d))
322
 
        for f in ('revision-history', 'merged-patches',
323
 
                  'pending-merged-patches', 'branch-name',
324
 
                  'branch-lock',
325
 
                  'pending-merges'):
326
 
            self.controlfile(f, 'w').write('')
327
 
        mutter('created control directory in ' + self.base)
328
 
 
329
 
        # if we want per-tree root ids then this is the place to set
330
 
        # them; they're not needed for now and so ommitted for
331
 
        # simplicity.
332
 
        f = self.controlfile('inventory','w')
333
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
334
 
 
335
 
 
336
 
    def _check_format(self):
337
 
        """Check this branch format is supported.
338
 
 
339
 
        The current tool only supports the current unstable format.
340
 
 
341
 
        In the future, we might need different in-memory Branch
342
 
        classes to support downlevel branches.  But not yet.
343
 
        """
344
 
        # This ignores newlines so that we can open branches created
345
 
        # on Windows from Linux and so on.  I think it might be better
346
 
        # to always make all internal files in unix format.
347
 
        fmt = self.controlfile('branch-format', 'r').read()
348
 
        fmt = fmt.replace('\r\n', '\n')
349
 
        if fmt != BZR_BRANCH_FORMAT:
350
 
            raise BzrError('sorry, branch format %r not supported' % fmt,
351
 
                           ['use a different bzr version',
352
 
                            'or remove the .bzr directory and "bzr init" again'])
 
211
        raise NotImplementedError('controlfile is abstract')
 
212
 
 
213
    def put_controlfile(self, path, f, encode=True):
 
214
        """Write an entry as a controlfile.
 
215
 
 
216
        :param path: The path to put the file, relative to the .bzr control
 
217
                     directory
 
218
        :param f: A file-like or string object whose contents should be copied.
 
219
        :param encode:  If true, encode the contents as utf-8
 
220
        """
 
221
        raise NotImplementedError('put_controlfile is abstract')
 
222
 
 
223
    def put_controlfiles(self, files, encode=True):
 
224
        """Write several entries as controlfiles.
 
225
 
 
226
        :param files: A list of [(path, file)] pairs, where the path is the directory
 
227
                      underneath the bzr control directory
 
228
        :param encode:  If true, encode the contents as utf-8
 
229
        """
 
230
        raise NotImplementedError('put_controlfiles is abstract')
353
231
 
354
232
    def get_root_id(self):
355
233
        """Return the id of this branches root"""
356
 
        inv = self.read_working_inventory()
357
 
        return inv.root.file_id
 
234
        raise NotImplementedError('get_root_id is abstract')
358
235
 
359
236
    def set_root_id(self, file_id):
360
 
        inv = self.read_working_inventory()
361
 
        orig_root_id = inv.root.file_id
362
 
        del inv._byid[inv.root.file_id]
363
 
        inv.root.file_id = file_id
364
 
        inv._byid[inv.root.file_id] = inv.root
365
 
        for fid in inv:
366
 
            entry = inv[fid]
367
 
            if entry.parent_id in (None, orig_root_id):
368
 
                entry.parent_id = inv.root.file_id
369
 
        self._write_inventory(inv)
370
 
 
371
 
    def read_working_inventory(self):
372
 
        """Read the working inventory."""
373
 
        from bzrlib.inventory import Inventory
374
 
        self.lock_read()
375
 
        try:
376
 
            # ElementTree does its own conversion from UTF-8, so open in
377
 
            # binary.
378
 
            f = self.controlfile('inventory', 'rb')
379
 
            return bzrlib.xml.serializer_v4.read_inventory(f)
380
 
        finally:
381
 
            self.unlock()
382
 
            
383
 
 
384
 
    def _write_inventory(self, inv):
385
 
        """Update the working inventory.
386
 
 
387
 
        That is to say, the inventory describing changes underway, that
388
 
        will be committed to the next revision.
389
 
        """
390
 
        from bzrlib.atomicfile import AtomicFile
391
 
        
392
 
        self.lock_write()
393
 
        try:
394
 
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
395
 
            try:
396
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
397
 
                f.commit()
398
 
            finally:
399
 
                f.close()
400
 
        finally:
401
 
            self.unlock()
402
 
        
403
 
        mutter('wrote working inventory')
404
 
            
405
 
 
406
 
    inventory = property(read_working_inventory, _write_inventory, None,
407
 
                         """Inventory for the working copy.""")
408
 
 
409
 
 
410
 
    def add(self, files, ids=None):
411
 
        """Make files versioned.
412
 
 
413
 
        Note that the command line normally calls smart_add instead,
414
 
        which can automatically recurse.
415
 
 
416
 
        This puts the files in the Added state, so that they will be
417
 
        recorded by the next commit.
418
 
 
419
 
        files
420
 
            List of paths to add, relative to the base of the tree.
421
 
 
422
 
        ids
423
 
            If set, use these instead of automatically generated ids.
424
 
            Must be the same length as the list of files, but may
425
 
            contain None for ids that are to be autogenerated.
426
 
 
427
 
        TODO: Perhaps have an option to add the ids even if the files do
428
 
              not (yet) exist.
429
 
 
430
 
        TODO: Perhaps yield the ids and paths as they're added.
431
 
        """
432
 
        # TODO: Re-adding a file that is removed in the working copy
433
 
        # should probably put it back with the previous ID.
434
 
        if isinstance(files, basestring):
435
 
            assert(ids is None or isinstance(ids, basestring))
436
 
            files = [files]
437
 
            if ids is not None:
438
 
                ids = [ids]
439
 
 
440
 
        if ids is None:
441
 
            ids = [None] * len(files)
442
 
        else:
443
 
            assert(len(ids) == len(files))
444
 
 
445
 
        self.lock_write()
446
 
        try:
447
 
            inv = self.read_working_inventory()
448
 
            for f,file_id in zip(files, ids):
449
 
                if is_control_file(f):
450
 
                    raise BzrError("cannot add control file %s" % quotefn(f))
451
 
 
452
 
                fp = splitpath(f)
453
 
 
454
 
                if len(fp) == 0:
455
 
                    raise BzrError("cannot add top-level %r" % f)
456
 
 
457
 
                fullpath = os.path.normpath(self.abspath(f))
458
 
 
459
 
                try:
460
 
                    kind = file_kind(fullpath)
461
 
                except OSError:
462
 
                    # maybe something better?
463
 
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
464
 
 
465
 
                if kind != 'file' and kind != 'directory':
466
 
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
467
 
 
468
 
                if file_id is None:
469
 
                    file_id = gen_file_id(f)
470
 
                inv.add_path(f, kind=kind, file_id=file_id)
471
 
 
472
 
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
473
 
 
474
 
            self._write_inventory(inv)
475
 
        finally:
476
 
            self.unlock()
477
 
            
478
 
 
479
 
    def print_file(self, file, revno):
 
237
        raise NotImplementedError('set_root_id is abstract')
 
238
 
 
239
    def print_file(self, file, revision_id):
480
240
        """Print `file` to stdout."""
481
 
        self.lock_read()
482
 
        try:
483
 
            tree = self.revision_tree(self.get_rev_id(revno))
484
 
            # use inventory as it was in that revision
485
 
            file_id = tree.inventory.path2id(file)
486
 
            if not file_id:
487
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
488
 
            tree.print_file(file_id)
489
 
        finally:
490
 
            self.unlock()
491
 
 
492
 
 
493
 
    def remove(self, files, verbose=False):
494
 
        """Mark nominated files for removal from the inventory.
495
 
 
496
 
        This does not remove their text.  This does not run on 
497
 
 
498
 
        TODO: Refuse to remove modified files unless --force is given?
499
 
 
500
 
        TODO: Do something useful with directories.
501
 
 
502
 
        TODO: Should this remove the text or not?  Tough call; not
503
 
        removing may be useful and the user can just use use rm, and
504
 
        is the opposite of add.  Removing it is consistent with most
505
 
        other tools.  Maybe an option.
506
 
        """
507
 
        ## TODO: Normalize names
508
 
        ## TODO: Remove nested loops; better scalability
509
 
        if isinstance(files, basestring):
510
 
            files = [files]
511
 
 
512
 
        self.lock_write()
513
 
 
514
 
        try:
515
 
            tree = self.working_tree()
516
 
            inv = tree.inventory
517
 
 
518
 
            # do this before any modifications
519
 
            for f in files:
520
 
                fid = inv.path2id(f)
521
 
                if not fid:
522
 
                    raise BzrError("cannot remove unversioned file %s" % quotefn(f))
523
 
                mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
524
 
                if verbose:
525
 
                    # having remove it, it must be either ignored or unknown
526
 
                    if tree.is_ignored(f):
527
 
                        new_status = 'I'
528
 
                    else:
529
 
                        new_status = '?'
530
 
                    show_status(new_status, inv[fid].kind, quotefn(f))
531
 
                del inv[fid]
532
 
 
533
 
            self._write_inventory(inv)
534
 
        finally:
535
 
            self.unlock()
536
 
 
537
 
 
538
 
    # FIXME: this doesn't need to be a branch method
539
 
    def set_inventory(self, new_inventory_list):
540
 
        from bzrlib.inventory import Inventory, InventoryEntry
541
 
        inv = Inventory(self.get_root_id())
542
 
        for path, file_id, parent, kind in new_inventory_list:
543
 
            name = os.path.basename(path)
544
 
            if name == "":
545
 
                continue
546
 
            inv.add(InventoryEntry(file_id, name, kind, parent))
547
 
        self._write_inventory(inv)
548
 
 
549
 
 
550
 
    def unknowns(self):
551
 
        """Return all unknown files.
552
 
 
553
 
        These are files in the working directory that are not versioned or
554
 
        control files or ignored.
555
 
        
556
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
557
 
        >>> list(b.unknowns())
558
 
        ['foo']
559
 
        >>> b.add('foo')
560
 
        >>> list(b.unknowns())
561
 
        []
562
 
        >>> b.remove('foo')
563
 
        >>> list(b.unknowns())
564
 
        ['foo']
565
 
        """
566
 
        return self.working_tree().unknowns()
567
 
 
 
241
        raise NotImplementedError('print_file is abstract')
568
242
 
569
243
    def append_revision(self, *revision_ids):
570
 
        from bzrlib.atomicfile import AtomicFile
571
 
 
572
 
        for revision_id in revision_ids:
573
 
            mutter("add {%s} to revision-history" % revision_id)
574
 
 
575
 
        rev_history = self.revision_history()
576
 
        rev_history.extend(revision_ids)
577
 
 
578
 
        f = AtomicFile(self.controlfilename('revision-history'))
579
 
        try:
580
 
            for rev_id in rev_history:
581
 
                print >>f, rev_id
582
 
            f.commit()
583
 
        finally:
584
 
            f.close()
585
 
 
586
 
 
587
 
    def get_revision_xml_file(self, revision_id):
588
 
        """Return XML file object for revision object."""
589
 
        if not revision_id or not isinstance(revision_id, basestring):
590
 
            raise InvalidRevisionId(revision_id)
591
 
 
592
 
        self.lock_read()
593
 
        try:
594
 
            try:
595
 
                return self.revision_store[revision_id]
596
 
            except IndexError:
597
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
598
 
        finally:
599
 
            self.unlock()
600
 
 
601
 
 
602
 
    #deprecated
603
 
    get_revision_xml = get_revision_xml_file
604
 
 
 
244
        raise NotImplementedError('append_revision is abstract')
 
245
 
 
246
    def set_revision_history(self, rev_history):
 
247
        raise NotImplementedError('set_revision_history is abstract')
 
248
 
 
249
    def has_revision(self, revision_id):
 
250
        """True if this branch has a copy of the revision.
 
251
 
 
252
        This does not necessarily imply the revision is merge
 
253
        or on the mainline."""
 
254
        raise NotImplementedError('has_revision is abstract')
 
255
 
 
256
    def get_revision_xml(self, revision_id):
 
257
        raise NotImplementedError('get_revision_xml is abstract')
605
258
 
606
259
    def get_revision(self, revision_id):
607
260
        """Return the Revision object for a named revision"""
608
 
        xml_file = self.get_revision_xml_file(revision_id)
609
 
 
610
 
        try:
611
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
612
 
        except SyntaxError, e:
613
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
614
 
                                         [revision_id,
615
 
                                          str(e)])
616
 
            
617
 
        assert r.revision_id == revision_id
618
 
        return r
619
 
 
 
261
        raise NotImplementedError('get_revision is abstract')
620
262
 
621
263
    def get_revision_delta(self, revno):
622
264
        """Return the delta for one revision.
639
281
 
640
282
        return compare_trees(old_tree, new_tree)
641
283
 
642
 
        
643
 
 
644
284
    def get_revision_sha1(self, revision_id):
645
285
        """Hash the stored value of a revision, and return it."""
646
 
        # In the future, revision entries will be signed. At that
647
 
        # point, it is probably best *not* to include the signature
648
 
        # in the revision hash. Because that lets you re-sign
649
 
        # the revision, (add signatures/remove signatures) and still
650
 
        # have all hash pointers stay consistent.
651
 
        # But for now, just hash the contents.
652
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
653
 
 
654
 
 
655
 
    def get_inventory(self, inventory_id):
656
 
        """Get Inventory object by hash.
657
 
 
658
 
        TODO: Perhaps for this and similar methods, take a revision
659
 
               parameter which can be either an integer revno or a
660
 
               string hash."""
661
 
        from bzrlib.inventory import Inventory
662
 
 
663
 
        f = self.get_inventory_xml_file(inventory_id)
664
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
665
 
 
666
 
 
667
 
    def get_inventory_xml(self, inventory_id):
 
286
        raise NotImplementedError('get_revision_sha1 is abstract')
 
287
 
 
288
    def get_ancestry(self, revision_id):
 
289
        """Return a list of revision-ids integrated by a revision.
 
290
        
 
291
        This currently returns a list, but the ordering is not guaranteed:
 
292
        treat it as a set.
 
293
        """
 
294
        raise NotImplementedError('get_ancestry is abstract')
 
295
 
 
296
    def get_inventory(self, revision_id):
 
297
        """Get Inventory object by hash."""
 
298
        raise NotImplementedError('get_inventory is abstract')
 
299
 
 
300
    def get_inventory_xml(self, revision_id):
668
301
        """Get inventory XML as a file object."""
669
 
        return self.inventory_store[inventory_id]
670
 
 
671
 
    get_inventory_xml_file = get_inventory_xml
672
 
            
673
 
 
674
 
    def get_inventory_sha1(self, inventory_id):
675
 
        """Return the sha1 hash of the inventory entry
676
 
        """
677
 
        return sha_file(self.get_inventory_xml(inventory_id))
678
 
 
 
302
        raise NotImplementedError('get_inventory_xml is abstract')
 
303
 
 
304
    def get_inventory_sha1(self, revision_id):
 
305
        """Return the sha1 hash of the inventory entry."""
 
306
        raise NotImplementedError('get_inventory_sha1 is abstract')
679
307
 
680
308
    def get_revision_inventory(self, revision_id):
681
309
        """Return inventory of a past revision."""
682
 
        # bzr 0.0.6 imposes the constraint that the inventory_id
683
 
        # must be the same as its revision, so this is trivial.
684
 
        if revision_id == None:
685
 
            from bzrlib.inventory import Inventory
686
 
            return Inventory(self.get_root_id())
687
 
        else:
688
 
            return self.get_inventory(revision_id)
689
 
 
 
310
        raise NotImplementedError('get_revision_inventory is abstract')
690
311
 
691
312
    def revision_history(self):
692
 
        """Return sequence of revision hashes on to this branch.
693
 
 
694
 
        >>> ScratchBranch().revision_history()
695
 
        []
696
 
        """
697
 
        self.lock_read()
698
 
        try:
699
 
            return [l.rstrip('\r\n') for l in
700
 
                    self.controlfile('revision-history', 'r').readlines()]
701
 
        finally:
702
 
            self.unlock()
703
 
 
704
 
 
705
 
    def common_ancestor(self, other, self_revno=None, other_revno=None):
706
 
        """
707
 
        >>> from bzrlib.commit import commit
708
 
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
709
 
        >>> sb.common_ancestor(sb) == (None, None)
710
 
        True
711
 
        >>> commit(sb, "Committing first revision", verbose=False)
712
 
        >>> sb.common_ancestor(sb)[0]
713
 
        1
714
 
        >>> clone = sb.clone()
715
 
        >>> commit(sb, "Committing second revision", verbose=False)
716
 
        >>> sb.common_ancestor(sb)[0]
717
 
        2
718
 
        >>> sb.common_ancestor(clone)[0]
719
 
        1
720
 
        >>> commit(clone, "Committing divergent second revision", 
721
 
        ...               verbose=False)
722
 
        >>> sb.common_ancestor(clone)[0]
723
 
        1
724
 
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
725
 
        True
726
 
        >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
727
 
        True
728
 
        >>> clone2 = sb.clone()
729
 
        >>> sb.common_ancestor(clone2)[0]
730
 
        2
731
 
        >>> sb.common_ancestor(clone2, self_revno=1)[0]
732
 
        1
733
 
        >>> sb.common_ancestor(clone2, other_revno=1)[0]
734
 
        1
735
 
        """
736
 
        my_history = self.revision_history()
737
 
        other_history = other.revision_history()
738
 
        if self_revno is None:
739
 
            self_revno = len(my_history)
740
 
        if other_revno is None:
741
 
            other_revno = len(other_history)
742
 
        indices = range(min((self_revno, other_revno)))
743
 
        indices.reverse()
744
 
        for r in indices:
745
 
            if my_history[r] == other_history[r]:
746
 
                return r+1, my_history[r]
747
 
        return None, None
748
 
 
 
313
        """Return sequence of revision hashes on to this branch."""
 
314
        raise NotImplementedError('revision_history is abstract')
749
315
 
750
316
    def revno(self):
751
317
        """Return current revision number for this branch.
755
321
        """
756
322
        return len(self.revision_history())
757
323
 
758
 
 
759
 
    def last_patch(self):
760
 
        """Return last patch hash, or None if no history.
761
 
        """
 
324
    def last_revision(self):
 
325
        """Return last patch hash, or None if no history."""
762
326
        ph = self.revision_history()
763
327
        if ph:
764
328
            return ph[-1]
765
329
        else:
766
330
            return None
767
331
 
768
 
 
769
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
770
 
        """
 
332
    def missing_revisions(self, other, stop_revision=None):
 
333
        """Return a list of new revisions that would perfectly fit.
 
334
        
771
335
        If self and other have not diverged, return a list of the revisions
772
336
        present in other, but missing from self.
773
337
 
791
355
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
792
356
        >>> br1.missing_revisions(br2)
793
357
        Traceback (most recent call last):
794
 
        DivergedBranches: These branches have diverged.
 
358
        DivergedBranches: These branches have diverged.  Try merge.
795
359
        """
796
360
        self_history = self.revision_history()
797
361
        self_len = len(self_history)
804
368
 
805
369
        if stop_revision is None:
806
370
            stop_revision = other_len
807
 
        elif stop_revision > other_len:
808
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
809
 
        
 
371
        else:
 
372
            assert isinstance(stop_revision, int)
 
373
            if stop_revision > other_len:
 
374
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
810
375
        return other_history[self_len:stop_revision]
811
 
 
812
 
 
 
376
    
813
377
    def update_revisions(self, other, stop_revision=None):
814
 
        """Pull in all new revisions from other branch.
 
378
        """Pull in new perfect-fit revisions.
 
379
 
 
380
        :param other: Another Branch to pull from
 
381
        :param stop_revision: Updated until the given revision
 
382
        :return: None
815
383
        """
816
 
        from bzrlib.fetch import greedy_fetch
817
 
 
818
 
        pb = bzrlib.ui.ui_factory.progress_bar()
819
 
        pb.update('comparing histories')
820
 
 
821
 
        revision_ids = self.missing_revisions(other, stop_revision)
822
 
 
823
 
        if len(revision_ids) > 0:
824
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
825
 
        else:
826
 
            count = 0
827
 
        self.append_revision(*revision_ids)
828
 
        ## note("Added %d revisions." % count)
829
 
        pb.clear()
830
 
 
831
 
    def install_revisions(self, other, revision_ids, pb):
832
 
        if hasattr(other.revision_store, "prefetch"):
833
 
            other.revision_store.prefetch(revision_ids)
834
 
        if hasattr(other.inventory_store, "prefetch"):
835
 
            inventory_ids = [other.get_revision(r).inventory_id
836
 
                             for r in revision_ids]
837
 
            other.inventory_store.prefetch(inventory_ids)
838
 
 
839
 
        if pb is None:
840
 
            pb = bzrlib.ui.ui_factory.progress_bar()
841
 
                
842
 
        revisions = []
843
 
        needed_texts = set()
844
 
        i = 0
845
 
 
846
 
        failures = set()
847
 
        for i, rev_id in enumerate(revision_ids):
848
 
            pb.update('fetching revision', i+1, len(revision_ids))
849
 
            try:
850
 
                rev = other.get_revision(rev_id)
851
 
            except bzrlib.errors.NoSuchRevision:
852
 
                failures.add(rev_id)
853
 
                continue
854
 
 
855
 
            revisions.append(rev)
856
 
            inv = other.get_inventory(str(rev.inventory_id))
857
 
            for key, entry in inv.iter_entries():
858
 
                if entry.text_id is None:
859
 
                    continue
860
 
                if entry.text_id not in self.text_store:
861
 
                    needed_texts.add(entry.text_id)
862
 
 
863
 
        pb.clear()
864
 
                    
865
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
866
 
                                                    needed_texts)
867
 
        #print "Added %d texts." % count 
868
 
        inventory_ids = [ f.inventory_id for f in revisions ]
869
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
870
 
                                                         inventory_ids)
871
 
        #print "Added %d inventories." % count 
872
 
        revision_ids = [ f.revision_id for f in revisions]
873
 
 
874
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
875
 
                                                          revision_ids,
876
 
                                                          permit_failure=True)
877
 
        assert len(cp_fail) == 0 
878
 
        return count, failures
879
 
       
880
 
 
881
 
    def commit(self, *args, **kw):
882
 
        from bzrlib.commit import commit
883
 
        commit(self, *args, **kw)
 
384
        raise NotImplementedError('update_revisions is abstract')
 
385
 
 
386
    def pullable_revisions(self, other, stop_revision):
 
387
        raise NotImplementedError('pullable_revisions is abstract')
884
388
        
885
 
 
886
 
    def lookup_revision(self, revision):
887
 
        """Return the revision identifier for a given revision specifier."""
888
 
        # XXX: I'm not sure this method belongs here; I'd rather have the
889
 
        # revision spec stuff be an UI thing, and branch blissfully unaware
890
 
        # of it.
891
 
        # Also, I'm not entirely happy with this method returning None
892
 
        # when the revision doesn't exist.
893
 
        # But I'm keeping the contract I found, because this seems to be
894
 
        # used in a lot of places - and when I do change these, I'd rather
895
 
        # figure out case-by-case which ones actually want to care about
896
 
        # revision specs (eg, they are UI-level) and which ones should trust
897
 
        # that they have a revno/revid.
898
 
        #   -- lalo@exoweb.net, 2005-09-07
899
 
        from bzrlib.errors import NoSuchRevision
900
 
        from bzrlib.revisionspec import RevisionSpec
901
 
        try:
902
 
            spec = RevisionSpec(self, revision)
903
 
        except NoSuchRevision:
904
 
            return None
905
 
        return spec.rev_id
906
 
 
907
 
 
908
389
    def revision_id_to_revno(self, revision_id):
909
390
        """Given a revision id, return its revno"""
 
391
        if revision_id is None:
 
392
            return 0
910
393
        history = self.revision_history()
911
394
        try:
912
395
            return history.index(revision_id) + 1
913
396
        except ValueError:
914
397
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
915
398
 
916
 
 
917
399
    def get_rev_id(self, revno, history=None):
918
400
        """Find the revision id of the specified revno."""
919
401
        if revno == 0:
929
411
 
930
412
        `revision_id` may be None for the null revision, in which case
931
413
        an `EmptyTree` is returned."""
932
 
        # TODO: refactor this to use an existing revision object
933
 
        # so we don't need to read it in twice.
934
 
        if revision_id == None:
935
 
            return EmptyTree()
936
 
        else:
937
 
            inv = self.get_revision_inventory(revision_id)
938
 
            return RevisionTree(self.text_store, inv)
939
 
 
 
414
        raise NotImplementedError('revision_tree is abstract')
940
415
 
941
416
    def working_tree(self):
942
 
        """Return a `Tree` for the working copy."""
943
 
        from bzrlib.workingtree import WorkingTree
944
 
        return WorkingTree(self.base, self.read_working_inventory())
 
417
        """Return a `Tree` for the working copy if this is a local branch."""
 
418
        raise NotImplementedError('working_tree is abstract')
945
419
 
 
420
    def pull(self, source, overwrite=False):
 
421
        raise NotImplementedError('pull is abstract')
946
422
 
947
423
    def basis_tree(self):
948
424
        """Return `Tree` object for last revision.
949
425
 
950
426
        If there are no revisions yet, return an `EmptyTree`.
951
427
        """
952
 
        r = self.last_patch()
953
 
        if r == None:
954
 
            return EmptyTree()
955
 
        else:
956
 
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
957
 
 
958
 
 
 
428
        return self.revision_tree(self.last_revision())
959
429
 
960
430
    def rename_one(self, from_rel, to_rel):
961
431
        """Rename one file.
962
432
 
963
433
        This can change the directory or the filename or both.
964
434
        """
965
 
        self.lock_write()
966
 
        try:
967
 
            tree = self.working_tree()
968
 
            inv = tree.inventory
969
 
            if not tree.has_filename(from_rel):
970
 
                raise BzrError("can't rename: old working file %r does not exist" % from_rel)
971
 
            if tree.has_filename(to_rel):
972
 
                raise BzrError("can't rename: new working file %r already exists" % to_rel)
973
 
 
974
 
            file_id = inv.path2id(from_rel)
975
 
            if file_id == None:
976
 
                raise BzrError("can't rename: old name %r is not versioned" % from_rel)
977
 
 
978
 
            if inv.path2id(to_rel):
979
 
                raise BzrError("can't rename: new name %r is already versioned" % to_rel)
980
 
 
981
 
            to_dir, to_tail = os.path.split(to_rel)
982
 
            to_dir_id = inv.path2id(to_dir)
983
 
            if to_dir_id == None and to_dir != '':
984
 
                raise BzrError("can't determine destination directory id for %r" % to_dir)
985
 
 
986
 
            mutter("rename_one:")
987
 
            mutter("  file_id    {%s}" % file_id)
988
 
            mutter("  from_rel   %r" % from_rel)
989
 
            mutter("  to_rel     %r" % to_rel)
990
 
            mutter("  to_dir     %r" % to_dir)
991
 
            mutter("  to_dir_id  {%s}" % to_dir_id)
992
 
 
993
 
            inv.rename(file_id, to_dir_id, to_tail)
994
 
 
995
 
            from_abs = self.abspath(from_rel)
996
 
            to_abs = self.abspath(to_rel)
997
 
            try:
998
 
                os.rename(from_abs, to_abs)
999
 
            except OSError, e:
1000
 
                raise BzrError("failed to rename %r to %r: %s"
1001
 
                        % (from_abs, to_abs, e[1]),
1002
 
                        ["rename rolled back"])
1003
 
 
1004
 
            self._write_inventory(inv)
1005
 
        finally:
1006
 
            self.unlock()
1007
 
 
 
435
        raise NotImplementedError('rename_one is abstract')
1008
436
 
1009
437
    def move(self, from_paths, to_name):
1010
438
        """Rename files.
1020
448
        This returns a list of (from_path, to_path) pairs for each
1021
449
        entry that is moved.
1022
450
        """
1023
 
        result = []
1024
 
        self.lock_write()
1025
 
        try:
1026
 
            ## TODO: Option to move IDs only
1027
 
            assert not isinstance(from_paths, basestring)
1028
 
            tree = self.working_tree()
1029
 
            inv = tree.inventory
1030
 
            to_abs = self.abspath(to_name)
1031
 
            if not isdir(to_abs):
1032
 
                raise BzrError("destination %r is not a directory" % to_abs)
1033
 
            if not tree.has_filename(to_name):
1034
 
                raise BzrError("destination %r not in working directory" % to_abs)
1035
 
            to_dir_id = inv.path2id(to_name)
1036
 
            if to_dir_id == None and to_name != '':
1037
 
                raise BzrError("destination %r is not a versioned directory" % to_name)
1038
 
            to_dir_ie = inv[to_dir_id]
1039
 
            if to_dir_ie.kind not in ('directory', 'root_directory'):
1040
 
                raise BzrError("destination %r is not a directory" % to_abs)
1041
 
 
1042
 
            to_idpath = inv.get_idpath(to_dir_id)
1043
 
 
1044
 
            for f in from_paths:
1045
 
                if not tree.has_filename(f):
1046
 
                    raise BzrError("%r does not exist in working tree" % f)
1047
 
                f_id = inv.path2id(f)
1048
 
                if f_id == None:
1049
 
                    raise BzrError("%r is not versioned" % f)
1050
 
                name_tail = splitpath(f)[-1]
1051
 
                dest_path = appendpath(to_name, name_tail)
1052
 
                if tree.has_filename(dest_path):
1053
 
                    raise BzrError("destination %r already exists" % dest_path)
1054
 
                if f_id in to_idpath:
1055
 
                    raise BzrError("can't move %r to a subdirectory of itself" % f)
1056
 
 
1057
 
            # OK, so there's a race here, it's possible that someone will
1058
 
            # create a file in this interval and then the rename might be
1059
 
            # left half-done.  But we should have caught most problems.
1060
 
 
1061
 
            for f in from_paths:
1062
 
                name_tail = splitpath(f)[-1]
1063
 
                dest_path = appendpath(to_name, name_tail)
1064
 
                result.append((f, dest_path))
1065
 
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1066
 
                try:
1067
 
                    os.rename(self.abspath(f), self.abspath(dest_path))
1068
 
                except OSError, e:
1069
 
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1070
 
                            ["rename rolled back"])
1071
 
 
1072
 
            self._write_inventory(inv)
1073
 
        finally:
1074
 
            self.unlock()
1075
 
 
1076
 
        return result
1077
 
 
1078
 
 
1079
 
    def revert(self, filenames, old_tree=None, backups=True):
1080
 
        """Restore selected files to the versions from a previous tree.
1081
 
 
1082
 
        backups
1083
 
            If true (default) backups are made of files before
1084
 
            they're renamed.
1085
 
        """
1086
 
        from bzrlib.errors import NotVersionedError, BzrError
1087
 
        from bzrlib.atomicfile import AtomicFile
1088
 
        from bzrlib.osutils import backup_file
1089
 
        
1090
 
        inv = self.read_working_inventory()
1091
 
        if old_tree is None:
1092
 
            old_tree = self.basis_tree()
1093
 
        old_inv = old_tree.inventory
1094
 
 
1095
 
        nids = []
1096
 
        for fn in filenames:
1097
 
            file_id = inv.path2id(fn)
1098
 
            if not file_id:
1099
 
                raise NotVersionedError("not a versioned file", fn)
1100
 
            if not old_inv.has_id(file_id):
1101
 
                raise BzrError("file not present in old tree", fn, file_id)
1102
 
            nids.append((fn, file_id))
1103
 
            
1104
 
        # TODO: Rename back if it was previously at a different location
1105
 
 
1106
 
        # TODO: If given a directory, restore the entire contents from
1107
 
        # the previous version.
1108
 
 
1109
 
        # TODO: Make a backup to a temporary file.
1110
 
 
1111
 
        # TODO: If the file previously didn't exist, delete it?
1112
 
        for fn, file_id in nids:
1113
 
            backup_file(fn)
1114
 
            
1115
 
            f = AtomicFile(fn, 'wb')
1116
 
            try:
1117
 
                f.write(old_tree.get_file(file_id).read())
1118
 
                f.commit()
1119
 
            finally:
1120
 
                f.close()
1121
 
 
1122
 
 
1123
 
    def pending_merges(self):
1124
 
        """Return a list of pending merges.
1125
 
 
1126
 
        These are revisions that have been merged into the working
1127
 
        directory but not yet committed.
1128
 
        """
1129
 
        cfn = self.controlfilename('pending-merges')
1130
 
        if not os.path.exists(cfn):
1131
 
            return []
1132
 
        p = []
1133
 
        for l in self.controlfile('pending-merges', 'r').readlines():
1134
 
            p.append(l.rstrip('\n'))
1135
 
        return p
1136
 
 
1137
 
 
1138
 
    def add_pending_merge(self, revision_id):
1139
 
        from bzrlib.revision import validate_revision_id
1140
 
 
1141
 
        validate_revision_id(revision_id)
1142
 
 
1143
 
        p = self.pending_merges()
1144
 
        if revision_id in p:
1145
 
            return
1146
 
        p.append(revision_id)
1147
 
        self.set_pending_merges(p)
1148
 
 
1149
 
 
1150
 
    def set_pending_merges(self, rev_list):
1151
 
        from bzrlib.atomicfile import AtomicFile
1152
 
        self.lock_write()
1153
 
        try:
1154
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1155
 
            try:
1156
 
                for l in rev_list:
1157
 
                    print >>f, l
1158
 
                f.commit()
1159
 
            finally:
1160
 
                f.close()
1161
 
        finally:
1162
 
            self.unlock()
1163
 
 
 
451
        raise NotImplementedError('move is abstract')
1164
452
 
1165
453
    def get_parent(self):
1166
454
        """Return the parent location of the branch.
1169
457
        pattern is that the user can override it by specifying a
1170
458
        location.
1171
459
        """
1172
 
        import errno
1173
 
        _locs = ['parent', 'pull', 'x-pull']
1174
 
        for l in _locs:
1175
 
            try:
1176
 
                return self.controlfile(l, 'r').read().strip('\n')
1177
 
            except IOError, e:
1178
 
                if e.errno != errno.ENOENT:
1179
 
                    raise
1180
 
        return None
1181
 
 
 
460
        raise NotImplementedError('get_parent is abstract')
 
461
 
 
462
    def get_push_location(self):
 
463
        """Return the None or the location to push this branch to."""
 
464
        raise NotImplementedError('get_push_location is abstract')
 
465
 
 
466
    def set_push_location(self, location):
 
467
        """Set a new push location for this branch."""
 
468
        raise NotImplementedError('set_push_location is abstract')
1182
469
 
1183
470
    def set_parent(self, url):
1184
 
        # TODO: Maybe delete old location files?
1185
 
        from bzrlib.atomicfile import AtomicFile
1186
 
        self.lock_write()
1187
 
        try:
1188
 
            f = AtomicFile(self.controlfilename('parent'))
1189
 
            try:
1190
 
                f.write(url + '\n')
1191
 
                f.commit()
1192
 
            finally:
1193
 
                f.close()
1194
 
        finally:
1195
 
            self.unlock()
 
471
        raise NotImplementedError('set_parent is abstract')
1196
472
 
1197
473
    def check_revno(self, revno):
1198
474
        """\
1210
486
        if revno < 1 or revno > self.revno():
1211
487
            raise InvalidRevisionNumber(revno)
1212
488
        
1213
 
        
1214
 
 
1215
 
 
1216
 
class ScratchBranch(LocalBranch):
 
489
    def sign_revision(self, revision_id, gpg_strategy):
 
490
        raise NotImplementedError('sign_revision is abstract')
 
491
 
 
492
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
493
        raise NotImplementedError('store_revision_signature is abstract')
 
494
 
 
495
class BzrBranch(Branch):
 
496
    """A branch stored in the actual filesystem.
 
497
 
 
498
    Note that it's "local" in the context of the filesystem; it doesn't
 
499
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
 
500
    it's writable, and can be accessed via the normal filesystem API.
 
501
 
 
502
    _lock_mode
 
503
        None, or 'r' or 'w'
 
504
 
 
505
    _lock_count
 
506
        If _lock_mode is true, a positive count of the number of times the
 
507
        lock has been taken.
 
508
 
 
509
    _lock
 
510
        Lock object from bzrlib.lock.
 
511
    """
 
512
    # We actually expect this class to be somewhat short-lived; part of its
 
513
    # purpose is to try to isolate what bits of the branch logic are tied to
 
514
    # filesystem access, so that in a later step, we can extricate them to
 
515
    # a separarte ("storage") class.
 
516
    _lock_mode = None
 
517
    _lock_count = None
 
518
    _lock = None
 
519
    _inventory_weave = None
 
520
    _master_branch = None
 
521
    # If set to False (by a plugin, etc) BzrBranch will not set the
 
522
    # mode on created files or directories
 
523
    _set_file_mode = True
 
524
    _set_dir_mode = True
 
525
    
 
526
    # Map some sort of prefix into a namespace
 
527
    # stuff like "revno:10", "revid:", etc.
 
528
    # This should match a prefix with a function which accepts
 
529
    REVISION_NAMESPACES = {}
 
530
 
 
531
    def push_stores(self, branch_to):
 
532
        """See Branch.push_stores."""
 
533
        if (self._branch_format != branch_to._branch_format
 
534
            or self._branch_format != 4):
 
535
            from bzrlib.fetch import greedy_fetch
 
536
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
 
537
                   self, self._branch_format, branch_to, branch_to._branch_format)
 
538
            greedy_fetch(to_branch=branch_to, from_branch=self,
 
539
                         revision=self.last_revision())
 
540
            return
 
541
 
 
542
        store_pairs = ((self.text_store,      branch_to.text_store),
 
543
                       (self.inventory_store, branch_to.inventory_store),
 
544
                       (self.revision_store,  branch_to.revision_store))
 
545
        try:
 
546
            for from_store, to_store in store_pairs: 
 
547
                copy_all(from_store, to_store)
 
548
        except UnlistableStore:
 
549
            raise UnlistableBranch(from_store)
 
550
 
 
551
    def __init__(self, transport, init=False,
 
552
                 relax_version_check=False):
 
553
        """Create new branch object at a particular location.
 
554
 
 
555
        transport -- A Transport object, defining how to access files.
 
556
        
 
557
        init -- If True, create new control files in a previously
 
558
             unversioned directory.  If False, the branch must already
 
559
             be versioned.
 
560
 
 
561
        relax_version_check -- If true, the usual check for the branch
 
562
            version is not applied.  This is intended only for
 
563
            upgrade/recovery type use; it's not guaranteed that
 
564
            all operations will work on old format branches.
 
565
 
 
566
        In the test suite, creation of new trees is tested using the
 
567
        `ScratchBranch` class.
 
568
        """
 
569
        assert isinstance(transport, Transport), \
 
570
            "%r is not a Transport" % transport
 
571
        self._transport = transport
 
572
        if init:
 
573
            self._make_control()
 
574
        self._check_format(relax_version_check)
 
575
        self._find_modes()
 
576
 
 
577
        def get_store(name, compressed=True, prefixed=False):
 
578
            relpath = self._rel_controlfilename(unicode(name))
 
579
            store = TextStore(self._transport.clone(relpath),
 
580
                              dir_mode=self._dir_mode,
 
581
                              file_mode=self._file_mode,
 
582
                              prefixed=prefixed,
 
583
                              compressed=compressed)
 
584
            return store
 
585
 
 
586
        def get_weave(name, prefixed=False):
 
587
            relpath = self._rel_controlfilename(unicode(name))
 
588
            ws = WeaveStore(self._transport.clone(relpath),
 
589
                            prefixed=prefixed,
 
590
                            dir_mode=self._dir_mode,
 
591
                            file_mode=self._file_mode)
 
592
            if self._transport.should_cache():
 
593
                ws.enable_cache = True
 
594
            return ws
 
595
 
 
596
        if self._branch_format == 4:
 
597
            self.inventory_store = get_store('inventory-store')
 
598
            self.text_store = get_store('text-store')
 
599
            self.revision_store = get_store('revision-store')
 
600
        elif self._branch_format == 5:
 
601
            self.control_weaves = get_weave(u'')
 
602
            self.weave_store = get_weave(u'weaves')
 
603
            self.revision_store = get_store(u'revision-store', compressed=False)
 
604
        elif self._branch_format == 6:
 
605
            self.control_weaves = get_weave(u'')
 
606
            self.weave_store = get_weave(u'weaves', prefixed=True)
 
607
            self.revision_store = get_store(u'revision-store', compressed=False,
 
608
                                            prefixed=True)
 
609
        self.revision_store.register_suffix('sig')
 
610
        self._transaction = None
 
611
 
 
612
    def __str__(self):
 
613
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
 
614
 
 
615
    __repr__ = __str__
 
616
 
 
617
    def __del__(self):
 
618
        if self._lock_mode or self._lock:
 
619
            # XXX: This should show something every time, and be suitable for
 
620
            # headless operation and embedding
 
621
            warn("branch %r was not explicitly unlocked" % self)
 
622
            self._lock.unlock()
 
623
 
 
624
        # TODO: It might be best to do this somewhere else,
 
625
        # but it is nice for a Branch object to automatically
 
626
        # cache it's information.
 
627
        # Alternatively, we could have the Transport objects cache requests
 
628
        # See the earlier discussion about how major objects (like Branch)
 
629
        # should never expect their __del__ function to run.
 
630
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
631
            try:
 
632
                shutil.rmtree(self.cache_root)
 
633
            except:
 
634
                pass
 
635
            self.cache_root = None
 
636
 
 
637
    def _get_base(self):
 
638
        if self._transport:
 
639
            return self._transport.base
 
640
        return None
 
641
 
 
642
    base = property(_get_base, doc="The URL for the root of this branch.")
 
643
 
 
644
    def _finish_transaction(self):
 
645
        """Exit the current transaction."""
 
646
        if self._transaction is None:
 
647
            raise errors.LockError('Branch %s is not in a transaction' %
 
648
                                   self)
 
649
        transaction = self._transaction
 
650
        self._transaction = None
 
651
        transaction.finish()
 
652
 
 
653
    def get_transaction(self):
 
654
        """See Branch.get_transaction."""
 
655
        if self._transaction is None:
 
656
            return transactions.PassThroughTransaction()
 
657
        else:
 
658
            return self._transaction
 
659
 
 
660
    def _set_transaction(self, new_transaction):
 
661
        """Set a new active transaction."""
 
662
        if self._transaction is not None:
 
663
            raise errors.LockError('Branch %s is in a transaction already.' %
 
664
                                   self)
 
665
        self._transaction = new_transaction
 
666
 
 
667
    def lock_write(self):
 
668
        #mutter("lock write: %s (%s)", self, self._lock_count)
 
669
        # TODO: Upgrade locking to support using a Transport,
 
670
        # and potentially a remote locking protocol
 
671
        if self._lock_mode:
 
672
            if self._lock_mode != 'w':
 
673
                raise LockError("can't upgrade to a write lock from %r" %
 
674
                                self._lock_mode)
 
675
            self._lock_count += 1
 
676
        else:
 
677
            self._lock = self._transport.lock_write(
 
678
                    self._rel_controlfilename('branch-lock'))
 
679
            self._lock_mode = 'w'
 
680
            self._lock_count = 1
 
681
            self._set_transaction(transactions.PassThroughTransaction())
 
682
 
 
683
    def lock_read(self):
 
684
        #mutter("lock read: %s (%s)", self, self._lock_count)
 
685
        if self._lock_mode:
 
686
            assert self._lock_mode in ('r', 'w'), \
 
687
                   "invalid lock mode %r" % self._lock_mode
 
688
            self._lock_count += 1
 
689
        else:
 
690
            self._lock = self._transport.lock_read(
 
691
                    self._rel_controlfilename('branch-lock'))
 
692
            self._lock_mode = 'r'
 
693
            self._lock_count = 1
 
694
            self._set_transaction(transactions.ReadOnlyTransaction())
 
695
            # 5K may be excessive, but hey, its a knob.
 
696
            self.get_transaction().set_cache_size(5000)
 
697
                        
 
698
    def unlock(self):
 
699
        #mutter("unlock: %s (%s)", self, self._lock_count)
 
700
        if not self._lock_mode:
 
701
            raise LockError('branch %r is not locked' % (self))
 
702
 
 
703
        if self._lock_count > 1:
 
704
            self._lock_count -= 1
 
705
        else:
 
706
            self._finish_transaction()
 
707
            self._lock.unlock()
 
708
            self._lock = None
 
709
            self._lock_mode = self._lock_count = None
 
710
            # TODO: jam 20051230 Consider letting go of the master_branch
 
711
 
 
712
    def abspath(self, name):
 
713
        """See Branch.abspath."""
 
714
        return self._transport.abspath(name)
 
715
 
 
716
    def _rel_controlfilename(self, file_or_path):
 
717
        if not isinstance(file_or_path, basestring):
 
718
            file_or_path = u'/'.join(file_or_path)
 
719
        if file_or_path == '':
 
720
            return bzrlib.BZRDIR
 
721
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
 
722
 
 
723
    def controlfilename(self, file_or_path):
 
724
        """See Branch.controlfilename."""
 
725
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
 
726
 
 
727
    def controlfile(self, file_or_path, mode='r'):
 
728
        """See Branch.controlfile."""
 
729
        import codecs
 
730
 
 
731
        relpath = self._rel_controlfilename(file_or_path)
 
732
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
733
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
734
        if mode == 'rb': 
 
735
            return self._transport.get(relpath)
 
736
        elif mode == 'wb':
 
737
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
 
738
        elif mode == 'r':
 
739
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
740
            # an error, or at least reported, if there's incorrectly-encoded
 
741
            # data inside a file.
 
742
            # <https://launchpad.net/products/bzr/+bug/3823>
 
743
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
744
        elif mode == 'w':
 
745
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
 
746
        else:
 
747
            raise BzrError("invalid controlfile mode %r" % mode)
 
748
 
 
749
    def put_controlfile(self, path, f, encode=True):
 
750
        """See Branch.put_controlfile."""
 
751
        self.put_controlfiles([(path, f)], encode=encode)
 
752
 
 
753
    def put_controlfiles(self, files, encode=True):
 
754
        """See Branch.put_controlfiles."""
 
755
        import codecs
 
756
        ctrl_files = []
 
757
        for path, f in files:
 
758
            if encode:
 
759
                if isinstance(f, basestring):
 
760
                    f = f.encode('utf-8', 'replace')
 
761
                else:
 
762
                    f = codecs.getwriter('utf-8')(f, errors='replace')
 
763
            path = self._rel_controlfilename(path)
 
764
            ctrl_files.append((path, f))
 
765
        self._transport.put_multi(ctrl_files, mode=self._file_mode)
 
766
 
 
767
    def _find_modes(self, path=None):
 
768
        """Determine the appropriate modes for files and directories."""
 
769
        try:
 
770
            if path is None:
 
771
                path = self._rel_controlfilename('')
 
772
            st = self._transport.stat(path)
 
773
        except errors.TransportNotPossible:
 
774
            self._dir_mode = 0755
 
775
            self._file_mode = 0644
 
776
        else:
 
777
            self._dir_mode = st.st_mode & 07777
 
778
            # Remove the sticky and execute bits for files
 
779
            self._file_mode = self._dir_mode & ~07111
 
780
        if not self._set_dir_mode:
 
781
            self._dir_mode = None
 
782
        if not self._set_file_mode:
 
783
            self._file_mode = None
 
784
 
 
785
    def _make_control(self):
 
786
        from bzrlib.inventory import Inventory
 
787
        from bzrlib.weavefile import write_weave_v5
 
788
        from bzrlib.weave import Weave
 
789
        
 
790
        # Create an empty inventory
 
791
        sio = StringIO()
 
792
        # if we want per-tree root ids then this is the place to set
 
793
        # them; they're not needed for now and so ommitted for
 
794
        # simplicity.
 
795
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
 
796
        empty_inv = sio.getvalue()
 
797
        sio = StringIO()
 
798
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
 
799
        empty_weave = sio.getvalue()
 
800
 
 
801
        cfn = self._rel_controlfilename
 
802
        # Since we don't have a .bzr directory, inherit the
 
803
        # mode from the root directory
 
804
        self._find_modes(u'.')
 
805
 
 
806
        dirs = ['', 'revision-store', 'weaves']
 
807
        files = [('README', 
 
808
            "This is a Bazaar-NG control directory.\n"
 
809
            "Do not change any files in this directory.\n"),
 
810
            ('branch-format', BZR_BRANCH_FORMAT_6),
 
811
            ('revision-history', ''),
 
812
            ('branch-name', ''),
 
813
            ('branch-lock', ''),
 
814
            ('pending-merges', ''),
 
815
            ('inventory', empty_inv),
 
816
            ('inventory.weave', empty_weave),
 
817
            ('ancestry.weave', empty_weave)
 
818
        ]
 
819
        self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
 
820
        self.put_controlfiles(files)
 
821
        mutter('created control directory in ' + self._transport.base)
 
822
 
 
823
    def _check_format(self, relax_version_check):
 
824
        """Check this branch format is supported.
 
825
 
 
826
        The format level is stored, as an integer, in
 
827
        self._branch_format for code that needs to check it later.
 
828
 
 
829
        In the future, we might need different in-memory Branch
 
830
        classes to support downlevel branches.  But not yet.
 
831
        """
 
832
        try:
 
833
            fmt = self.controlfile('branch-format', 'r').read()
 
834
        except NoSuchFile:
 
835
            raise NotBranchError(path=self.base)
 
836
        mutter("got branch format %r", fmt)
 
837
        if fmt == BZR_BRANCH_FORMAT_6:
 
838
            self._branch_format = 6
 
839
        elif fmt == BZR_BRANCH_FORMAT_5:
 
840
            self._branch_format = 5
 
841
        elif fmt == BZR_BRANCH_FORMAT_4:
 
842
            self._branch_format = 4
 
843
 
 
844
        if (not relax_version_check
 
845
            and self._branch_format not in (5, 6)):
 
846
            raise errors.UnsupportedFormatError(
 
847
                           'sorry, branch format %r not supported' % fmt,
 
848
                           ['use a different bzr version',
 
849
                            'or remove the .bzr directory'
 
850
                            ' and "bzr init" again'])
 
851
 
 
852
    @needs_read_lock
 
853
    def get_root_id(self):
 
854
        """See Branch.get_root_id."""
 
855
        inv = self.get_inventory(self.last_revision())
 
856
        return inv.root.file_id
 
857
 
 
858
    @needs_read_lock
 
859
    def print_file(self, file, revision_id):
 
860
        """See Branch.print_file."""
 
861
        tree = self.revision_tree(revision_id)
 
862
        # use inventory as it was in that revision
 
863
        file_id = tree.inventory.path2id(file)
 
864
        if not file_id:
 
865
            try:
 
866
                revno = self.revision_id_to_revno(revision_id)
 
867
            except errors.NoSuchRevision:
 
868
                # TODO: This should not be BzrError,
 
869
                # but NoSuchFile doesn't fit either
 
870
                raise BzrError('%r is not present in revision %s' 
 
871
                                % (file, revision_id))
 
872
            else:
 
873
                raise BzrError('%r is not present in revision %s'
 
874
                                % (file, revno))
 
875
        tree.print_file(file_id)
 
876
 
 
877
    @needs_write_lock
 
878
    def append_revision(self, *revision_ids):
 
879
        """See Branch.append_revision."""
 
880
        for revision_id in revision_ids:
 
881
            mutter("add {%s} to revision-history" % revision_id)
 
882
        rev_history = self.revision_history()
 
883
        rev_history.extend(revision_ids)
 
884
        self.set_revision_history(rev_history)
 
885
 
 
886
    @needs_write_lock
 
887
    def set_revision_history(self, rev_history):
 
888
        """See Branch.set_revision_history."""
 
889
        old_revision = self.last_revision()
 
890
        new_revision = rev_history[-1]
 
891
        
 
892
        # TODO: jam 20051230 This is actually just an integrity check
 
893
        #       This shouldn't be necessary, as other code should
 
894
        #       handle making sure this is correct
 
895
        master_branch = self.get_master_branch()
 
896
        if master_branch:
 
897
            master_history = master_branch.revision_history()
 
898
            if rev_history != master_history[:len(rev_history)]:
 
899
                mutter('Invalid revision history, bound branches should always be a subset of their master history')
 
900
                mutter('Local: %s', rev_history)
 
901
                mutter('Master: %s', master_history)
 
902
                assert False, 'Invalid revision history'
 
903
 
 
904
        self.put_controlfile('revision-history', '\n'.join(rev_history))
 
905
        try:
 
906
            self.working_tree().set_last_revision(new_revision, old_revision)
 
907
        except NoWorkingTree:
 
908
            mutter('Unable to set_last_revision without a working tree.')
 
909
 
 
910
    def has_revision(self, revision_id):
 
911
        """See Branch.has_revision."""
 
912
        return (revision_id is None
 
913
                or self.revision_store.has_id(revision_id))
 
914
 
 
915
    @needs_read_lock
 
916
    def _get_revision_xml_file(self, revision_id):
 
917
        if not revision_id or not isinstance(revision_id, basestring):
 
918
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
 
919
        try:
 
920
            return self.revision_store.get(revision_id)
 
921
        except (IndexError, KeyError):
 
922
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
923
 
 
924
    def get_revision_xml(self, revision_id):
 
925
        """See Branch.get_revision_xml."""
 
926
        return self._get_revision_xml_file(revision_id).read()
 
927
 
 
928
    def get_revision(self, revision_id):
 
929
        """See Branch.get_revision."""
 
930
        xml_file = self._get_revision_xml_file(revision_id)
 
931
 
 
932
        try:
 
933
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
934
        except SyntaxError, e:
 
935
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
936
                                         [revision_id,
 
937
                                          str(e)])
 
938
            
 
939
        assert r.revision_id == revision_id
 
940
        return r
 
941
 
 
942
    def get_revision_sha1(self, revision_id):
 
943
        """See Branch.get_revision_sha1."""
 
944
        # In the future, revision entries will be signed. At that
 
945
        # point, it is probably best *not* to include the signature
 
946
        # in the revision hash. Because that lets you re-sign
 
947
        # the revision, (add signatures/remove signatures) and still
 
948
        # have all hash pointers stay consistent.
 
949
        # But for now, just hash the contents.
 
950
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
 
951
 
 
952
    def get_ancestry(self, revision_id):
 
953
        """See Branch.get_ancestry."""
 
954
        if revision_id is None:
 
955
            return [None]
 
956
        w = self._get_inventory_weave()
 
957
        return [None] + map(w.idx_to_name,
 
958
                            w.inclusions([w.lookup(revision_id)]))
 
959
 
 
960
    def _get_inventory_weave(self):
 
961
        return self.control_weaves.get_weave('inventory',
 
962
                                             self.get_transaction())
 
963
 
 
964
    def get_inventory(self, revision_id):
 
965
        """See Branch.get_inventory."""
 
966
        xml = self.get_inventory_xml(revision_id)
 
967
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
968
 
 
969
    def get_inventory_xml(self, revision_id):
 
970
        """See Branch.get_inventory_xml."""
 
971
        try:
 
972
            assert isinstance(revision_id, basestring), type(revision_id)
 
973
            iw = self._get_inventory_weave()
 
974
            return iw.get_text(iw.lookup(revision_id))
 
975
        except IndexError:
 
976
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
 
977
 
 
978
    def get_inventory_sha1(self, revision_id):
 
979
        """See Branch.get_inventory_sha1."""
 
980
        return self.get_revision(revision_id).inventory_sha1
 
981
 
 
982
    def get_revision_inventory(self, revision_id):
 
983
        """See Branch.get_revision_inventory."""
 
984
        # TODO: Unify this with get_inventory()
 
985
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
986
        # must be the same as its revision, so this is trivial.
 
987
        if revision_id == None:
 
988
            # This does not make sense: if there is no revision,
 
989
            # then it is the current tree inventory surely ?!
 
990
            # and thus get_root_id() is something that looks at the last
 
991
            # commit on the branch, and the get_root_id is an inventory check.
 
992
            raise NotImplementedError
 
993
            # return Inventory(self.get_root_id())
 
994
        else:
 
995
            return self.get_inventory(revision_id)
 
996
 
 
997
    @needs_read_lock
 
998
    def revision_history(self):
 
999
        """See Branch.revision_history."""
 
1000
        transaction = self.get_transaction()
 
1001
        history = transaction.map.find_revision_history()
 
1002
        if history is not None:
 
1003
            mutter("cache hit for revision-history in %s", self)
 
1004
            return list(history)
 
1005
        history = [l.rstrip('\r\n') for l in
 
1006
                self.controlfile('revision-history', 'r').readlines()]
 
1007
        transaction.map.add_revision_history(history)
 
1008
        # this call is disabled because revision_history is 
 
1009
        # not really an object yet, and the transaction is for objects.
 
1010
        # transaction.register_clean(history, precious=True)
 
1011
        return list(history)
 
1012
 
 
1013
    def update_revisions(self, other, stop_revision=None):
 
1014
        """See Branch.update_revisions."""
 
1015
        from bzrlib.fetch import greedy_fetch
 
1016
        if stop_revision is None:
 
1017
            stop_revision = other.last_revision()
 
1018
        ### Should this be checking is_ancestor instead of revision_history?
 
1019
        if (stop_revision is not None and 
 
1020
            stop_revision in self.revision_history()):
 
1021
            return
 
1022
        greedy_fetch(to_branch=self, from_branch=other,
 
1023
                     revision=stop_revision)
 
1024
        pullable_revs = self.pullable_revisions(other, stop_revision)
 
1025
        if len(pullable_revs) > 0:
 
1026
            self.append_revision(*pullable_revs)
 
1027
 
 
1028
    def pullable_revisions(self, other, stop_revision):
 
1029
        other_revno = other.revision_id_to_revno(stop_revision)
 
1030
        try:
 
1031
            return self.missing_revisions(other, other_revno)
 
1032
        except DivergedBranches, e:
 
1033
            try:
 
1034
                pullable_revs = get_intervening_revisions(self.last_revision(),
 
1035
                                                          stop_revision, self)
 
1036
                assert self.last_revision() not in pullable_revs
 
1037
                return pullable_revs
 
1038
            except bzrlib.errors.NotAncestor:
 
1039
                if is_ancestor(self.last_revision(), stop_revision, self):
 
1040
                    return []
 
1041
                else:
 
1042
                    raise e
 
1043
        
 
1044
    def revision_tree(self, revision_id):
 
1045
        """See Branch.revision_tree."""
 
1046
        # TODO: refactor this to use an existing revision object
 
1047
        # so we don't need to read it in twice.
 
1048
        if revision_id == None or revision_id == NULL_REVISION:
 
1049
            return EmptyTree()
 
1050
        else:
 
1051
            inv = self.get_revision_inventory(revision_id)
 
1052
            return RevisionTree(self, inv, revision_id)
 
1053
 
 
1054
    def basis_tree(self):
 
1055
        """See Branch.basis_tree."""
 
1056
        try:
 
1057
            revision_id = self.revision_history()[-1]
 
1058
            xml = self.working_tree().read_basis_inventory(revision_id)
 
1059
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
1060
            return RevisionTree(self, inv, revision_id)
 
1061
        except (IndexError, NoSuchFile, NoWorkingTree), e:
 
1062
            return self.revision_tree(self.last_revision())
 
1063
 
 
1064
    def working_tree(self):
 
1065
        """See Branch.working_tree."""
 
1066
        from bzrlib.workingtree import WorkingTree
 
1067
        if self._transport.base.find('://') != -1:
 
1068
            raise NoWorkingTree(self.base)
 
1069
        return WorkingTree(self.base, branch=self)
 
1070
 
 
1071
    @needs_write_lock
 
1072
    def pull(self, source, overwrite=False):
 
1073
        """See Branch.pull."""
 
1074
        # TODO: jam 20051230 This does work, in that 'bzr pull'
 
1075
        #       will update the master branch before updating the
 
1076
        #       local branch. However, 'source' can also already
 
1077
        #       be the master branch. Which means that we are
 
1078
        #       asking it to update from itself, before we continue.
 
1079
        #       This probably causes double downloads, etc.
 
1080
        #       So we probably want to put in an explicit check
 
1081
        #       of whether source is already the master branch.
 
1082
        master_branch = self.get_master_branch()
 
1083
        if master_branch:
 
1084
            # TODO: jam 20051230 It would certainly be possible
 
1085
            #       to overwrite the master branch, I just feel
 
1086
            #       a little funny about doing it. This should be
 
1087
            #       discussed.
 
1088
            if overwrite:
 
1089
                raise errors.OverwriteBoundBranch(self)
 
1090
            # TODO: jam 20051230 Consider updating the working tree
 
1091
            master_branch.pull(source)
 
1092
            source = master_branch
 
1093
 
 
1094
        source.lock_read()
 
1095
        try:
 
1096
            old_count = len(self.revision_history())
 
1097
            try:
 
1098
                self.update_revisions(source)
 
1099
            except DivergedBranches:
 
1100
                if not overwrite:
 
1101
                    raise
 
1102
            if overwrite:
 
1103
                self.set_revision_history(source.revision_history())
 
1104
            new_count = len(self.revision_history())
 
1105
            return new_count - old_count
 
1106
        finally:
 
1107
            source.unlock()
 
1108
 
 
1109
    def get_parent(self):
 
1110
        """See Branch.get_parent."""
 
1111
        import errno
 
1112
        _locs = ['parent', 'pull', 'x-pull']
 
1113
        for l in _locs:
 
1114
            try:
 
1115
                return self.controlfile(l, 'r').read().strip('\n')
 
1116
            except NoSuchFile:
 
1117
                pass
 
1118
        return None
 
1119
 
 
1120
    def get_push_location(self):
 
1121
        """See Branch.get_push_location."""
 
1122
        config = bzrlib.config.BranchConfig(self)
 
1123
        push_loc = config.get_user_option('push_location')
 
1124
        return push_loc
 
1125
 
 
1126
    def set_push_location(self, location):
 
1127
        """See Branch.set_push_location."""
 
1128
        config = bzrlib.config.LocationConfig(self.base)
 
1129
        config.set_user_option('push_location', location)
 
1130
 
 
1131
    @needs_write_lock
 
1132
    def set_parent(self, url):
 
1133
        """See Branch.set_parent."""
 
1134
        # TODO: Maybe delete old location files?
 
1135
        from bzrlib.atomicfile import AtomicFile
 
1136
        f = AtomicFile(self.controlfilename('parent'))
 
1137
        try:
 
1138
            f.write(url + '\n')
 
1139
            f.commit()
 
1140
        finally:
 
1141
            f.close()
 
1142
 
 
1143
    def tree_config(self):
 
1144
        return TreeConfig(self)
 
1145
 
 
1146
    def sign_revision(self, revision_id, gpg_strategy):
 
1147
        """See Branch.sign_revision."""
 
1148
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
 
1149
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
 
1150
 
 
1151
    @needs_write_lock
 
1152
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1153
        """See Branch.store_revision_signature."""
 
1154
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
 
1155
                                revision_id, "sig")
 
1156
 
 
1157
    @needs_read_lock
 
1158
    def get_bound_location(self):
 
1159
        bound_path = self._rel_controlfilename('bound')
 
1160
        try:
 
1161
            f = self._transport.get(bound_path)
 
1162
        except NoSuchFile:
 
1163
            return None
 
1164
        else:
 
1165
            return f.read().strip()
 
1166
 
 
1167
    @needs_read_lock
 
1168
    def get_master_branch(self):
 
1169
        """Return the branch we are bound to.
 
1170
        
 
1171
        :return: Either a Branch, or None
 
1172
        """
 
1173
        bound_loc = self.get_bound_location()
 
1174
        if not bound_loc:
 
1175
            self._master_branch = None
 
1176
            return None
 
1177
        if self._master_branch is None:
 
1178
            try:
 
1179
                self._master_branch = Branch.open(bound_loc)
 
1180
            except (errors.NotBranchError, errors.ConnectionError), e:
 
1181
                raise errors.BoundBranchConnectionFailure(
 
1182
                        self, bound_loc, e)
 
1183
        return self._master_branch
 
1184
 
 
1185
    @needs_write_lock
 
1186
    def set_bound_location(self, location):
 
1187
        """Set the target where this branch is bound to.
 
1188
 
 
1189
        :param location: URL to the target branch
 
1190
        """
 
1191
        self._master_branch = None
 
1192
        if location:
 
1193
            self.put_controlfile('bound', location+'\n')
 
1194
        else:
 
1195
            bound_path = self._rel_controlfilename('bound')
 
1196
            try:
 
1197
                self._transport.delete(bound_path)
 
1198
            except NoSuchFile:
 
1199
                return False
 
1200
            return True
 
1201
 
 
1202
    @needs_write_lock
 
1203
    def bind(self, other):
 
1204
        """Bind the local branch the other branch.
 
1205
 
 
1206
        :param other: The branch to bind to
 
1207
        :type other: Branch
 
1208
        """
 
1209
        # TODO: jam 20051230 According to the API tests, Branch should 
 
1210
        #       avoid knowing about the working tree. However, since on
 
1211
        #       binding A pulls B and B pulls A even if we moved this 
 
1212
        #       code into Working Tree, you get much more complicated
 
1213
        #       logic to handle where one of them has a working tree,
 
1214
        #       but the other one doesn't
 
1215
        #       And all we really end up doing is moving the try/except
 
1216
        #       into builtins.bind()
 
1217
        #       The other issue is that Working Tree should not have
 
1218
        #       a bind() member. Because working trees are not bound.
 
1219
        #       at some point in the future they may be checkouts,
 
1220
        #       which means they reference some other branch. But
 
1221
        #       only the branch itself is bound.
 
1222
        #       I started creating a Working Tree.bind() but realized
 
1223
        #       that was worse than having Branch.bind() try to
 
1224
        #       update its working tree.
 
1225
 
 
1226
        # TODO: jam 20051230 Consider checking if the target is bound
 
1227
        #       It is debatable whether you should be able to bind to
 
1228
        #       a branch which is itself bound.
 
1229
        #       Committing is obviously forbidden,
 
1230
        #       but binding itself may not be.
 
1231
        #       Since we *have* to check at commit time, we don't
 
1232
        #       *need* to check here
 
1233
        
 
1234
        try:
 
1235
            self.working_tree().pull(other)
 
1236
        except NoWorkingTree:
 
1237
            self.pull(other)
 
1238
 
 
1239
        # Since we have 'pulled' from the remote location,
 
1240
        # now we should try to pull in the opposite direction
 
1241
        # in case the local tree has more revisions than the
 
1242
        # remote one.
 
1243
        # There may be a different check you could do here
 
1244
        # rather than actually trying to install revisions remotely.
 
1245
        # TODO: capture an exception which indicates the remote branch
 
1246
        #       is not writeable. 
 
1247
        #       If it is up-to-date, this probably should not be a failure
 
1248
 
 
1249
        # We used to update the working tree here.
 
1250
        # However, it was the only place. 'bzr pull', and 'bzr commit'
 
1251
        # Would not update the remote working tree.
 
1252
        # So for consistency, we are only updating the branch information
 
1253
        other.pull(self)
 
1254
 
 
1255
        # Make sure the revision histories are now identical
 
1256
        other_rh = other.revision_history()
 
1257
        self.set_revision_history(other_rh)
 
1258
 
 
1259
        # Both branches should now be at the same revision
 
1260
        self.set_bound_location(other.base)
 
1261
 
 
1262
    @needs_write_lock
 
1263
    def unbind(self):
 
1264
        """If bound, unbind"""
 
1265
        return self.set_bound_location(None)
 
1266
 
 
1267
 
 
1268
class ScratchBranch(BzrBranch):
1217
1269
    """Special test class: a branch that cleans up after itself.
1218
1270
 
1219
1271
    >>> b = ScratchBranch()
1220
1272
    >>> isdir(b.base)
1221
1273
    True
1222
1274
    >>> bd = b.base
1223
 
    >>> b.destroy()
 
1275
    >>> b._transport.__del__()
1224
1276
    >>> isdir(bd)
1225
1277
    False
1226
1278
    """
1227
 
    def __init__(self, files=[], dirs=[], base=None):
 
1279
 
 
1280
    def __init__(self, files=[], dirs=[], transport=None):
1228
1281
        """Make a test branch.
1229
1282
 
1230
1283
        This creates a temporary directory and runs init-tree in it.
1231
1284
 
1232
1285
        If any files are listed, they are created in the working copy.
1233
1286
        """
1234
 
        from tempfile import mkdtemp
1235
 
        init = False
1236
 
        if base is None:
1237
 
            base = mkdtemp()
1238
 
            init = True
1239
 
        LocalBranch.__init__(self, base, init=init)
 
1287
        if transport is None:
 
1288
            transport = bzrlib.transport.local.ScratchTransport()
 
1289
            super(ScratchBranch, self).__init__(transport, init=True)
 
1290
        else:
 
1291
            super(ScratchBranch, self).__init__(transport)
 
1292
 
1240
1293
        for d in dirs:
1241
 
            os.mkdir(self.abspath(d))
 
1294
            self._transport.mkdir(d)
1242
1295
            
1243
1296
        for f in files:
1244
 
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
1297
            self._transport.put(f, 'content of %s' % f)
1245
1298
 
1246
1299
 
1247
1300
    def clone(self):
1248
1301
        """
1249
1302
        >>> orig = ScratchBranch(files=["file1", "file2"])
1250
1303
        >>> clone = orig.clone()
1251
 
        >>> os.path.samefile(orig.base, clone.base)
 
1304
        >>> if os.name != 'nt':
 
1305
        ...   os.path.samefile(orig.base, clone.base)
 
1306
        ... else:
 
1307
        ...   orig.base == clone.base
 
1308
        ...
1252
1309
        False
1253
 
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
 
1310
        >>> os.path.isfile(pathjoin(clone.base, "file1"))
1254
1311
        True
1255
1312
        """
1256
1313
        from shutil import copytree
1257
 
        from tempfile import mkdtemp
 
1314
        from bzrlib.osutils import mkdtemp
1258
1315
        base = mkdtemp()
1259
1316
        os.rmdir(base)
1260
1317
        copytree(self.base, base, symlinks=True)
1261
 
        return ScratchBranch(base=base)
1262
 
 
1263
 
 
1264
 
        
1265
 
    def __del__(self):
1266
 
        self.destroy()
1267
 
 
1268
 
    def destroy(self):
1269
 
        """Destroy the test branch, removing the scratch directory."""
1270
 
        from shutil import rmtree
1271
 
        try:
1272
 
            if self.base:
1273
 
                mutter("delete ScratchBranch %s" % self.base)
1274
 
                rmtree(self.base)
1275
 
        except OSError, e:
1276
 
            # Work around for shutil.rmtree failing on Windows when
1277
 
            # readonly files are encountered
1278
 
            mutter("hit exception in destroying ScratchBranch: %s" % e)
1279
 
            for root, dirs, files in os.walk(self.base, topdown=False):
1280
 
                for name in files:
1281
 
                    os.chmod(os.path.join(root, name), 0700)
1282
 
            rmtree(self.base)
1283
 
        self.base = None
1284
 
 
 
1318
        return ScratchBranch(
 
1319
            transport=bzrlib.transport.local.ScratchTransport(base))
1285
1320
    
1286
1321
 
1287
1322
######################################################################
1290
1325
 
1291
1326
def is_control_file(filename):
1292
1327
    ## FIXME: better check
1293
 
    filename = os.path.normpath(filename)
 
1328
    filename = normpath(filename)
1294
1329
    while filename != '':
1295
1330
        head, tail = os.path.split(filename)
1296
1331
        ## mutter('check %r for control file' % ((head, tail), ))
1300
1335
            break
1301
1336
        filename = head
1302
1337
    return False
1303
 
 
1304
 
 
1305
 
 
1306
 
def gen_file_id(name):
1307
 
    """Return new file id.
1308
 
 
1309
 
    This should probably generate proper UUIDs, but for the moment we
1310
 
    cope with just randomness because running uuidgen every time is
1311
 
    slow."""
1312
 
    import re
1313
 
    from binascii import hexlify
1314
 
    from time import time
1315
 
 
1316
 
    # get last component
1317
 
    idx = name.rfind('/')
1318
 
    if idx != -1:
1319
 
        name = name[idx+1 : ]
1320
 
    idx = name.rfind('\\')
1321
 
    if idx != -1:
1322
 
        name = name[idx+1 : ]
1323
 
 
1324
 
    # make it not a hidden file
1325
 
    name = name.lstrip('.')
1326
 
 
1327
 
    # remove any wierd characters; we don't escape them but rather
1328
 
    # just pull them out
1329
 
    name = re.sub(r'[^\w.]', '', name)
1330
 
 
1331
 
    s = hexlify(rand_bytes(8))
1332
 
    return '-'.join((name, compact_date(time()), s))
1333
 
 
1334
 
 
1335
 
def gen_root_id():
1336
 
    """Return a new tree-root file id."""
1337
 
    return gen_file_id('TREE_ROOT')
1338
 
 
1339
 
 
1340
 
def copy_branch(branch_from, to_location, revision=None):
1341
 
    """Copy branch_from into the existing directory to_location.
1342
 
 
1343
 
    revision
1344
 
        If not None, only revisions up to this point will be copied.
1345
 
        The head of the new branch will be that revision.
1346
 
 
1347
 
    to_location
1348
 
        The name of a local directory that exists but is empty.
1349
 
    """
1350
 
    from bzrlib.merge import merge
1351
 
    from bzrlib.revisionspec import RevisionSpec
1352
 
 
1353
 
    assert isinstance(branch_from, Branch)
1354
 
    assert isinstance(to_location, basestring)
1355
 
    
1356
 
    br_to = Branch(to_location, init=True)
1357
 
    br_to.set_root_id(branch_from.get_root_id())
1358
 
    if revision is None:
1359
 
        revno = branch_from.revno()
1360
 
    else:
1361
 
        revno, rev_id = RevisionSpec(branch_from, revision)
1362
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1363
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1364
 
          check_clean=False, ignore_zero=True)
1365
 
    
1366
 
    from_location = branch_from.base
1367
 
    br_to.set_parent(branch_from.base)
1368