~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2005-09-15 21:35:53 UTC
  • mfrom: (907.1.57)
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050915213552-a6c83a5ef1e20897
(broken) Transport work is merged in. Tests do not pass yet.

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import sys
19
19
import os
20
 
import errno
21
 
from warnings import warn
22
 
from cStringIO import StringIO
23
 
 
24
20
 
25
21
import bzrlib
26
 
from bzrlib.inventory import InventoryEntry
27
 
import bzrlib.inventory as inventory
28
22
from bzrlib.trace import mutter, note
29
 
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
30
 
                            rename, splitpath, sha_file, appendpath, 
31
 
                            file_kind)
32
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
33
 
                           NoSuchRevision, HistoryMissing, NotBranchError,
34
 
                           DivergedBranches, LockError, UnlistableStore,
35
 
                           UnlistableBranch, NoSuchFile)
 
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
36
29
from bzrlib.textui import show_status
37
30
from bzrlib.revision import Revision
38
31
from bzrlib.delta import compare_trees
39
32
from bzrlib.tree import EmptyTree, RevisionTree
40
 
from bzrlib.inventory import Inventory
41
 
from bzrlib.store import copy_all
42
 
from bzrlib.store.compressed_text import CompressedTextStore
43
 
from bzrlib.store.text import TextStore
44
 
from bzrlib.store.weave import WeaveStore
45
 
from bzrlib.transport import Transport, get_transport
46
 
import bzrlib.xml5
 
33
import bzrlib.xml
47
34
import bzrlib.ui
48
35
 
49
36
 
50
 
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
51
 
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
37
 
 
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
52
39
## TODO: Maybe include checks for common corruption of newlines, etc?
53
40
 
54
41
 
55
42
# TODO: Some operations like log might retrieve the same revisions
56
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
57
 
# cache in memory to make this faster.  In general anything can be
58
 
# cached in memory between lock and unlock operations.
59
 
 
60
 
def find_branch(*ignored, **ignored_too):
61
 
    # XXX: leave this here for about one release, then remove it
62
 
    raise NotImplementedError('find_branch() is not supported anymore, '
63
 
                              'please use one of the new branch constructors')
 
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
    from bzrlib.transport import transport
 
52
    from bzrlib.local_transport import LocalTransport
 
53
    t = transport(f)
 
54
    # FIXME: This is a hack around transport so that
 
55
    #        We can search the local directories for
 
56
    #        a branch root.
 
57
    if args.has_key('init') and args['init']:
 
58
        # Don't search if we are init-ing
 
59
        return Branch(t, **args)
 
60
    if isinstance(t, LocalTransport):
 
61
        root = find_branch_root(f)
 
62
        if root != f:
 
63
            t = transport(root)
 
64
    return Branch(t, **args)
 
65
 
64
66
def _relpath(base, path):
65
67
    """Return path relative to base, or raise exception.
66
68
 
88
90
    return os.sep.join(s)
89
91
        
90
92
 
91
 
def find_branch_root(t):
92
 
    """Find the branch root enclosing the transport's base.
93
 
 
94
 
    t is a Transport object.
95
 
 
96
 
    It is not necessary that the base of t exists.
 
93
def find_branch_root(f=None):
 
94
    """Find the branch root enclosing f, or pwd.
 
95
 
 
96
    f may be a filename or a URL.
 
97
 
 
98
    It is not necessary that f exists.
97
99
 
98
100
    Basically we keep looking up until we find the control directory or
99
101
    run into the root.  If there isn't one, raises NotBranchError.
100
102
    """
101
 
    orig_base = t.base
 
103
    if f == None:
 
104
        f = os.getcwd()
 
105
    else:
 
106
        f = os.path.realpath(f)
 
107
    if not os.path.exists(f):
 
108
        raise BzrError('%r does not exist' % f)
 
109
        
 
110
 
 
111
    orig_f = f
 
112
 
102
113
    while True:
103
 
        if t.has(bzrlib.BZRDIR):
104
 
            return t
105
 
        new_t = t.clone('..')
106
 
        if new_t.base == t.base:
 
114
        if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
 
115
            return f
 
116
        head, tail = os.path.split(f)
 
117
        if head == f:
107
118
            # reached the root, whatever that may be
108
 
            raise NotBranchError('%s is not in a branch' % orig_base)
109
 
        t = new_t
 
119
            raise NotBranchError('%s is not in a branch' % orig_f)
 
120
        f = head
 
121
 
 
122
 
110
123
 
111
124
 
112
125
######################################################################
116
129
    """Branch holding a history of revisions.
117
130
 
118
131
    base
119
 
        Base directory/url of the branch.
120
 
    """
121
 
    base = None
122
 
 
123
 
    def __init__(self, *ignored, **ignored_too):
124
 
        raise NotImplementedError('The Branch class is abstract')
125
 
 
126
 
    @staticmethod
127
 
    def open_downlevel(base):
128
 
        """Open a branch which may be of an old format.
129
 
        
130
 
        Only local branches are supported."""
131
 
        return _Branch(get_transport(base), relax_version_check=True)
132
 
        
133
 
    @staticmethod
134
 
    def open(base):
135
 
        """Open an existing branch, rooted at 'base' (url)"""
136
 
        t = get_transport(base)
137
 
        mutter("trying to open %r with transport %r", base, t)
138
 
        return _Branch(t)
139
 
 
140
 
    @staticmethod
141
 
    def open_containing(url):
142
 
        """Open an existing branch which contains url.
143
 
        
144
 
        This probes for a branch at url, and searches upwards from there.
145
 
        """
146
 
        t = get_transport(url)
147
 
        t = find_branch_root(t)
148
 
        return _Branch(t)
149
 
 
150
 
    @staticmethod
151
 
    def initialize(base):
152
 
        """Create a new branch, rooted at 'base' (url)"""
153
 
        t = get_transport(base)
154
 
        return _Branch(t, init=True)
155
 
 
156
 
    def setup_caching(self, cache_root):
157
 
        """Subclasses that care about caching should override this, and set
158
 
        up cached stores located under cache_root.
159
 
        """
160
 
        self.cache_root = cache_root
161
 
 
162
 
 
163
 
