~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Michael Ellerman
  • Date: 2005-12-10 22:11:13 UTC
  • mto: This revision was merged to the branch mainline in revision 1528.
  • Revision ID: michael@ellerman.id.au-20051210221113-99ca561aaab4661e
Simplify handling of DivergedBranches in cmd_pull()

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
20
21
import errno
23
24
 
24
25
 
25
26
import bzrlib
26
 
from bzrlib.inventory import InventoryEntry
27
27
import bzrlib.inventory as inventory
28
28
from bzrlib.trace import mutter, note
29
 
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
 
29
from bzrlib.osutils import (isdir, quotefn,
30
30
                            rename, splitpath, sha_file, appendpath, 
31
31
                            file_kind, abspath)
32
32
import bzrlib.errors as errors
33
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
34
                           NoSuchRevision, HistoryMissing, NotBranchError,
35
35
                           DivergedBranches, LockError, UnlistableStore,
36
 
                           UnlistableBranch, NoSuchFile, NotVersionedError)
 
36
                           UnlistableBranch, NoSuchFile, NotVersionedError,
 
37
                           NoWorkingTree)
37
38
from bzrlib.textui import show_status
38
 
from bzrlib.revision import Revision, is_ancestor, get_intervening_revisions
 
39
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
 
40
                             NULL_REVISION)
39
41
 
40
42
from bzrlib.delta import compare_trees
41
43
from bzrlib.tree import EmptyTree, RevisionTree
42
44
from bzrlib.inventory import Inventory
43
45
from bzrlib.store import copy_all
44
 
from bzrlib.store.compressed_text import CompressedTextStore
45
46
from bzrlib.store.text import TextStore
46
47
from bzrlib.store.weave import WeaveStore
47
48
from bzrlib.testament import Testament
49
50
from bzrlib.transport import Transport, get_transport
50
51
import bzrlib.xml5
51
52
import bzrlib.ui
 
53
from config import TreeConfig
52
54
 
53
55
 
54
56
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
108
110
        """Open a branch which may be of an old format.
109
111
        
110
112
        Only local branches are supported."""
111
 
        return _Branch(get_transport(base), relax_version_check=True)
 
113
        return BzrBranch(get_transport(base), relax_version_check=True)
112
114
        
113
115
    @staticmethod
114
116
    def open(base):
115
117
        """Open an existing branch, rooted at 'base' (url)"""
116
118
        t = get_transport(base)
117
119
        mutter("trying to open %r with transport %r", base, t)
118
 
        return _Branch(t)
 
120
        return BzrBranch(t)
119
121
 
120
122
    @staticmethod
121
123
    def open_containing(url):
130
132
        t = get_transport(url)
131
133
        while True:
132
134
            try:
133
 
                return _Branch(t), t.relpath(url)
 
135
                return BzrBranch(t), t.relpath(url)
134
136
            except NotBranchError:
135
137
                pass
136
138
            new_t = t.clone('..')
143
145
    def initialize(base):
144
146
        """Create a new branch, rooted at 'base' (url)"""
145
147
        t = get_transport(base)
146
 
        return _Branch(t, init=True)
 
148
        return BzrBranch(t, init=True)
147
149
 
148
150
    def setup_caching(self, cache_root):
149
151
        """Subclasses that care about caching should override this, and set
151
153
        """
152
154
        self.cache_root = cache_root
153
155
 
154
 
 
155
 
class _Branch(Branch):
 
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')
 
178
 
 
179
    def lock_write(self):
 
180
        raise NotImplementedError('lock_write is abstract')
 
181
        
 
182
    def lock_read(self):
 
183
        raise NotImplementedError('lock_read is abstract')
 
184
 
 
185
    def unlock(self):
 
186
        raise NotImplementedError('unlock is abstract')
 
187
 
 
188
    def abspath(self, name):
 
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')
 
195
 
 
196
    def controlfilename(self, file_or_path):
 
197
        """Return location relative to branch."""
 
198
        raise NotImplementedError('controlfilename is abstract')
 
199
 
 
200
    def controlfile(self, file_or_path, mode='r'):
 
201
        """Open a control file for this branch.
 
202
 
 
203
        There are two classes of file in the control directory: text
 
204
        and binary.  binary files are untranslated byte streams.  Text
 
205
        control files are stored with Unix newlines and in UTF-8, even
 
206
        if the platform or locale defaults are different.
 
207
 
 
208
        Controlfiles should almost never be opened in write mode but
 
209
        rather should be atomically copied and replaced using atomicfile.
 
210
        """
 
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')
 
231
 
 
232
    def get_root_id(self):
 
233
        """Return the id of this branches root"""
 
234
        raise NotImplementedError('get_root_id is abstract')
 
235
 
 
236
    def set_root_id(self, file_id):
 
237
        raise NotImplementedError('set_root_id is abstract')
 
238
 
 
239
    def print_file(self, file, revision_id):
 
240
        """Print `file` to stdout."""
 
241
        raise NotImplementedError('print_file is abstract')
 
242
 
 
243
    def append_revision(self, *revision_ids):
 
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')
 
258
 
 
259
    def get_revision(self, revision_id):
 
260
        """Return the Revision object for a named revision"""
 
261
        raise NotImplementedError('get_revision is abstract')
 
262
 
 
263
    def get_revision_delta(self, revno):
 
264
        """Return the delta for one revision.
 
265
 
 
266
        The delta is relative to its mainline predecessor, or the
 
267
        empty tree for revision 1.
 
268
        """
 
269
        assert isinstance(revno, int)
 
270
        rh = self.revision_history()
 
271
        if not (1 <= revno <= len(rh)):
 
272
            raise InvalidRevisionNumber(revno)
 
273
 
 
274
        # revno is 1-based; list is 0-based
 
275
 
 
276
        new_tree = self.revision_tree(rh[revno-1])
 
277
        if revno == 1:
 
278
            old_tree = EmptyTree()
 
279
        else:
 
280
            old_tree = self.revision_tree(rh[revno-2])
 
281
 
 
282
        return compare_trees(old_tree, new_tree)
 
283
 
 
284
    def get_revision_sha1(self, revision_id):
 
285
        """Hash the stored value of a revision, and return it."""
 
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):
 
301
        """Get inventory XML as a file object."""
 
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')
 
307
 
 
308
    def get_revision_inventory(self, revision_id):
 
309
        """Return inventory of a past revision."""
 
310
        raise NotImplementedError('get_revision_inventory is abstract')
 
311
 
 
312
    def revision_history(self):
 
313
        """Return sequence of revision hashes on to this branch."""
 
314
        raise NotImplementedError('revision_history is abstract')
 
315
 
 
316
    def revno(self):
 
317
        """Return current revision number for this branch.
 
318
 
 
319
        That is equivalent to the number of revisions committed to
 
320
        this branch.
 
321
        """
 
322
        return len(self.revision_history())
 