class _Branch(Branch):
164
 
    """A branch stored in the actual filesystem.
165
 
 
166
 
    Note that it's "local" in the context of the filesystem; it doesn't
167
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
168
 
    it's writable, and can be accessed via the normal filesystem API.
 
132
        Base directory of the branch.
169
133
 
170
134
    _lock_mode
171
135
        None, or 'r' or 'w'
177
141
    _lock
178
142
        Lock object from bzrlib.lock.
179
143
    """
180
 
    # We actually expect this class to be somewhat short-lived; part of its
181
 
    # purpose is to try to isolate what bits of the branch logic are tied to
182
 
    # filesystem access, so that in a later step, we can extricate them to
183
 
    # a separarte ("storage") class.
 
144
    base = None
184
145
    _lock_mode = None
185
146
    _lock_count = None
186
147
    _lock = None
187
 
    _inventory_weave = None
 
148
    cache_root = None
188
149
    
189
150
    # Map some sort of prefix into a namespace
190
151
    # stuff like "revno:10", "revid:", etc.
191
152
    # This should match a prefix with a function which accepts
192
153
    REVISION_NAMESPACES = {}
193
154
 
194
 
    def push_stores(self, branch_to):
195
 
        """Copy the content of this branches store to branch_to."""
196
 
        if (self._branch_format != branch_to._branch_format
197
 
            or self._branch_format != 4):
198
 
            from bzrlib.fetch import greedy_fetch
199
 
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
200
 
                   self, self._branch_format, branch_to, branch_to._branch_format)
201
 
            greedy_fetch(to_branch=branch_to, from_branch=self,
202
 
                         revision=self.last_revision())
203
 
            return
204
 
 
205
 
        store_pairs = ((self.text_store,      branch_to.text_store),
206
 
                       (self.inventory_store, branch_to.inventory_store),
207
 
                       (self.revision_store,  branch_to.revision_store))
208
 
        try:
209
 
            for from_store, to_store in store_pairs: 
210
 
                copy_all(from_store, to_store)
211
 
        except UnlistableStore:
212
 
            raise UnlistableBranch(from_store)
213
 
 
214
 
    def __init__(self, transport, init=False,
215
 
                 relax_version_check=False):
 
155
    def __init__(self, transport, init=False):
216
156
        """Create new branch object at a particular location.
217
157
 
218
158
        transport -- A Transport object, defining how to access files.
223
163
             unversioned directory.  If False, the branch must already
224
164
             be versioned.
225
165
 
226
 
        relax_version_check -- If true, the usual check for the branch
227
 
            version is not applied.  This is intended only for
228
 
            upgrade/recovery type use; it's not guaranteed that
229
 
            all operations will work on old format branches.
230
 
 
231
166
        In the test suite, creation of new trees is tested using the
232
167
        `ScratchBranch` class.
233
168
        """
234
 
        assert isinstance(transport, Transport), \
235
 
            "%r is not a Transport" % transport
 
169
        if isinstance(transport, basestring):
 
170
            from transport import transport as get_transport
 
171
            transport = get_transport(transport)
 
172
 
236
173
        self._transport = transport
237
174
        if init:
238
175
            self._make_control()
239
 
        self._check_format(relax_version_check)
240
 
 
241
 
        def get_store(name, compressed=True):
242
 
            # FIXME: This approach of assuming stores are all entirely compressed
243
 
            # or entirely uncompressed is tidy, but breaks upgrade from 
244
 
            # some existing branches where there's a mixture; we probably 
245
 
            # still want the option to look for both.
246
 
            relpath = self._rel_controlfilename(name)
247
 
            if compressed:
248
 
                store = CompressedTextStore(self._transport.clone(relpath))
249
 
            else:
250
 
                store = TextStore(self._transport.clone(relpath))
251
 
            #if self._transport.should_cache():
252
 
            #    cache_path = os.path.join(self.cache_root, name)
253
 
            #    os.mkdir(cache_path)
254
 
            #    store = bzrlib.store.CachedStore(store, cache_path)
255
 
            return store
256
 
        def get_weave(name):
257
 
            relpath = self._rel_controlfilename(name)
258
 
            ws = WeaveStore(self._transport.clone(relpath))
259
 
            if self._transport.should_cache():
260
 
                ws.enable_cache = True
261
 
            return ws
262
 
 
263
 
        if self._branch_format == 4:
264
 
            self.inventory_store = get_store('inventory-store')
265
 
            self.text_store = get_store('text-store')
266
 
            self.revision_store = get_store('revision-store')
267
 
        elif self._branch_format == 5:
268
 
            self.control_weaves = get_weave([])
269
 
            self.weave_store = get_weave('weaves')
270
 
            self.revision_store = get_store('revision-store', compressed=False)
 
176
        self._check_format()
 
177
 
271
178
 
272
179
    def __str__(self):
273
180
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
278
185
 
279
186
    def __del__(self):
280
187
        if self._lock_mode or self._lock:
281
 
            # XXX: This should show something every time, and be suitable for
282
 
            # headless operation and embedding
 
188
            from bzrlib.warnings import warn
283
189
            warn("branch %r was not explicitly unlocked" % self)
284
190
            self._lock.unlock()
285
191
 
289
195
        # Alternatively, we could have the Transport objects cache requests
290
196
        # See the earlier discussion about how major objects (like Branch)
291
197
        # should never expect their __del__ function to run.
292
 
        if hasattr(self, 'cache_root') and self.cache_root is not None:
 
198
        if self.cache_root is not None:
 
199
            #from warnings import warn
 
200
            #warn("branch %r auto-cleanup of cache files" % self)
293
201
            try:
294
202
                import shutil
295
203
                shutil.rmtree(self.cache_root)
310
218
        # and potentially a remote locking protocol
311
219
        if self._lock_mode:
312
220
            if self._lock_mode != 'w':
 
221
                from bzrlib.errors import LockError
313
222
                raise LockError("can't upgrade to a write lock from %r" %
314
223
                                self._lock_mode)
315
224
            self._lock_count += 1
333
242
                        
334
243
    def unlock(self):
335
244
        if not self._lock_mode:
 