323
 
 
324
    def last_revision(self):
 
325
        """Return last patch hash, or None if no history."""
 
326
        ph = self.revision_history()
 
327
        if ph:
 
328
            return ph[-1]
 
329
        else:
 
330
            return None
 
331
 
 
332
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
333
        """Return a list of new revisions that would perfectly fit.
 
334
        
 
335
        If self and other have not diverged, return a list of the revisions
 
336
        present in other, but missing from self.
 
337
 
 
338
        >>> from bzrlib.commit import commit
 
339
        >>> bzrlib.trace.silent = True
 
340
        >>> br1 = ScratchBranch()
 
341
        >>> br2 = ScratchBranch()
 
342
        >>> br1.missing_revisions(br2)
 
343
        []
 
344
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
 
345
        >>> br1.missing_revisions(br2)
 
346
        [u'REVISION-ID-1']
 
347
        >>> br2.missing_revisions(br1)
 
348
        []
 
349
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
 
350
        >>> br1.missing_revisions(br2)
 
351
        []
 
352
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
 
353
        >>> br1.missing_revisions(br2)
 
354
        [u'REVISION-ID-2A']
 
355
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
 
356
        >>> br1.missing_revisions(br2)
 
357
        Traceback (most recent call last):
 
358
        DivergedBranches: These branches have diverged.  Try merge.
 
359
        """
 
360
        self_history = self.revision_history()
 
361
        self_len = len(self_history)
 
362
        other_history = other.revision_history()
 
363
        other_len = len(other_history)
 
364
        common_index = min(self_len, other_len) -1
 
365
        if common_index >= 0 and \
 
366
            self_history[common_index] != other_history[common_index]:
 
367
            raise DivergedBranches(self, other)
 
368
 
 
369
        if stop_revision is None:
 
370
            stop_revision = other_len
 
371
        else:
 
372
            assert isinstance(stop_revision, int)
 
373
            if stop_revision > other_len:
 
374
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
375
        return other_history[self_len:stop_revision]
 
376
    
 
377
    def update_revisions(self, other, stop_revision=None):
 
378
        """Pull in new perfect-fit revisions."""
 
379
        raise NotImplementedError('update_revisions is abstract')
 
380
 
 
381
    def pullable_revisions(self, other, stop_revision):
 
382
        raise NotImplementedError('pullable_revisions is abstract')
 
383
        
 
384
    def revision_id_to_revno(self, revision_id):
 
385
        """Given a revision id, return its revno"""
 
386
        if revision_id is None:
 
387
            return 0
 
388
        history = self.revision_history()
 
389
        try:
 
390
            return history.index(revision_id) + 1
 
391
        except ValueError:
 
392
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
393
 
 
394
    def get_rev_id(self, revno, history=None):
 
395
        """Find the revision id of the specified revno."""
 
396
        if revno == 0:
 
397
            return None
 
398
        if history is None:
 
399
            history = self.revision_history()
 
400
        elif revno <= 0 or revno > len(history):
 
401
            raise bzrlib.errors.NoSuchRevision(self, revno)
 
402
        return history[revno - 1]
 
403
 
 
404
    def revision_tree(self, revision_id):
 
405
        """Return Tree for a revision on this branch.
 
406
 
 
407
        `revision_id` may be None for the null revision, in which case
 
408
        an `EmptyTree` is returned."""
 
409
        raise NotImplementedError('revision_tree is abstract')
 
410
 
 
411
    def working_tree(self):
 
412
        """Return a `Tree` for the working copy if this is a local branch."""
 
413
        raise NotImplementedError('working_tree is abstract')
 
414
 
 
415
    def pull(self, source, overwrite=False):
 
416
        raise NotImplementedError('pull is abstract')
 
417
 
 
418
    def basis_tree(self):
 
419
        """Return `Tree` object for last revision.
 
420
 
 
421
        If there are no revisions yet, return an `EmptyTree`.
 
422
        """
 
423
        return self.revision_tree(self.last_revision())
 
424
 
 
425
    def rename_one(self, from_rel, to_rel):
 
426
        """Rename one file.
 
427
 
 
428
        This can change the directory or the filename or both.
 
429
        """
 
430
        raise NotImplementedError('rename_one is abstract')
 
431
 
 
432
    def move(self, from_paths, to_name):
 
433
        """Rename files.
 
434
 
 
435
        to_name must exist as a versioned directory.
 
436
 
 
437
        If to_name exists and is a directory, the files are moved into
 
438
        it, keeping their old names.  If it is a directory, 
 
439
 
 
440
        Note that to_name is only the last component of the new name;
 
441
        this doesn't change the directory.
 
442
 
 
443
        This returns a list of (from_path, to_path) pairs for each
 
444
        entry that is moved.
 
445
        """
 
446
        raise NotImplementedError('move is abstract')
 
447
 
 
448
    def get_parent(self):
 
449
        """Return the parent location of the branch.
 
450
 
 
451
        This is the default location for push/pull/missing.  The usual
 
452
        pattern is that the user can override it by specifying a
 
453
        location.
 
454
        """
 
455
        raise NotImplementedError('get_parent is abstract')
 
456
 
 
457
    def get_push_location(self):
 
458
        """Return the None or the location to push this branch to."""
 
459
        raise NotImplementedError('get_push_location is abstract')
 
460
 
 
461
    def set_push_location(self, location):
 
462
        """Set a new push location for this branch."""
 
463
        raise NotImplementedError('set_push_location is abstract')
 
464
 
 
465
    def set_parent(self, url):
 
466
        raise NotImplementedError('set_parent is abstract')
 
467
 
 
468
    def check_revno(self, revno):
 
469
        """\
 
470
        Check whether a revno corresponds to any revision.
 
471
        Zero (the NULL revision) is considered valid.
 
472
        """
 
473
        if revno != 0:
 
474
            self.check_real_revno(revno)
 
475
            
 
476
    def check_real_revno(self, revno):
 
477
        """\
 
478
        Check whether a revno corresponds to a real revision.
 
479
        Zero (the NULL revision) is considered invalid
 
480
        """
 
481
        if revno < 1 or revno > self.revno():
 
482
            raise InvalidRevisionNumber(revno)
 
483
        
 
484
    def sign_revision(self, revision_id, gpg_strategy):
 
485
        raise NotImplementedError('sign_revision is abstract')
 
486
 
 
487
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
488
        raise NotImplementedError('store_revision_signature is abstract')
 
489
 
 
490
class BzrBranch(Branch):
156
491
    """A branch stored in the actual filesystem.
157
492
 
158
493
    Note that it's "local" in the context of the filesystem; it doesn't
184
519
    REVISION_NAMESPACES = {}
185
520
 
186
521
    def push_stores(self, branch_to):
187
 
        """Copy the content of this branches store to branch_to."""
 
522
        """See Branch.push_stores."""
188
523
        if (self._branch_format != branch_to._branch_format
189
524
            or self._branch_format != 4):
190
525
            from bzrlib.fetch import greedy_fetch
208
543
        """Create new branch object at a particular location.
209
544
 
210
545
        transport -- A Transport object, defining how to access files.
211
 
                (If a string, transport.transport() will be used to
212
 
                create a Transport object)
213
546
        
214
547
        init -- If True, create new control files in a previously
215
548
             unversioned directory.  If False, the branch must already
236
569
            # some existing branches where there's a mixture; we probably 
237
570
            # still want the option to look for both.
238
571
            relpath = self._rel_controlfilename(name)
239
 
            if compressed:
240
 
                store = CompressedTextStore(self._transport.clone(relpath),
241
 
                                            prefixed=prefixed)
242
 
            else:
243
 
                store = TextStore(self._transport.clone(relpath),
244
 
                                  prefixed=prefixed)
 
572
            store = TextStore(self._transport.clone(relpath),
 
573
                              prefixed=prefixed,
 
574
                              compressed=compressed)
245
575
            #if self._transport.should_cache():
246
576
            #    cache_path = os.path.join(self.cache_root, name)
247
577
            #    os.mkdir(cache_path)
255
585
            return ws
256
586
 
257
587
        if self._branch_format == 4:
258
 
            self.inventory_store = get_store('inventory-store')
259
 
            self.text_store = get_store('text-store')
260
 
            self.revision_store = get_store('revision-store')
 
588
            self.inventory_store = get_store(u'inventory-store')
 
589
            self.text_store = get_store(u'text-store')
 
590
            self.revision_store = get_store(u'revision-store')
261
591
        elif self._branch_format == 5:
262
 
            self.control_weaves = get_weave([])
263
 
            self.weave_store = get_weave('weaves')
264
 
            self.revision_store = get_store('revision-store', compressed=False)
 
592
            self.control_weaves = get_weave(u'')
 
593
            self.weave_store = get_weave(u'weaves')
 
594
            self.revision_store = get_store(u'revision-store', compressed=False)
265
595
        elif self._branch_format == 6:
266
 
            self.control_weaves = get_weave([])
267
 
            self.weave_store = get_weave('weaves', prefixed=True)
268
 
            self.revision_store = get_store('revision-store', compressed=False,
 
596
            self.control_weaves = get_weave(u'')
 
597
            self.weave_store = get_weave(u'weaves', prefixed=True)
 
598
            self.revision_store = get_store(u'revision-store', compressed=False,
269
599
                                            prefixed=True)
270
600
        self.revision_store.register_suffix('sig')
271
601
        self._transaction = None
273
603
    def __str__(self):
274
604
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
275
605
 
276
 
 
277
606
    __repr__ = __str__
278
607
 
279
 
 
280
608
    def __del__(self):
281
609
        if self._lock_mode or self._lock:
282
610
            # XXX: This should show something every time, and be suitable for
292
620
        # should never expect their __del__ function to run.
293
621
        if hasattr(self, 'cache_root') and self.cache_root is not None:
294
622
            try:
295
 
                import shutil
296
623
                shutil.rmtree(self.cache_root)
297
624
            except:
298
625
                pass
315
642
        transaction.finish()
316
643
 
317
644
    def get_transaction(self):
318
 
        """Return the current active transaction.
319
 
 
320
 
        If no transaction is active, this returns a passthrough object
321
 
        for which all data is immedaitely flushed and no caching happens.
322
 
        """
 
645
        """See Branch.get_transaction."""
323
646
        if self._transaction is None:
324
647
            return transactions.PassThroughTransaction()
325
648
        else:
333
656
        self._transaction = new_transaction
334
657
 
335
658
    def lock_write(self):
336
 
        mutter("lock write: %s (%s)", self, self._lock_count)
 
659
        #mutter("lock write: %s (%s)", self, self._lock_count)
337
660
        # TODO: Upgrade locking to support using a Transport,
338
661
        # and potentially a remote locking protocol
339
662
        if self._lock_mode:
349
672
            self._set_transaction(transactions.PassThroughTransaction())
350
673
 
351
674
    def lock_read(self):
352
 
        mutter("lock read: %s (%s)", self, self._lock_count)
 
675
        #mutter("lock read: %s (%s)", self, self._lock_count)
353
676
        if self._lock_mode:
354
677
            assert self._lock_mode in ('r', 'w'), \
355
678
                   "invalid lock mode %r" % self._lock_mode
364
687
            self.get_transaction().set_cache_size(5000)
365
688
                        
366
689
    def unlock(self):
367
 
        mutter("unlock: %s (%s)", self, self._lock_count)
 
690
        #mutter("unlock: %s (%s)", self, self._lock_count)
368
691
        if not self._lock_mode:
369
692
            raise LockError('branch %r is not locked' % (self))
370
693
 
377
700
            self._lock_mode = self._lock_count = None
378
701
 
379
702
    def abspath(self, name):
380
 
        """Return absolute filename for something in the branch
381
 
        
382
 
        XXX: Robert Collins 20051017 what is this used for? why is it a branch
383
 
        method and not a tree method.
384
 
        """
 
703
        """See Branch.abspath."""
385
704
        return self._transport.abspath(name)
386
705
 
387
706
    def _rel_controlfilename(self, file_or_path):
388
 
        if isinstance(file_or_path, basestring):
389
 
            file_or_path = [file_or_path]
390
 
        return [bzrlib.BZRDIR] + file_or_path
 
707
        if not isinstance(file_or_path, basestring):
 
708
            file_or_path = u'/'.join(file_or_path)
 
709
        if file_or_path == '':
 
710
            return bzrlib.BZRDIR
 
711
        return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
391
712
 
392
713
    def controlfilename(self, file_or_path):
393
 
        """Return location relative to branch."""
 
714
        """See Branch.controlfilename."""
394
715
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
395
716
 
396
 
 
397
717
    def controlfile(self, file_or_path, mode='r'):
398
 
        """Open a control file for this branch.
399
 
 
400
 
        There are two classes of file in the control directory: text
401
 
        and binary.  binary files are untranslated byte streams.  Text
402
 
        control files are stored with Unix newlines and in UTF-8, even
403
 
        if the platform or locale defaults are different.
404
 
 
405
 
        Controlfiles should almost never be opened in write mode but
406
 
        rather should be atomically copied and replaced using atomicfile.
407
 
        """
 
718
        """See Branch.controlfile."""
408
719
        import codecs
409
720
 
410
721
        relpath = self._rel_controlfilename(file_or_path)
415
726
        elif mode == 'wb':
416
727
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
417
728
        elif mode == 'r':
 
729
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
730
            # an error, or at least reported, if there's incorrectly-encoded
 
731
            # data inside a file.
 
732
            # <https://launchpad.net/products/bzr/+bug/3823>
418
733
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
419
734
        elif mode == 'w':
420
735
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
422
737
            raise BzrError("invalid controlfile mode %r" % mode)
423
738
 
424
739
    def put_controlfile(self, path, f, encode=True):
425
 
        """Write an entry as a controlfile.
426
 
 
427
 
        :param path: The path to put the file, relative to the .bzr control
428
 
                     directory
429
 
        :param f: A file-like or string object whose contents should be copied.
430
 
        :param encode:  If true, encode the contents as utf-8
431
 
        """
 
740
        """See Branch.put_controlfile."""
432
741
        self.put_controlfiles([(path, f)], encode=encode)
433
742
 
434
743
    def put_controlfiles(self, files, encode=True):
435
 
        """Write several entries as controlfiles.
436
 
 
437
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
438
 
                      underneath the bzr control directory
439
 
        :param encode:  If true, encode the contents as utf-8
440
 
        """
 
744
        """See Branch.put_controlfiles."""
441
745
        import codecs
442
746
        ctrl_files = []
443
747
        for path, f in files:
513
817
                            'or remove the .bzr directory'
514
818
                            ' and "bzr init" again'])