245
            from bzrlib.errors import LockError
336
246
            raise LockError('branch %r is not locked' % (self))
337
247
 
338
248
        if self._lock_count > 1:
421
331
 
422
332
    def _make_control(self):
423
333
        from bzrlib.inventory import Inventory
424
 
        from bzrlib.weavefile import write_weave_v5
425
 
        from bzrlib.weave import Weave
 
334
        from cStringIO import StringIO
426
335
        
427
336
        # Create an empty inventory
428
337
        sio = StringIO()
429
338
        # if we want per-tree root ids then this is the place to set
430
339
        # them; they're not needed for now and so ommitted for
431
340
        # simplicity.
432
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
433
 
        empty_inv = sio.getvalue()
434
 
        sio = StringIO()
435
 
        bzrlib.weavefile.write_weave_v5(Weave(), sio)
436
 
        empty_weave = sio.getvalue()
 
341
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), sio)
437
342
 
438
 
        dirs = [[], 'revision-store', 'weaves']
 
343
        dirs = [[], 'text-store', 'inventory-store', 'revision-store']
439
344
        files = [('README', 
440
345
            "This is a Bazaar-NG control directory.\n"
441
346
            "Do not change any files in this directory.\n"),
442
 
            ('branch-format', BZR_BRANCH_FORMAT_5),
 
347
            ('branch-format', BZR_BRANCH_FORMAT),
443
348
            ('revision-history', ''),
 
349
            ('merged-patches', ''),
 
350
            ('pending-merged-patches', ''),
444
351
            ('branch-name', ''),
445
352
            ('branch-lock', ''),
446
353
            ('pending-merges', ''),
447
 
            ('inventory', empty_inv),
448
 
            ('inventory.weave', empty_weave),
449
 
            ('ancestry.weave', empty_weave)
 
354
            ('inventory', sio.getvalue())
450
355
        ]
451
 
        cfn = self._rel_controlfilename
452
 
        self._transport.mkdir_multi([cfn(d) for d in dirs])
 
356
        self._transport.mkdir_multi([self._rel_controlfilename(d) for d in dirs])
453
357
        self.put_controlfiles(files)
454
358
        mutter('created control directory in ' + self._transport.base)
455
359
 
456
 
    def _check_format(self, relax_version_check):
 
360
    def _check_format(self):
457
361
        """Check this branch format is supported.
458
362
 
459
 
        The format level is stored, as an integer, in
460
 
        self._branch_format for code that needs to check it later.
 
363
        The current tool only supports the current unstable format.
461
364
 
462
365
        In the future, we might need different in-memory Branch
463
366
        classes to support downlevel branches.  But not yet.
464
367
        """
465
 
        try:
466
 
            fmt = self.controlfile('branch-format', 'r').read()
467
 
        except NoSuchFile:
468
 
            raise NotBranchError(self.base)
469
 
        mutter("got branch format %r", fmt)
470
 
        if fmt == BZR_BRANCH_FORMAT_5:
471
 
            self._branch_format = 5
472
 
        elif fmt == BZR_BRANCH_FORMAT_4:
473
 
            self._branch_format = 4
474
 
 
475
 
        if (not relax_version_check
476
 
            and self._branch_format != 5):
 
368
        # This ignores newlines so that we can open branches created
 
369
        # on Windows from Linux and so on.  I think it might be better
 
370
        # to always make all internal files in unix format.
 
371
        fmt = self.controlfile('branch-format', 'r').read()
 
372
        fmt = fmt.replace('\r\n', '\n')
 
373
        if fmt != BZR_BRANCH_FORMAT:
477
374
            raise BzrError('sorry, branch format %r not supported' % fmt,
478
375
                           ['use a different bzr version',
479
 
                            'or remove the .bzr directory'
480
 
                            ' and "bzr init" again'])
 
376
                            'or remove the .bzr directory and "bzr init" again'])
 
377
 
 
378
        # We know that the format is the currently supported one.
 
379
        # So create the rest of the entries.
 
380
        from bzrlib.store.compressed_text import CompressedTextStore
 
381
 
 
382
        if self._transport.should_cache():
 
383
            import tempfile
 
384
            self.cache_root = tempfile.mkdtemp(prefix='bzr-cache')
 
385
            mutter('Branch %r using caching in %r' % (self, self.cache_root))
 
386
        else:
 
387
            self.cache_root = None
 
388
 
 
389
        def get_store(name):
 
390
            relpath = self._rel_controlfilename(name)
 
391
            store = CompressedTextStore(self._transport.clone(relpath))
 
392
            if self._transport.should_cache():
 
393
                from meta_store import CachedStore
 
394
                cache_path = os.path.join(self.cache_root, name)
 
395
                os.mkdir(cache_path)
 
396
                store = CachedStore(store, cache_path)
 
397
            return store
 
398
 
 
399
        self.text_store = get_store('text-store')
 
400
        self.revision_store = get_store('revision-store')
 
401
        self.inventory_store = get_store('inventory-store')
481
402
 
482
403
    def get_root_id(self):
483
404
        """Return the id of this branches root"""
498
419
 
499
420
    def read_working_inventory(self):
500
421
        """Read the working inventory."""
 
422
        from bzrlib.inventory import Inventory
501
423
        self.lock_read()
502
424
        try:
503
425
            # ElementTree does its own conversion from UTF-8, so open in
504
426
            # binary.
505
427
            f = self.controlfile('inventory', 'rb')
506
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
 
428
            return bzrlib.xml.serializer_v4.read_inventory(f)
507
429
        finally:
508
430
            self.unlock()
509
431
            
518
440
        self.lock_write()
519
441
        try:
520
442
            sio = StringIO()
521
 
            bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
 
443
            bzrlib.xml.serializer_v4.write_inventory(inv, sio)
522
444
            sio.seek(0)
523
445
            # Transport handles atomicity
524
446
            self.put_controlfile('inventory', sio)
527
449
        
528
450
        mutter('wrote working inventory')
529
451
            
 
452
 
530
453
    inventory = property(read_working_inventory, _write_inventory, None,
531
454
                         """Inventory for the working copy.""")
532
455
 
 
456
 
533
457
    def add(self, files, ids=None):