515
819
 
 
820
    @needs_read_lock
516
821
    def get_root_id(self):
517
 
        """Return the id of this branches root"""
518
 
        inv = self.read_working_inventory()
 
822
        """See Branch.get_root_id."""
 
823
        inv = self.get_inventory(self.last_revision())
519
824
        return inv.root.file_id
520
825
 
521
 
    def set_root_id(self, file_id):
522
 
        inv = self.read_working_inventory()
523
 
        orig_root_id = inv.root.file_id
524
 
        del inv._byid[inv.root.file_id]
525
 
        inv.root.file_id = file_id
526
 
        inv._byid[inv.root.file_id] = inv.root
527
 
        for fid in inv:
528
 
            entry = inv[fid]
529
 
            if entry.parent_id in (None, orig_root_id):
530
 
                entry.parent_id = inv.root.file_id
531
 
        self._write_inventory(inv)
532
 
 
533
 
    @needs_read_lock
534
 
    def read_working_inventory(self):
535
 
        """Read the working inventory."""
536
 
        # ElementTree does its own conversion from UTF-8, so open in
537
 
        # binary.
538
 
        f = self.controlfile('inventory', 'rb')
539
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
540
 
 
541
 
    @needs_write_lock
542
 
    def _write_inventory(self, inv):
543
 
        """Update the working inventory.
544
 
 
545
 
        That is to say, the inventory describing changes underway, that
546
 
        will be committed to the next revision.
547
 
        """
548
 
        from cStringIO import StringIO
549
 
        sio = StringIO()
550
 
        bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
551
 
        sio.seek(0)
552
 
        # Transport handles atomicity
553
 
        self.put_controlfile('inventory', sio)
554
 
        
555
 
        mutter('wrote working inventory')
556
 
            
557
 
    inventory = property(read_working_inventory, _write_inventory, None,
558
 
                         """Inventory for the working copy.""")
559
 
 
560
 
    @needs_write_lock
561
 
    def add(self, files, ids=None):
562
 
        """Make files versioned.
563
 
 
564
 
        Note that the command line normally calls smart_add instead,
565
 
        which can automatically recurse.
566
 
 
567
 
        This puts the files in the Added state, so that they will be
568
 
        recorded by the next commit.
569
 
 
570
 
        files
571
 
            List of paths to add, relative to the base of the tree.
572
 
 
573
 
        ids
574
 
            If set, use these instead of automatically generated ids.
575
 
            Must be the same length as the list of files, but may
576
 
            contain None for ids that are to be autogenerated.
577
 
 
578
 
        TODO: Perhaps have an option to add the ids even if the files do
579
 
              not (yet) exist.
580
 
 
581
 
        TODO: Perhaps yield the ids and paths as they're added.
582
 
        """
583
 
        # TODO: Re-adding a file that is removed in the working copy
584
 
        # should probably put it back with the previous ID.
585
 
        if isinstance(files, basestring):
586
 
            assert(ids is None or isinstance(ids, basestring))
587
 
            files = [files]
588
 
            if ids is not None:
589
 
                ids = [ids]
590
 
 
591
 
        if ids is None:
592
 
            ids = [None] * len(files)
593
 
        else:
594
 
            assert(len(ids) == len(files))
595
 
 
596
 
        inv = self.read_working_inventory()
597
 
        for f,file_id in zip(files, ids):
598
 
            if is_control_file(f):
599
 
                raise BzrError("cannot add control file %s" % quotefn(f))
600
 
 
601
 
            fp = splitpath(f)
602
 
 
603
 
            if len(fp) == 0:
604
 
                raise BzrError("cannot add top-level %r" % f)
605
 
 
606
 
            fullpath = os.path.normpath(self.abspath(f))
607
 
 
608
 
            try:
609
 
                kind = file_kind(fullpath)
610
 
            except OSError:
611
 
                # maybe something better?
612
 
                raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
613
 
 
614
 
            if not InventoryEntry.versionable_kind(kind):
615
 
                raise BzrError('cannot add: not a versionable file ('
616
 
                               'i.e. regular file, symlink or directory): %s' % quotefn(f))
617
 
 
618
 
            if file_id is None:
619
 
                file_id = gen_file_id(f)
620
 
            inv.add_path(f, kind=kind, file_id=file_id)
621
 
 
622
 
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
623
 
 
624
 
        self._write_inventory(inv)
625
 
 
626
 
    @needs_read_lock
627
 
    def print_file(self, file, revno):
628
 
        """Print `file` to stdout."""
629
 
        tree = self.revision_tree(self.get_rev_id(revno))
 
826
    @needs_read_lock
 
827
    def print_file(self, file, revision_id):
 
828
        """See Branch.print_file."""
 
829
        tree = self.revision_tree(revision_id)
630
830
        # use inventory as it was in that revision
631
831
        file_id = tree.inventory.path2id(file)
632
832
        if not file_id:
633
 
            raise BzrError("%r is not present in revision %s" % (file, revno))
 
833
            try:
 
834
                revno = self.revision_id_to_revno(revision_id)
 
835
            except errors.NoSuchRevision:
 
836
                # TODO: This should not be BzrError,
 
837
                # but NoSuchFile doesn't fit either
 
838
                raise BzrError('%r is not present in revision %s' 
 
839
                                % (file, revision_id))
 
840
            else:
 
841
                raise BzrError('%r is not present in revision %s'
 
842
                                % (file, revno))
634
843
        tree.print_file(file_id)
635
844
 
636
 
    # FIXME: this doesn't need to be a branch method
637
 
    def set_inventory(self, new_inventory_list):
638
 
        from bzrlib.inventory import Inventory, InventoryEntry
639
 
        inv = Inventory(self.get_root_id())
640
 
        for path, file_id, parent, kind in new_inventory_list:
641
 
            name = os.path.basename(path)
642
 
            if name == "":
643
 
                continue
644
 
            # fixme, there should be a factory function inv,add_?? 
645
 
            if kind == 'directory':
646
 
                inv.add(inventory.InventoryDirectory(file_id, name, parent))
647
 
            elif kind == 'file':
648
 
                inv.add(inventory.InventoryFile(file_id, name, parent))
649
 
            elif kind == 'symlink':
650
 
                inv.add(inventory.InventoryLink(file_id, name, parent))
651
 
            else:
652
 
                raise BzrError("unknown kind %r" % kind)
653
 
        self._write_inventory(inv)
654
 
 
655
 
    def unknowns(self):
656
 
        """Return all unknown files.
657
 
 
658
 
        These are files in the working directory that are not versioned or
659
 
        control files or ignored.
660
 
        
661
 
        >>> from bzrlib.workingtree import WorkingTree
662
 
        >>> b = ScratchBranch(files=['foo', 'foo~'])
663
 
        >>> map(str, b.unknowns())
664
 
        ['foo']
665
 
        >>> b.add('foo')
666
 
        >>> list(b.unknowns())
667
 
        []
668
 
        >>> WorkingTree(b.base, b).remove('foo')
669
 
        >>> list(b.unknowns())
670
 
        ['foo']
671
 
        """
672
 
        return self.working_tree().unknowns()
673
 
 
674
845
    @needs_write_lock
675
846
    def append_revision(self, *revision_ids):
 
847
        """See Branch.append_revision."""
676
848
        for revision_id in revision_ids:
677
849
            mutter("add {%s} to revision-history" % revision_id)
678
850
        rev_history = self.revision_history()
679
851
        rev_history.extend(revision_ids)
 
852
        self.set_revision_history(rev_history)
 
853
 
 
854
    @needs_write_lock
 
855
    def set_revision_history(self, rev_history):
 
856
        """See Branch.set_revision_history."""
 
857
        old_revision = self.last_revision()
 
858
        new_revision = rev_history[-1]
680
859
        self.put_controlfile('revision-history', '\n'.join(rev_history))
 
860
        try:
 
861
            self.working_tree().set_last_revision(new_revision, old_revision)
 
862
        except NoWorkingTree:
 
863
            mutter('Unable to set_last_revision without a working tree.')
681
864
 
682
865
    def has_revision(self, revision_id):
683
 
        """True if this branch has a copy of the revision.
684
 
 
685
 
        This does not necessarily imply the revision is merge
686
 
        or on the mainline."""
 
866
        """See Branch.has_revision."""
687
867
        return (revision_id is None
688
868
                or self.revision_store.has_id(revision_id))
689
869
 
690
870
    @needs_read_lock
691
 
    def get_revision_xml_file(self, revision_id):
692
 
        """Return XML file object for revision object."""
 
871
    def _get_revision_xml_file(self, revision_id):
693
872
        if not revision_id or not isinstance(revision_id, basestring):
694
 
            raise InvalidRevisionId(revision_id)
 
873
            raise InvalidRevisionId(revision_id=revision_id, branch=self)
695
874
        try:
696
875
            return self.revision_store.get(revision_id)
697
876
        except (IndexError, KeyError):
698
877
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
699
878
 
700
 
    #deprecated
701
 
    get_revision_xml = get_revision_xml_file
702
 
 
703
879
    def get_revision_xml(self, revision_id):
704
 
        return self.get_revision_xml_file(revision_id).read()
705
 
 
 
880
        """See Branch.get_revision_xml."""
 
881
        return self._get_revision_xml_file(revision_id).read()
706
882
 
707
883
    def get_revision(self, revision_id):
708
 
        """Return the Revision object for a named revision"""
709
 
        xml_file = self.get_revision_xml_file(revision_id)
 
884
        """See Branch.get_revision."""
 
885
        xml_file = self._get_revision_xml_file(revision_id)
710
886
 
711
887
        try:
712
888
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
718
894
        assert r.revision_id == revision_id
719
895
        return r
720
896
 
721
 
    def get_revision_delta(self, revno):
722
 
        """Return the delta for one revision.
723
 
 
724
 
        The delta is relative to its mainline predecessor, or the
725
 
        empty tree for revision 1.
726
 
        """
727
 
        assert isinstance(revno, int)
728
 
        rh = self.revision_history()
729
 
        if not (1 <= revno <= len(rh)):
730
 
            raise InvalidRevisionNumber(revno)
731
 
 
732
 
        # revno is 1-based; list is 0-based
733
 
 
734
 
        new_tree = self.revision_tree(rh[revno-1])
735
 
        if revno == 1:
736
 
            old_tree = EmptyTree()
737
 
        else:
738
 
            old_tree = self.revision_tree(rh[revno-2])
739
 
 
740
 
        return compare_trees(old_tree, new_tree)
741
 
 
742
897
    def get_revision_sha1(self, revision_id):
743
 
        """Hash the stored value of a revision, and return it."""
 
898
        """See Branch.get_revision_sha1."""
744
899
        # In the future, revision entries will be signed. At that
745
900
        # point, it is probably best *not* to include the signature
746
901
        # in the revision hash. Because that lets you re-sign
750
905
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
751
906
 
752
907
    def get_ancestry(self, revision_id):
753
 
        """Return a list of revision-ids integrated by a revision.
754
 
        
755
 
        This currently returns a list, but the ordering is not guaranteed:
756
 
        treat it as a set.
757
 
        """
 
908
        """See Branch.get_ancestry."""
758
909
        if revision_id is None:
759
910
            return [None]
760
 
        w = self.get_inventory_weave()
 
911
        w = self._get_inventory_weave()
761
912
        return [None] + map(w.idx_to_name,
762
913
                            w.inclusions([w.lookup(revision_id)]))
763
914
 
764
 
    def get_inventory_weave(self):
 
915
    def _get_inventory_weave(self):
765
916
        return self.control_weaves.get_weave('inventory',
766
917
                                             self.get_transaction())
767
918
 
768
919
    def get_inventory(self, revision_id):
769
 
        """Get Inventory object by hash."""
 
920
        """See Branch.get_inventory."""
770
921
        xml = self.get_inventory_xml(revision_id)
771
922
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
772
923
 
773
924
    def get_inventory_xml(self, revision_id):
774
 
        """Get inventory XML as a file object."""
 
925
        """See Branch.get_inventory_xml."""
775
926
        try:
776
927
            assert isinstance(revision_id, basestring), type(revision_id)
777
 
            iw = self.get_inventory_weave()
 
928
            iw = self._get_inventory_weave()
778
929
            return iw.get_text(iw.lookup(revision_id))
779
930
        except IndexError:
780
931
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
781
932
 
782
933
    def get_inventory_sha1(self, revision_id):
783
 
        """Return the sha1 hash of the inventory entry
784
 
        """
 