534
458
        """Make files versioned.
535
459
 
583
507
                    kind = file_kind(fullpath)
584
508
                except OSError:
585
509
                    # maybe something better?
586
 
                    raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
 
510
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
587
511
 
588
 
                if not InventoryEntry.versionable_kind(kind):
589
 
                    raise BzrError('cannot add: not a versionable file ('
590
 
                                   'i.e. regular file, symlink or directory): %s' % quotefn(f))
 
512
                if kind != 'file' and kind != 'directory':
 
513
                    raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
591
514
 
592
515
                if file_id is None:
593
516
                    file_id = gen_file_id(f)
604
527
        """Print `file` to stdout."""
605
528
        self.lock_read()
606
529
        try:
607
 
            tree = self.revision_tree(self.get_rev_id(revno))
 
530
            tree = self.revision_tree(self.lookup_revision(revno))
608
531
            # use inventory as it was in that revision
609
532
            file_id = tree.inventory.path2id(file)
610
533
            if not file_id:
658
581
        finally:
659
582
            self.unlock()
660
583
 
 
584
 
661
585
    # FIXME: this doesn't need to be a branch method
662
586
    def set_inventory(self, new_inventory_list):
663
587
        from bzrlib.inventory import Inventory, InventoryEntry
666
590
            name = os.path.basename(path)
667
591
            if name == "":
668
592
                continue
669
 
            # fixme, there should be a factory function inv,add_?? 
670
 
            if kind == 'directory':
671
 
                inv.add(inventory.InventoryDirectory(file_id, name, parent))
672
 
            elif kind == 'file':
673
 
                inv.add(inventory.InventoryFile(file_id, name, parent))
674
 
            elif kind == 'symlink':
675
 
                inv.add(inventory.InventoryLink(file_id, name, parent))
676
 
            else:
677
 
                raise BzrError("unknown kind %r" % kind)
 
593
            inv.add(InventoryEntry(file_id, name, kind, parent))
678
594
        self._write_inventory(inv)
679
595
 
 
596
 
680
597
    def unknowns(self):
681
598
        """Return all unknown files.
682
599
 
699
616
    def append_revision(self, *revision_ids):
700
617
        for revision_id in revision_ids:
701
618
            mutter("add {%s} to revision-history" % revision_id)
 
619
 
 
620
        rev_history = self.revision_history()
 
621
        rev_history.extend(revision_ids)
 
622
 
702
623
        self.lock_write()
703
624
        try:
704
 
            rev_history = self.revision_history()
705
 
            rev_history.extend(revision_ids)
706
625
            self.put_controlfile('revision-history', '\n'.join(rev_history))
707
626
        finally:
708
627
            self.unlock()
709
628
 
710
 
    def has_revision(self, revision_id):
711
 
        """True if this branch has a copy of the revision.
712
 
 
713
 
        This does not necessarily imply the revision is merge
714
 
        or on the mainline."""
715
 
        return (revision_id is None
716
 
                or revision_id in self.revision_store)
717
629
 
718
630
    def get_revision_xml_file(self, revision_id):
719
631
        """Return XML file object for revision object."""
729
641
        finally:
730
642
            self.unlock()
731
643
 
 
644
 
732
645
    #deprecated
733
646
    get_revision_xml = get_revision_xml_file
734
647
 
735
 
    def get_revision_xml(self, revision_id):
736
 
        return self.get_revision_xml_file(revision_id).read()
737
 
 
738
648
 
739
649
    def get_revision(self, revision_id):
740
650
        """Return the Revision object for a named revision"""
741
651
        xml_file = self.get_revision_xml_file(revision_id)
742
652
 
743
653
        try:
744
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
654
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
745
655
        except SyntaxError, e:
746
656
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
747
657
                                         [revision_id,
750
660
        assert r.revision_id == revision_id
751
661
        return r
752
662
 
 
663
 
753
664
    def get_revision_delta(self, revno):
754
665
        """Return the delta for one revision.
755
666
 
771
682
 
772
683
        return compare_trees(old_tree, new_tree)
773
684
 
 
685
        
 
686
    def get_revisions(self, revision_ids, pb=None):
 
687
        """Return the Revision object for a set of named revisions"""
 
688
        from bzrlib.revision import Revision
 
689
        from bzrlib.xml import unpack_xml
 
690
 
 
691
        # TODO: We need to decide what to do here
 
692
        # we cannot use a generator with a try/finally, because
 
693
        # you cannot guarantee that the caller will iterate through
 
694
        # all entries.
 
695
        # in the past, get_inventory wasn't even wrapped in a
 
696
        # try/finally locking block.
 
697
        # We could either lock without the try/finally, or just
 
698
        # not lock at all. We are reading entries that should
 
699
        # never be updated.
 
700
        # I prefer locking with no finally, so that if someone
 
701
        # asks for a list of revisions, but doesn't consume them,
 
702
        # that is their problem, and they will suffer the consequences
 
703
        self.lock_read()
 
704
        for xml_file in self.revision_store.get(revision_ids, pb=pb):
 
705
            try:
 
706
                r = bzrlib.xml.serializer_v4.read_revision(xml_file)
 
707
            except SyntaxError, e:
 
708
                raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
709
                                             [revision_id,
 
710
                                              str(e)])
 
711
            yield r
 
712
        self.unlock()
 
713
            
774
714
    def get_revision_sha1(self, revision_id):
775
715
        """Hash the stored value of a revision, and return it."""
776
716
        # In the future, revision entries will be signed. At that
779
719
        # the revision, (add signatures/remove signatures) and still
780
720
        # have all hash pointers stay consistent.
781
721
        # But for now, just hash the contents.
782
 
        return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
783
 
 
784
 
    def get_ancestry(self, revision_id):
785
 
        """Return a list of revision-ids integrated by a revision.
786
 
        
787
 
        This currently returns a list, but the ordering is not guaranteed:
788
 
        treat it as a set.
 
722
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
 
723
 
 
724
 
 
725
    def get_inventory(self, inventory_id):
 
726
        """Get Inventory object by hash.
 
727
 
 
728
        TODO: Perhaps for this and similar methods, take a revision
 
729
               parameter which can be either an integer revno or a
 
730
               string hash.
789
731
        """
790
 
        if revision_id is None:
791
 
            return [None]
792
 
        w = self.control_weaves.get_weave('inventory')
793
 
        return [None] + map(w.idx_to_name,
794
 
                            w.inclusions([w.lookup(revision_id)]))
795
 
 
796
 
    def get_inventory_weave(self):
797
 
        return self.control_weaves.get_weave('inventory')
798
 
 
799
 
    def get_inventory(self, revision_id):
800
 
        """Get Inventory object by hash."""
801
 
        xml = self.get_inventory_xml(revision_id)
802
 
        return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
803
 
 
804
 
    def get_inventory_xml(self, revision_id):
 
732
        f = self.get_inventory_xml_file(inventory_id)
 
733
        return bzrlib.xml.serializer_v4.read_inventory(f)
 
734
 
 
735
 
 
736
    def get_inventory_xml(self, inventory_id):
805
737
        """Get inventory XML as a file object."""
806
 
        try:
807
 
            assert isinstance(revision_id, basestring), type(revision_id)
808
 
            iw = self.get_inventory_weave()
809
 
            return iw.get_text(iw.lookup(revision_id))
810
 
        except IndexError:
811
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
812
 
 
813
 
    def get_inventory_sha1(self, revision_id):
 
738
        # Shouldn't this have a read-lock around it?
 
739
        # As well as some sort of trap for missing ids?
 
740
        return self.inventory_store[inventory_id]
 
741
 
 
742
    get_inventory_xml_file = get_inventory_xml
 
743
            
 
744
    def get_inventories(self, inventory_ids, pb=None, ignore_missing=False):
 
745
        """Get Inventory objects by id
 
746
        """
 
747
        from bzrlib.inventory import Inventory
 
748
 
 
749
        # See the discussion in get_revisions for why
 
750
        # we don't use a try/finally block here
 
751
        self.lock_read()
 
752
        for f in self.inventory_store.get(inventory_ids, pb=pb, ignore_missing=ignore_missing):
 
753
            if f is not None:
 
754
                # TODO: Possibly put a try/except around this to handle
 
755
                # read serialization errors
 
756
                r = bzrlib.xml.serializer_v4.read_inventory(f)
 
757
                yield r
 
758
            elif ignore_missing:
 
759
                yield None
 
760
            else:
 
761
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
762
        self.unlock()
 
763
 
 
764
    def get_inventory_sha1(self, inventory_id):
814
765
        """Return the sha1 hash of the inventory entry
815
766
        """
816
 
        return self.get_revision(revision_id).inventory_sha1
 
767
        return sha_file(self.get_inventory_xml(inventory_id))
 
768
 
817
769
 
818
770
    def get_revision_inventory(self, revision_id):
819
771
        """Return inventory of a past revision."""
820
 
        # TODO: Unify this with get_inventory()
821
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
772
        # bzr 0.0.6 imposes the constraint that the inventory_id
822
773
        # must be the same as its revision, so this is trivial.
823
774
        if revision_id == None:
 
775
            from bzrlib.inventory import Inventory
824
776
            return Inventory(self.get_root_id())
825
777
        else:
826
778
            return self.get_inventory(revision_id)
827
779
 
 
780
 
828
781
    def revision_history(self):
829
 
        """Return sequence of revision hashes on to this branch."""
 
782
        """Return sequence of revision hashes on to this branch.
 
783
 
 
784
        >>> ScratchBranch().revision_history()
 
785
        []
 
786
        """
830
787
        self.lock_read()
831
788
        try:
832
789
            return [l.rstrip('\r\n') for l in
834
791
        finally:
835
792
            self.unlock()
836
793
 
 
794
 
837
795
    def common_ancestor(self, other, self_revno=None, other_revno=None):
838
796
        """
839
797
        >>> from bzrlib.commit import commit
888
846
        return len(self.revision_history())
889
847
 
890
848
 
891
 
    def last_revision(self):
 
849
    def last_patch(self):
892
850
        """Return last patch hash, or None if no history.
893
851
        """
894
852
        ph = self.revision_history()
899
857
 
900
858
 
901
859
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
902
 
        """Return a list of new revisions that would perfectly fit.
903
 
        
 
860
        """
904
861
        If self and other have not diverged, return a list of the revisions
905
862
        present in other, but missing from self.
906
863
 
926
883
        Traceback (most recent call last):
927
884
        DivergedBranches: These branches have diverged.
928
885
        """
929
 
        # FIXME: If the branches have diverged, but the latest
930
 
        # revision in this branch is completely merged into the other,
931
 
        # then we should still be able to pull.
932
886
        self_history = self.revision_history()
933
887
        self_len = len(self_history)
934
888
        other_history = other.revision_history()
940
894
 
941
895
        if stop_revision is None:
942
896
            stop_revision = other_len
943
 
        else:
944
 
            assert isinstance(stop_revision, int)
945
 
            if stop_revision > other_len:
946
 
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
897
        elif stop_revision > other_len:
 
898
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
899
        
947
900
        return other_history[self_len:stop_revision]
948
901
 
 
902
 
949
903
    def update_revisions(self, other, stop_revision=None):
950
 
        """Pull in new perfect-fit revisions."""
 
904
        """Pull in all new revisions from other branch.
 
905
        """
951
906
        from bzrlib.fetch import greedy_fetch
952
907
        from bzrlib.revision import get_intervening_revisions
 
908
 
 
909
        pb = bzrlib.ui.ui_factory.progress_bar()
 
910
        pb.update('comparing histories')
953
911
        if stop_revision is None:
954
 
            stop_revision = other.last_revision()
955
 
        greedy_fetch(to_branch=self, from_branch=other,
956
 
                     revision=stop_revision)
957
 
        pullable_revs = self.missing_revisions(
958
 
            other, other.revision_id_to_revno(stop_revision))
959
 
        if pullable_revs:
960
 
            greedy_fetch(to_branch=self,
961
 
                         from_branch=other,
962
 
                         revision=pullable_revs[-1])
963
 
            self.append_revision(*pullable_revs)
964
 
    
 
912
            other_revision = other.last_patch()
 
913
        else:
 
914
            other_revision = other.lookup_revision(stop_revision)
 
915
        count = greedy_fetch(self, other, other_revision, pb)[0]
 
916
        try:
 
917
            revision_ids = self.missing_revisions(other, stop_revision)
 
918
        except DivergedBranches, e:
 
919
            try:
 
920
                revision_ids = get_intervening_revisions(self.last_patch(), 
 
921
                                                         other_revision, self)
 
922
                assert self.last_patch() not in revision_ids
 
923
            except bzrlib.errors.NotAncestor:
 
924
                raise e
 
925
 
 
926
        self.append_revision(*revision_ids)
 
927
        pb.clear()
 
928
 
 
929
    def install_revisions(self, other, revision_ids, pb):
 
930
        # We are going to iterate this many times, so make sure
 
931
        # that it is a list, and not a generator
 
932
        revision_ids = list(revision_ids)
 
933
        if hasattr(other.revision_store, "prefetch"):
 
934
            other.revision_store.prefetch(revision_ids)
 
935
        if hasattr(other.inventory_store, "prefetch"):
 
936
            other.inventory_store.prefetch(inventory_ids)
 
937
 
 
938
        if pb is None:
 
939
            pb = bzrlib.ui.ui_factory.progress_bar()
 
940
                
 
941
        # This entire next section is generally done
 
942
        # with either generators, or bulk updates
 
943
        inventories = other.get_inventories(revision_ids, ignore_missing=True)
 
944
        needed_texts = set()
 
945
 
 
946
        failures = set()
 
947
        good_revisions = set()
 
948
        for i, (inv, rev_id) in enumerate(zip(inventories, revision_ids)):
 
949
            pb.update('fetching revision', i+1, len(revision_ids))
 
950
 
 
951
            # We don't really need to get the revision here, because
 
952
            # the only thing we needed was the inventory_id, which now
 
953
            # is (by design) identical to the revision_id
 
954
            # try:
 
955
            #     rev = other.get_revision(rev_id)
 
956
            # except bzrlib.errors.NoSuchRevision:
 
957
            #     failures.add(rev_id)
 
958
            #     continue
 
959
 
 
960
            if inv is None:
 
961
                failures.add(rev_id)
 
962
                continue
 
963
            else:
 
964
                good_revisions.add(rev_id)
 
965
 
 
966
            text_ids = []
 
967
            for key, entry in inv.iter_entries():
 
968
                if entry.text_id is None:
 
969
                    continue
 
970
                text_ids.append(entry.text_id)
 
971
 
 
972
            has_ids = self.text_store.has(text_ids)
 
973
            for has, text_id in zip(has_ids, text_ids):
 
974
                if not has:
 
975
                    needed_texts.add(text_id)
 
976
 
 
977
        pb.clear()
 
978
                    
 
979
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
980
                                                    needed_texts)
 
981
        #print "Added %d texts." % count 
 
982
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
 
983
                                                         good_revisions)
 