934
        """See Branch.get_inventory_sha1."""
785
935
        return self.get_revision(revision_id).inventory_sha1
786
936
 
787
937
    def get_revision_inventory(self, revision_id):
788
 
        """Return inventory of a past revision."""
 
938
        """See Branch.get_revision_inventory."""
789
939
        # TODO: Unify this with get_inventory()
790
940
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
791
941
        # must be the same as its revision, so this is trivial.
792
942
        if revision_id == None:
793
 
            return Inventory(self.get_root_id())
 
943
            # This does not make sense: if there is no revision,
 
944
            # then it is the current tree inventory surely ?!
 
945
            # and thus get_root_id() is something that looks at the last
 
946
            # commit on the branch, and the get_root_id is an inventory check.
 
947
            raise NotImplementedError
 
948
            # return Inventory(self.get_root_id())
794
949
        else:
795
950
            return self.get_inventory(revision_id)
796
951
 
797
952
    @needs_read_lock
798
953
    def revision_history(self):
799
 
        """Return sequence of revision hashes on to this branch."""
 
954
        """See Branch.revision_history."""
800
955
        transaction = self.get_transaction()
801
956
        history = transaction.map.find_revision_history()
802
957
        if history is not None:
810
965
        # transaction.register_clean(history, precious=True)
811
966
        return list(history)
812
967
 
813
 
    def revno(self):
814
 
        """Return current revision number for this branch.
815
 
 
816
 
        That is equivalent to the number of revisions committed to
817
 
        this branch.
818
 
        """
819
 
        return len(self.revision_history())
820
 
 
821
 
    def last_revision(self):
822
 
        """Return last patch hash, or None if no history.
823
 
        """
824
 
        ph = self.revision_history()
825
 
        if ph:
826
 
            return ph[-1]
827
 
        else:
828
 
            return None
829
 
 
830
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
831
 
        """Return a list of new revisions that would perfectly fit.
832
 
        
833
 
        If self and other have not diverged, return a list of the revisions
834
 
        present in other, but missing from self.
835
 
 
836
 
        >>> from bzrlib.commit import commit
837
 
        >>> bzrlib.trace.silent = True
838
 
        >>> br1 = ScratchBranch()
839
 
        >>> br2 = ScratchBranch()
840
 
        >>> br1.missing_revisions(br2)
841
 
        []
842
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-1")
843
 
        >>> br1.missing_revisions(br2)
844
 
        [u'REVISION-ID-1']
845
 
        >>> br2.missing_revisions(br1)
846
 
        []
847
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1")
848
 
        >>> br1.missing_revisions(br2)
849
 
        []
850
 
        >>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
851
 
        >>> br1.missing_revisions(br2)
852
 
        [u'REVISION-ID-2A']
853
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
854
 
        >>> br1.missing_revisions(br2)
855
 
        Traceback (most recent call last):
856
 
        DivergedBranches: These branches have diverged.
857
 
        """
858
 
        self_history = self.revision_history()
859
 
        self_len = len(self_history)
860
 
        other_history = other.revision_history()
861
 
        other_len = len(other_history)
862
 
        common_index = min(self_len, other_len) -1
863
 
        if common_index >= 0 and \
864
 
            self_history[common_index] != other_history[common_index]:
865
 
            raise DivergedBranches(self, other)
866
 
 
867
 
        if stop_revision is None:
868
 
            stop_revision = other_len
869
 
        else:
870
 
            assert isinstance(stop_revision, int)
871
 
            if stop_revision > other_len:
872
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
873
 
        return other_history[self_len:stop_revision]
874
 
 
875
968
    def update_revisions(self, other, stop_revision=None):
876
 
        """Pull in new perfect-fit revisions."""
877
 
        # FIXME: If the branches have diverged, but the latest
878
 
        # revision in this branch is completely merged into the other,
879
 
        # then we should still be able to pull.
 
969
        """See Branch.update_revisions."""
880
970
        from bzrlib.fetch import greedy_fetch
881
971
        if stop_revision is None:
882
972
            stop_revision = other.last_revision()
891
981
            self.append_revision(*pullable_revs)
892
982
 
893
983
    def pullable_revisions(self, other, stop_revision):
 
984
        """See Branch.pullable_revisions."""
894
985
        other_revno = other.revision_id_to_revno(stop_revision)
895
986
        try:
896
987
            return self.missing_revisions(other, other_revno)
906
997
                else:
907
998
                    raise e
908
999
        
909
 
    def commit(self, *args, **kw):
910
 
        from bzrlib.commit import Commit
911
 
        Commit().commit(self, *args, **kw)
912
 
    
913
 
    def revision_id_to_revno(self, revision_id):
914
 
        """Given a revision id, return its revno"""
915
 
        if revision_id is None:
916
 
            return 0
917
 
        history = self.revision_history()
918
 
        try:
919
 
            return history.index(revision_id) + 1
920
 
        except ValueError:
921
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
922
 
 
923
 
    def get_rev_id(self, revno, history=None):
924
 
        """Find the revision id of the specified revno."""
925
 
        if revno == 0:
926
 
            return None
927
 
        if history is None:
928
 
            history = self.revision_history()
929
 
        elif revno <= 0 or revno > len(history):
930
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
931
 
        return history[revno - 1]
932
 
 
933
1000
    def revision_tree(self, revision_id):
934
 
        """Return Tree for a revision on this branch.
935
 
 
936
 
        `revision_id` may be None for the null revision, in which case
937
 
        an `EmptyTree` is returned."""
 
1001
        """See Branch.revision_tree."""
938
1002
        # TODO: refactor this to use an existing revision object
939
1003
        # so we don't need to read it in twice.
940
 
        if revision_id == None:
 
1004
        if revision_id == None or revision_id == NULL_REVISION:
941
1005
            return EmptyTree()
942
1006
        else:
943
1007
            inv = self.get_revision_inventory(revision_id)
944
1008
            return RevisionTree(self.weave_store, inv, revision_id)
945
1009
 
 
1010
    def basis_tree(self):
 
1011
        """See Branch.basis_tree."""
 
1012
        try:
 
1013
            revision_id = self.revision_history()[-1]
 
1014
            xml = self.working_tree().read_basis_inventory(revision_id)
 
1015
            inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
 
1016
            return RevisionTree(self.weave_store, inv, revision_id)
 
1017
        except (IndexError, NoSuchFile, NoWorkingTree), e:
 
1018
            return self.revision_tree(self.last_revision())
 
1019
 
946
1020
    def working_tree(self):
947
 
        """Return a `Tree` for the working copy."""
 
1021
        """See Branch.working_tree."""
948
1022
        from bzrlib.workingtree import WorkingTree
949
 
        # TODO: In the future, perhaps WorkingTree should utilize Transport
950
 
        # RobertCollins 20051003 - I don't think it should - working trees are