984
        #print "Added %d inventories." % count 
 
985
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
986
                                                          good_revisions,
 
987
                                                          permit_failure=True)
 
988
        assert len(cp_fail) == 0 
 
989
        return count, failures
 
990
       
965
991
 
966
992
    def commit(self, *args, **kw):
967
 
        from bzrlib.commit import Commit
968
 
        Commit().commit(self, *args, **kw)
969
 
    
 
993
        from bzrlib.commit import commit
 
994
        commit(self, *args, **kw)
 
995
        
 
996
 
 
997
    def lookup_revision(self, revision):
 
998
        """Return the revision identifier for a given revision information."""
 
999
        revno, info = self._get_revision_info(revision)
 
1000
        return info
 
1001
 
 
1002
 
970
1003
    def revision_id_to_revno(self, revision_id):
971
1004
        """Given a revision id, return its revno"""
972
 
        if revision_id is None:
973
 
            return 0
974
1005
        history = self.revision_history()
975
1006
        try:
976
1007
            return history.index(revision_id) + 1
977
1008
        except ValueError:
978
1009
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
979
1010
 
 
1011
 
 
1012
    def get_revision_info(self, revision):
 
1013
        """Return (revno, revision id) for revision identifier.
 
1014
 
 
1015
        revision can be an integer, in which case it is assumed to be revno (though
 
1016
            this will translate negative values into positive ones)
 
1017
        revision can also be a string, in which case it is parsed for something like
 
1018
            'date:' or 'revid:' etc.
 
1019
        """
 
1020
        revno, rev_id = self._get_revision_info(revision)
 
1021
        if revno is None:
 
1022
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
1023
        return revno, rev_id
 
1024
 
980
1025
    def get_rev_id(self, revno, history=None):
981
1026
        """Find the revision id of the specified revno."""
982
1027
        if revno == 0:
987
1032
            raise bzrlib.errors.NoSuchRevision(self, revno)
988
1033
        return history[revno - 1]
989
1034
 
 
1035
    def _get_revision_info(self, revision):
 
1036
        """Return (revno, revision id) for revision specifier.
 
1037
 
 
1038
        revision can be an integer, in which case it is assumed to be revno
 
1039
        (though this will translate negative values into positive ones)
 
1040
        revision can also be a string, in which case it is parsed for something
 
1041
        like 'date:' or 'revid:' etc.
 
1042
 
 
1043
        A revid is always returned.  If it is None, the specifier referred to
 
1044
        the null revision.  If the revid does not occur in the revision
 
1045
        history, revno will be None.
 
1046
        """
 
1047
        
 
1048
        if revision is None:
 
1049
            return 0, None
 
1050
        revno = None
 
1051
        try:# Convert to int if possible
 
1052
            revision = int(revision)
 
1053
        except ValueError:
 
1054
            pass
 
1055
        revs = self.revision_history()
 
1056
        if isinstance(revision, int):
 
1057
            if revision < 0:
 
1058
                revno = len(revs) + revision + 1
 
1059
            else:
 
1060
                revno = revision
 
1061
            rev_id = self.get_rev_id(revno, revs)
 
1062
        elif isinstance(revision, basestring):
 
1063
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
 