951
 
        # much more complex to keep consistent than our careful .bzr subset.
952
 
        # instead, we should say that working trees are local only, and optimise
953
 
        # for that.
 
1023
        if self._transport.base.find('://') != -1:
 
1024
            raise NoWorkingTree(self.base)
954
1025
        return WorkingTree(self.base, branch=self)
955
1026
 
956
 
 
957
 
    def basis_tree(self):
958
 
        """Return `Tree` object for last revision.
959
 
 
960
 
        If there are no revisions yet, return an `EmptyTree`.
961
 
        """
962
 
        return self.revision_tree(self.last_revision())
963
 
 
964
1027
    @needs_write_lock
965
 
    def rename_one(self, from_rel, to_rel):
966
 
        """Rename one file.
967
 
 
968
 
        This can change the directory or the filename or both.
969
 
        """
970
 
        tree = self.working_tree()
971
 
        inv = tree.inventory
972
 
        if not tree.has_filename(from_rel):
973
 
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
974
 
        if tree.has_filename(to_rel):
975
 
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
976
 
 
977
 
        file_id = inv.path2id(from_rel)
978
 
        if file_id == None:
979
 
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
980
 
 
981
 
        if inv.path2id(to_rel):
982
 
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
983
 
 
984
 
        to_dir, to_tail = os.path.split(to_rel)
985
 
        to_dir_id = inv.path2id(to_dir)
986
 
        if to_dir_id == None and to_dir != '':
987
 
            raise BzrError("can't determine destination directory id for %r" % to_dir)
988
 
 
989
 
        mutter("rename_one:")
990
 
        mutter("  file_id    {%s}" % file_id)
991
 
        mutter("  from_rel   %r" % from_rel)
992
 
        mutter("  to_rel     %r" % to_rel)
993
 
        mutter("  to_dir     %r" % to_dir)
994
 
        mutter("  to_dir_id  {%s}" % to_dir_id)
995
 
 
996
 
        inv.rename(file_id, to_dir_id, to_tail)
997
 
 
998
 
        from_abs = self.abspath(from_rel)
999
 
        to_abs = self.abspath(to_rel)
 
1028
    def pull(self, source, overwrite=False):
 
1029
        """See Branch.pull."""
 
1030
        source.lock_read()
1000
1031
        try:
1001
 
            rename(from_abs, to_abs)
1002
 
        except OSError, e:
1003
 
            raise BzrError("failed to rename %r to %r: %s"
1004
 
                    % (from_abs, to_abs, e[1]),
1005
 
                    ["rename rolled back"])
1006
 
 
1007
 
        self._write_inventory(inv)
1008
 
 
1009
 
    @needs_write_lock
1010
 
    def move(self, from_paths, to_name):
1011
 
        """Rename files.
1012
 
 
1013
 
        to_name must exist as a versioned directory.
1014
 
 
1015
 
        If to_name exists and is a directory, the files are moved into
1016
 
        it, keeping their old names.  If it is a directory, 
1017
 
 
1018
 
        Note that to_name is only the last component of the new name;
1019
 
        this doesn't change the directory.
1020
 
 
1021
 
        This returns a list of (from_path, to_path) pairs for each
1022
 
        entry that is moved.
1023
 
        """
1024
 
        result = []
1025
 
        ## TODO: Option to move IDs only
1026
 
        assert not isinstance(from_paths, basestring)
1027
 
        tree = self.working_tree()
1028
 
        inv = tree.inventory
1029
 
        to_abs = self.abspath(to_name)
1030
 
        if not isdir(to_abs):
1031
 
            raise BzrError("destination %r is not a directory" % to_abs)
1032
 
        if not tree.has_filename(to_name):
1033
 
            raise BzrError("destination %r not in working directory" % to_abs)
1034
 
        to_dir_id = inv.path2id(to_name)
1035
 
        if to_dir_id == None and to_name != '':
1036
 
            raise BzrError("destination %r is not a versioned directory" % to_name)
1037
 
        to_dir_ie = inv[to_dir_id]
1038
 
        if to_dir_ie.kind not in ('directory', 'root_directory'):
1039
 
            raise BzrError("destination %r is not a directory" % to_abs)
1040
 
 
1041
 
        to_idpath = inv.get_idpath(to_dir_id)
1042
 
 
1043
 
        for f in from_paths:
1044
 
            if not tree.has_filename(f):
1045
 
                raise BzrError("%r does not exist in working tree" % f)
1046
 
            f_id = inv.path2id(f)
1047
 
            if f_id == None:
1048
 
                raise BzrError("%r is not versioned" % f)
1049
 
            name_tail = splitpath(f)[-1]
1050
 
            dest_path = appendpath(to_name, name_tail)
1051
 
            if tree.has_filename(dest_path):
1052
 
                raise BzrError("destination %r already exists" % dest_path)
1053
 
            if f_id in to_idpath:
1054
 
                raise BzrError("can't move %r to a subdirectory of itself" % f)
1055
 
 
1056
 
        # OK, so there's a race here, it's possible that someone will
1057
 
        # create a file in this interval and then the rename might be
1058
 
        # left half-done.  But we should have caught most problems.
1059
 
 
1060
 
        for f in from_paths:
1061
 
            name_tail = splitpath(f)[-1]
1062
 
            dest_path = appendpath(to_name, name_tail)
1063
 
            result.append((f, dest_path))
1064
 
            inv.rename(inv.path2id(f), to_dir_id, name_tail)
1065
 
            try:
1066
 
                rename(self.abspath(f), self.abspath(dest_path))
1067
 
            except OSError, e:
1068
 
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1069
 
                        ["rename rolled back"])
1070
 
 
1071
 
        self._write_inventory(inv)
1072
 
        return result
1073
 
 
1074
 
 
1075
 
    def revert(self, filenames, old_tree=None, backups=True):
1076
 
        """Restore selected files to the versions from a previous tree.
1077
 
 
1078
 
        backups
1079
 
            If true (default) backups are made of files before
1080
 
            they're renamed.
1081
 
        """
1082
 
        from bzrlib.atomicfile import AtomicFile
1083
 
        from bzrlib.osutils import backup_file
1084
 
        
1085
 
        inv = self.read_working_inventory()
1086
 
        if old_tree is None:
1087
 
            old_tree = self.basis_tree()
1088
 
        old_inv = old_tree.inventory
1089
 
 
1090
 
        nids = []
1091
 
        for fn in filenames:
1092
 
            file_id = inv.path2id(fn)
1093
 
            if not file_id:
1094
 
                raise NotVersionedError(path=fn)
1095
 
            if not old_inv.has_id(file_id):
1096
 
                raise BzrError("file not present in old tree", fn, file_id)
1097
 
            nids.append((fn, file_id))
1098
 
            
1099
 
        # TODO: Rename back if it was previously at a different location
1100
 
 
1101
 
        # TODO: If given a directory, restore the entire contents from