1064
                if revision.startswith(prefix):
 
1065
                    result = func(self, revs, revision)
 
1066
                    if len(result) > 1:
 
1067
                        revno, rev_id = result
 
1068
                    else:
 
1069
                        revno = result[0]
 
1070
                        rev_id = self.get_rev_id(revno, revs)
 
1071
                    break
 
1072
            else:
 
1073
                raise BzrError('No namespace registered for string: %r' %
 
1074
                               revision)
 
1075
        else:
 
1076
            raise TypeError('Unhandled revision type %s' % revision)
 
1077
 
 
1078
        if revno is None:
 
1079
            if rev_id is None:
 
1080
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
1081
        return revno, rev_id
 
1082
 
 
1083
    def _namespace_revno(self, revs, revision):
 
1084
        """Lookup a revision by revision number"""
 
1085
        assert revision.startswith('revno:')
 
1086
        try:
 
1087
            return (int(revision[6:]),)
 
1088
        except ValueError:
 
1089
            return None
 
1090
    REVISION_NAMESPACES['revno:'] = _namespace_revno
 
1091
 
 
1092
    def _namespace_revid(self, revs, revision):
 
1093
        assert revision.startswith('revid:')
 
1094
        rev_id = revision[len('revid:'):]
 
1095
        try:
 
1096
            return revs.index(rev_id) + 1, rev_id
 
1097
        except ValueError:
 
1098
            return None, rev_id
 
1099
    REVISION_NAMESPACES['revid:'] = _namespace_revid
 
1100
 
 
1101
    def _namespace_last(self, revs, revision):
 
1102
        assert revision.startswith('last:')
 
1103
        try:
 
1104
            offset = int(revision[5:])
 
1105
        except ValueError:
 
1106
            return (None,)
 
1107
        else:
 
1108
            if offset <= 0:
 
1109
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
1110
            return (len(revs) - offset + 1,)
 
1111
    REVISION_NAMESPACES['last:'] = _namespace_last
 
1112
 
 
1113
    def _namespace_tag(self, revs, revision):
 
1114
        assert revision.startswith('tag:')
 
1115
        raise BzrError('tag: namespace registered, but not implemented.')
 
1116
    REVISION_NAMESPACES['tag:'] = _namespace_tag
 
1117
 
 
1118
    def _namespace_date(self, revs, revision):
 
1119
        assert revision.startswith('date:')
 
1120
        import datetime
 
1121
        # Spec for date revisions:
 
1122
        #   date:value
 
1123
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
1124
        #   it can also start with a '+/-/='. '+' says match the first
 
1125
        #   entry after the given date. '-' is match the first entry before the date
 
1126
        #   '=' is match the first entry after, but still on the given date.
 
1127
        #
 
1128
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
1129
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
1130
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
1131
        #       May 13th, 2005 at 0:00
 
1132
        #
 
1133
        #   So the proper way of saying 'give me all entries for today' is:
 
1134
        #       -r {date:+today}:{date:-tomorrow}
 
1135
        #   The default is '=' when not supplied
 
1136
        val = revision[5:]
 
1137
        match_style = '='
 
1138
        if val[:1] in ('+', '-', '='):
 
1139
            match_style = val[:1]
 
1140
            val = val[1:]
 
1141
 
 
1142
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
 
1143
        if val.lower() == 'yesterday':
 
1144
            dt = today - datetime.timedelta(days=1)
 
1145
        elif val.lower() == 'today':
 
1146
            dt = today
 
1147
        elif val.lower() == 'tomorrow':
 
1148
            dt = today + datetime.timedelta(days=1)
 
1149
        else:
 
1150
            import re
 
1151
            # This should be done outside the function to avoid recompiling it.
 
1152
            _date_re = re.compile(
 
1153
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
1154
                    r'(,|T)?\s*'
 
1155
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
1156
                )
 
1157
            m = _date_re.match(val)
 
1158
            if not m or (not m.group('date') and not m.group('time')):
 
1159
                raise BzrError('Invalid revision date %r' % revision)
 
1160
 
 
1161
            if m.group('date'):
 
1162
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
1163
            else:
 
1164
                year, month, day = today.year, today.month, today.day
 
1165
            if m.group('time'):
 
1166
                hour = int(m.group('hour'))
 
1167
                minute = int(m.group('minute'))
 
1168
                if m.group('second'):
 
1169
                    second = int(m.group('second'))
 
1170
                else:
 
1171
                    second = 0
 
1172
            else:
 
1173
                hour, minute, second = 0,0,0
 
1174
 
 
1175
            dt = datetime.datetime(year=year, month=month, day=day,
 
1176
                    hour=hour, minute=minute, second=second)
 
1177
        first = dt
 
1178
        last = None
 
1179
        reversed = False
 
1180
        if match_style == '-':
 
1181
            reversed = True
 
1182
        elif match_style == '=':
 
1183
            last = dt + datetime.timedelta(days=1)
 
1184
 
 
1185
        if reversed:
 
1186
            for i in range(len(revs)-1, -1, -1):
 
1187
                r = self.get_revision(revs[i])
 
1188
                # TODO: Handle timezone.
 
1189
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1190
                if first >= dt and (last is None or dt >= last):
 
1191
                    return (i+1,)
 
1192
        else:
 
1193
            for i in range(len(revs)):
 
1194
                r = self.get_revision(revs[i])
 
1195
                # TODO: Handle timezone.
 
1196
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1197
                if first <= dt and (last is None or dt <= last):
 
1198
                    return (i+1,)
 
1199
    REVISION_NAMESPACES['date:'] = _namespace_date
 
1200
 
 
1201
 
 
1202
    def _namespace_ancestor(self, revs, revision):
 
1203
        from revision import common_ancestor, MultipleRevisionSources
 
1204
        other_branch = find_branch(_trim_namespace('ancestor', revision))
 
1205
        revision_a = self.last_patch()
 
1206
        revision_b = other_branch.last_patch()
 
1207
        for r, b in ((revision_a, self), (revision_b, other_branch)):
 
1208
            if r is None:
 
1209
                raise bzrlib.errors.NoCommits(b)
 
1210
        revision_source = MultipleRevisionSources(self, other_branch)
 
1211
        result = common_ancestor(revision_a, revision_b, revision_source)
 
1212
        try:
 
1213
            revno = self.revision_id_to_revno(result)
 
1214
        except bzrlib.errors.NoSuchRevision:
 
1215
            revno = None
 
1216
        return revno,result
 
1217
        
 
1218
 
 
1219
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
 
1220
 
990
1221
    def revision_tree(self, revision_id):
991
1222
        """Return Tree for a revision on this branch.
992
1223
 
998
1229
            return EmptyTree()
999
1230
        else:
1000
1231
            inv = self.get_revision_inventory(revision_id)
1001
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1232
            return RevisionTree(self.text_store, inv)
1002
1233
 
1003
1234
 
1004
1235
    def working_tree(self):
1005
1236
        """Return a `Tree` for the working copy."""
1006
1237
        from bzrlib.workingtree import WorkingTree
1007
1238
        # TODO: In the future, WorkingTree should utilize Transport
1008
 
        # RobertCollins 20051003 - I don't think it should - working trees are
1009
 
        # much more complex to keep consistent than our careful .bzr subset.
1010
 
        # instead, we should say that working trees are local only, and optimise
1011
 
        # for that.
1012
1239
        return WorkingTree(self._transport.base, self.read_working_inventory())
1013
1240
 
1014
1241
 
1017
1244
 
1018
1245
        If there are no revisions yet, return an `EmptyTree`.
1019
1246
        """
1020
 
        return self.revision_tree(self.last_revision())
 
1247
        r = self.last_patch()
 
1248
        if r == None:
 
1249
            return EmptyTree()
 
1250
        else:
 
1251
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
 
1252
 
1021
1253
 
1022
1254
 
1023
1255
    def rename_one(self, from_rel, to_rel):
1058
1290
            from_abs = self.abspath(from_rel)
1059
1291
            to_abs = self.abspath(to_rel)
1060
1292
            try:
1061
 
                rename(from_abs, to_abs)
 
1293
                os.rename(from_abs, to_abs)
1062
1294
            except OSError, e:
1063
1295
                raise BzrError("failed to rename %r to %r: %s"
1064
1296
                        % (from_abs, to_abs, e[1]),
1127
1359
                result.append((f, dest_path))
1128
1360
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1129
1361
                try:
1130
 
                    rename(self.abspath(f), self.abspath(dest_path))
 
1362
                    os.rename(self.abspath(f), self.abspath(dest_path))
1131
1363
                except OSError, e:
1132
1364
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1133
1365
                            ["rename rolled back"])
1199
1431
 
1200
1432
 
1201
1433
    def add_pending_merge(self, *revision_ids):
1202
 
        # TODO: Perhaps should check at this point that the
1203
 
        # history of the revision is actually present?
 
1434
        from bzrlib.revision import validate_revision_id
 
1435
 
 
1436
        for rev_id in revision_ids:
 
1437
            validate_revision_id(rev_id)
 
1438
 
1204
1439
        p = self.pending_merges()
1205
1440
        updated = False
1206
1441
        for rev_id in revision_ids:
1268
1503
            raise InvalidRevisionNumber(revno)
1269
1504
        
1270
1505
        
1271
 
        
1272
 
 
1273
 
 
1274
 
class ScratchBranch(_Branch):
 
1506
 
 
1507
 
 
1508
class ScratchBranch(Branch):
1275
1509
    """Special test class: a branch that cleans up after itself.
1276
1510
 
1277
1511
    >>> b = ScratchBranch()
1294
1528
        if base is None:
1295
1529
            base = mkdtemp()
1296
1530
            init = True
1297
 
        if isinstance(base, basestring):
1298
 
            base = get_transport(base)
1299
 
        _Branch.__init__(self, base, init=init)
 
1531
        Branch.__init__(self, base, init=init)
1300
1532
        for d in dirs:
1301
1533
            self._transport.mkdir(d)
1302
1534
            
1308
1540
        """
1309
1541
        >>> orig = ScratchBranch(files=["file1", "file2"])
1310
1542
        >>> clone = orig.clone()
1311
 
        >>> if os.name != 'nt':
1312
 
        ...   os.path.samefile(orig.base, clone.base)
1313
 
        ... else:
1314
 
        ...   orig.base == clone.base
1315
 
        ...
 
1543
        >>> os.path.samefile(orig.base, clone.base)
1316
1544
        False
1317
1545
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1318
1546
        True
1324
1552
        copytree(self.base, base, symlinks=True)
1325
1553
        return ScratchBranch(base=base)
1326
1554
 
 
1555
 
 
1556
        
1327
1557
    def __del__(self):
1328
1558
        self.destroy()
1329
1559
 
1399
1629
    return gen_file_id('TREE_ROOT')
1400
1630
 
1401
1631
 
 
1632
def copy_branch(branch_from, to_location, revision=None):
 
1633
    """Copy branch_from into the existing directory to_location.
 
1634
 
 
1635
    revision
 
1636
        If not None, only revisions up to this point will be copied.
 
1637
        The head of the new branch will be that revision.
 
1638
 
 
1639
    to_location
 
1640
        The name of a local directory that exists but is empty.
 
1641
    """
 
1642
    from bzrlib.merge import merge
 
1643
 
 
1644
    assert isinstance(branch_from, Branch)
 
1645
    assert isinstance(to_location, basestring)
 
1646
    
 
1647
    br_to = Branch(to_location, init=True)
 
1648
    br_to.set_root_id(branch_from.get_root_id())
 
1649
    if revision is None:
 
1650
        revno = branch_from.revno()
 
1651
    else:
 
1652
        revno, rev_id = branch_from.get_revision_info(revision)
 
1653
    br_to.update_revisions(branch_from, stop_revision=revno)
 
1654
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
1655
          check_clean=False, ignore_zero=True)
 
1656
    br_to.set_parent(branch_from.base)
 
1657
    return br_to
 
1658
 
 
1659
def _trim_namespace(namespace, spec):
 
1660
    full_namespace = namespace + ':'
 
1661
    assert spec.startswith(full_namespace)
 
1662
    return spec[len(full_namespace):]