1102
 
        # the previous version.
1103
 
 
1104
 
        # TODO: Make a backup to a temporary file.
1105
 
 
1106
 
        # TODO: If the file previously didn't exist, delete it?
1107
 
        for fn, file_id in nids:
1108
 
            backup_file(fn)
1109
 
            
1110
 
            f = AtomicFile(fn, 'wb')
1111
 
            try:
1112
 
                f.write(old_tree.get_file(file_id).read())
1113
 
                f.commit()
1114
 
            finally:
1115
 
                f.close()
1116
 
 
1117
 
 
1118
 
    def pending_merges(self):
1119
 
        """Return a list of pending merges.
1120
 
 
1121
 
        These are revisions that have been merged into the working
1122
 
        directory but not yet committed.
1123
 
        """
1124
 
        cfn = self._rel_controlfilename('pending-merges')
1125
 
        if not self._transport.has(cfn):
1126
 
            return []
1127
 
        p = []
1128
 
        for l in self.controlfile('pending-merges', 'r').readlines():
1129
 
            p.append(l.rstrip('\n'))
1130
 
        return p
1131
 
 
1132
 
 
1133
 
    def add_pending_merge(self, *revision_ids):
1134
 
        # TODO: Perhaps should check at this point that the
1135
 
        # history of the revision is actually present?
1136
 
        p = self.pending_merges()
1137
 
        updated = False
1138
 
        for rev_id in revision_ids:
1139
 
            if rev_id in p:
1140
 
                continue
1141
 
            p.append(rev_id)
1142
 
            updated = True
1143
 
        if updated:
1144
 
            self.set_pending_merges(p)
1145
 
 
1146
 
    @needs_write_lock
1147
 
    def set_pending_merges(self, rev_list):
1148
 
        self.put_controlfile('pending-merges', '\n'.join(rev_list))
 
1032
            old_count = len(self.revision_history())
 
1033
            try:
 
1034
                self.update_revisions(source)
 
1035
            except DivergedBranches:
 
1036
                if not overwrite:
 
1037
                    raise
 
1038
            if overwrite:
 
1039
                self.set_revision_history(source.revision_history())
 
1040
            new_count = len(self.revision_history())
 
1041
            return new_count - old_count
 
1042
        finally:
 
1043
            source.unlock()
1149
1044
 
1150
1045
    def get_parent(self):
1151
 
        """Return the parent location of the branch.
1152
 
 
1153
 
        This is the default location for push/pull/missing.  The usual
1154
 
        pattern is that the user can override it by specifying a
1155
 
        location.
1156
 
        """
 
1046
        """See Branch.get_parent."""
1157
1047
        import errno
1158
1048
        _locs = ['parent', 'pull', 'x-pull']
1159
1049
        for l in _locs:
1160
1050
            try:
1161
1051
                return self.controlfile(l, 'r').read().strip('\n')
1162
 
            except IOError, e:
1163
 
                if e.errno != errno.ENOENT:
1164
 
                    raise
 
1052
            except NoSuchFile:
 
1053
                pass
1165
1054
        return None
1166
1055
 
 
1056
    def get_push_location(self):
 
1057
        """See Branch.get_push_location."""
 
1058
        config = bzrlib.config.BranchConfig(self)
 
1059
        push_loc = config.get_user_option('push_location')
 
1060
        return push_loc
 
1061
 
 
1062
    def set_push_location(self, location):
 
1063
        """See Branch.set_push_location."""
 
1064
        config = bzrlib.config.LocationConfig(self.base)
 
1065
        config.set_user_option('push_location', location)
 
1066
 
1167
1067
    @needs_write_lock
1168
1068
    def set_parent(self, url):
 
1069
        """See Branch.set_parent."""
1169
1070
        # TODO: Maybe delete old location files?
1170
1071
        from bzrlib.atomicfile import AtomicFile
1171
1072
        f = AtomicFile(self.controlfilename('parent'))
1175
1076
        finally:
1176
1077
            f.close()
1177
1078
 
1178
 
    def check_revno(self, revno):
1179
 
        """\
1180
 
        Check whether a revno corresponds to any revision.
1181
 
        Zero (the NULL revision) is considered valid.
1182
 
        """
1183
 
        if revno != 0:
1184
 
            self.check_real_revno(revno)
1185
 
            
1186
 
    def check_real_revno(self, revno):
1187
 
        """\
1188
 
        Check whether a revno corresponds to a real revision.
1189
 
        Zero (the NULL revision) is considered invalid
1190
 
        """
1191
 
        if revno < 1 or revno > self.revno():
1192
 
            raise InvalidRevisionNumber(revno)
1193
 
        
 
1079
    def tree_config(self):
 
1080
        return TreeConfig(self)
 
1081
 
1194
1082
    def sign_revision(self, revision_id, gpg_strategy):
 
1083
        """See Branch.sign_revision."""
1195
1084
        plaintext = Testament.from_revision(self, revision_id).as_short_text()
1196
1085
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1197
1086
 
1198
1087
    @needs_write_lock
1199
1088
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
 
1089
        """See Branch.store_revision_signature."""
1200
1090
        self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)), 
1201
1091
                                revision_id, "sig")
1202
1092
 
1203
1093
 
1204
 
class ScratchBranch(_Branch):
 
1094
class ScratchBranch(BzrBranch):
1205
1095
    """Special test class: a branch that cleans up after itself.
1206
1096
 
1207
1097
    >>> b = ScratchBranch()
1271
1161
            break
1272
1162
        filename = head
1273
1163
    return False
1274
 
 
1275
 
 
1276
 
 
1277
 
def gen_file_id(name):
1278
 
    """Return new file id.
1279
 
 
1280
 
    This should probably generate proper UUIDs, but for the moment we
1281
 
    cope with just randomness because running uuidgen every time is
1282
 
    slow."""
1283
 
    import re
1284
 
    from binascii import hexlify
1285
 
    from time import time
1286
 
 
1287
 
    # get last component
1288
 
    idx = name.rfind('/')
1289
 
    if idx != -1:
1290
 
        name = name[idx+1 : ]
1291
 
    idx = name.rfind('\\')
1292
 
    if idx != -1:
1293
 
        name = name[idx+1 : ]
1294
 
 
1295
 
    # make it not a hidden file
1296
 
    name = name.lstrip('.')
1297
 
 
1298
 
    # remove any wierd characters; we don't escape them but rather
1299
 
    # just pull them out
1300
 
    name = re.sub(r'[^\w.]', '', name)
1301
 
 
1302
 
    s = hexlify(rand_bytes(8))
1303
 
    return '-'.join((name, compact_date(time()), s))
1304
 
 
1305
 
 
1306
 
def gen_root_id():
1307
 
    """Return a new tree-root file id."""
1308
 
    return gen_file_id('TREE_ROOT')
1309
 
 
1310