~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-07-04 12:26:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050704122602-69901910521e62c3
- check command checks that all inventory-ids are the same as in the revision.

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 sys
19
 
import os
 
18
import sys, os
20
19
 
21
20
import bzrlib
22
21
from bzrlib.trace import mutter, note
23
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
 
     splitpath, \
 
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
25
23
     sha_file, appendpath, file_kind
26
 
 
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
 
     DivergedBranches, NotBranchError
29
 
from bzrlib.textui import show_status
30
 
from bzrlib.revision import Revision
31
 
from bzrlib.delta import compare_trees
32
 
from bzrlib.tree import EmptyTree, RevisionTree
33
 
import bzrlib.xml
34
 
import bzrlib.ui
35
 
 
36
 
 
 
24
from bzrlib.errors import BzrError
37
25
 
38
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
27
## TODO: Maybe include checks for common corruption of newlines, etc?
40
28
 
41
29
 
42
 
# TODO: Some operations like log might retrieve the same revisions
43
 
# repeatedly to calculate deltas.  We could perhaps have a weakref
44
 
# cache in memory to make this faster.
45
 
 
46
 
# TODO: please move the revision-string syntax stuff out of the branch
47
 
# object; it's clutter
48
 
 
49
30
 
50
31
def find_branch(f, **args):
51
32
    if f and (f.startswith('http://') or f.startswith('https://')):
52
 
        from bzrlib.remotebranch import RemoteBranch
53
 
        return RemoteBranch(f, **args)
 
33
        import remotebranch 
 
34
        return remotebranch.RemoteBranch(f, **args)
54
35
    else:
55
 
        return LocalBranch(f, **args)
 
36
        return Branch(f, **args)
56
37
 
57
38
 
58
39
def find_cached_branch(f, cache_root, **args):
59
 
    from bzrlib.remotebranch import RemoteBranch
 
40
    from remotebranch import RemoteBranch
60
41
    br = find_branch(f, **args)
61
42
    def cacheify(br, store_name):
62
 
        from bzrlib.meta_store import CachedStore
 
43
        from meta_store import CachedStore
63
44
        cache_path = os.path.join(cache_root, store_name)
64
45
        os.mkdir(cache_path)
65
46
        new_store = CachedStore(getattr(br, store_name), cache_path)
94
75
        if tail:
95
76
            s.insert(0, tail)
96
77
    else:
 
78
        from errors import NotBranchError
97
79
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
98
80
 
99
81
    return os.sep.join(s)
107
89
    It is not necessary that f exists.
108
90
 
109
91
    Basically we keep looking up until we find the control directory or
110
 
    run into the root.  If there isn't one, raises NotBranchError.
111
 
    """
 
92
    run into the root."""
112
93
    if f == None:
113
94
        f = os.getcwd()
114
95
    elif hasattr(os.path, 'realpath'):
127
108
        head, tail = os.path.split(f)
128
109
        if head == f:
129
110
            # reached the root, whatever that may be
130
 
            raise NotBranchError('%s is not in a branch' % orig_f)
 
111
            raise BzrError('%r is not in a branch' % orig_f)
131
112
        f = head
132
 
 
133
 
 
 
113
    
 
114
class DivergedBranches(Exception):
 
115
    def __init__(self, branch1, branch2):
 
116
        self.branch1 = branch1
 
117
        self.branch2 = branch2
 
118
        Exception.__init__(self, "These branches have diverged.")
 
119
 
 
120
 
 
121
class NoSuchRevision(BzrError):
 
122
    def __init__(self, branch, revision):
 
123
        self.branch = branch
 
124
        self.revision = revision
 
125
        msg = "Branch %s has no revision %d" % (branch, revision)
 
126
        BzrError.__init__(self, msg)
134
127
 
135
128
 
136
129
######################################################################
140
133
    """Branch holding a history of revisions.
141
134
 
142
135
    base
143
 
        Base directory/url of the branch.
144
 
    """
145
 
    base = None
146
 
 
147
 
    def __new__(cls, *a, **kw):
148
 
        """this is temporary, till we get rid of all code that does
149
 
        b = Branch()
150
 
        """
151
 
        # XXX: AAARGH!  MY EYES!  UUUUGLY!!!
152
 
        if cls == Branch:
153
 
            cls = LocalBranch
154
 
        b = object.__new__(cls)
155
 
        return b
156
 
 
157
 
 
158
 
class LocalBranch(Branch):
159
 
    """A branch stored in the actual filesystem.
160
 
 
161
 
    Note that it's "local" in the context of the filesystem; it doesn't
162
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
163
 
    it's writable, and can be accessed via the normal filesystem API.
 
136
        Base directory of the branch.
164
137
 
165
138
    _lock_mode
166
139
        None, or 'r' or 'w'
172
145
    _lock
173
146
        Lock object from bzrlib.lock.
174
147
    """
175
 
    # We actually expect this class to be somewhat short-lived; part of its
176
 
    # purpose is to try to isolate what bits of the branch logic are tied to
177
 
    # filesystem access, so that in a later step, we can extricate them to
178
 
    # a separarte ("storage") class.
 
148
    base = None
179
149
    _lock_mode = None
180
150
    _lock_count = None
181
151
    _lock = None
182
 
 
 
152
    
183
153
    def __init__(self, base, init=False, find_root=True):
184
154
        """Create new branch object at a particular location.
185
155
 
204
174
        else:
205
175
            self.base = os.path.realpath(base)
206
176
            if not isdir(self.controlfilename('.')):
 
177
                from errors import NotBranchError
207
178
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
208
179
                                     ['use "bzr init" to initialize a new working tree',
209
180
                                      'current bzr can only operate from top-of-tree'])
223
194
 
224
195
    def __del__(self):
225
196
        if self._lock_mode or self._lock:
226
 
            from bzrlib.warnings import warn
 
197
            from warnings import warn
227
198
            warn("branch %r was not explicitly unlocked" % self)
228
199
            self._lock.unlock()
229
200
 
230
201
 
 
202
 
231
203
    def lock_write(self):
232
204
        if self._lock_mode:
233
205
            if self._lock_mode != 'w':
234
 
                from bzrlib.errors import LockError
 
206
                from errors import LockError
235
207
                raise LockError("can't upgrade to a write lock from %r" %
236
208
                                self._lock_mode)
237
209
            self._lock_count += 1
243
215
            self._lock_count = 1
244
216
 
245
217
 
 
218
 
246
219
    def lock_read(self):
247
220
        if self._lock_mode:
248
221
            assert self._lock_mode in ('r', 'w'), \
255
228
            self._lock_mode = 'r'
256
229
            self._lock_count = 1
257
230
                        
 
231
 
 
232
            
258
233
    def unlock(self):
259
234
        if not self._lock_mode:
260
 
            from bzrlib.errors import LockError
 
235
            from errors import LockError
261
236
            raise LockError('branch %r is not locked' % (self))
262
237
 
263
238
        if self._lock_count > 1:
267
242
            self._lock = None
268
243
            self._lock_mode = self._lock_count = None
269
244
 
 
245
 
270
246
    def abspath(self, name):
271
247
        """Return absolute filename for something in the branch"""
272
248
        return os.path.join(self.base, name)
273
249
 
 
250
 
274
251
    def relpath(self, path):
275
252
        """Return path relative to this branch of something inside it.
276
253
 
277
254
        Raises an error if path is not in this branch."""
278
255
        return _relpath(self.base, path)
279
256
 
 
257
 
280
258
    def controlfilename(self, file_or_path):
281
259
        """Return location relative to branch."""
282
260
        if isinstance(file_or_path, basestring):
309
287
        else:
310
288
            raise BzrError("invalid controlfile mode %r" % mode)
311
289
 
 
290
 
 
291
 
312
292
    def _make_control(self):
313
293
        from bzrlib.inventory import Inventory
 
294
        from bzrlib.xml import pack_xml
314
295
        
315
296
        os.mkdir(self.controlfilename([]))
316
297
        self.controlfile('README', 'w').write(
326
307
            self.controlfile(f, 'w').write('')
327
308
        mutter('created control directory in ' + self.base)
328
309
 
329
 
        # if we want per-tree root ids then this is the place to set
330
 
        # them; they're not needed for now and so ommitted for
331
 
        # simplicity.
332
 
        f = self.controlfile('inventory','w')
333
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
 
310
        pack_xml(Inventory(), self.controlfile('inventory','w'))
334
311
 
335
312
 
336
313
    def _check_format(self):
345
322
        # on Windows from Linux and so on.  I think it might be better
346
323
        # to always make all internal files in unix format.
347
324
        fmt = self.controlfile('branch-format', 'r').read()
348
 
        fmt = fmt.replace('\r\n', '\n')
 
325
        fmt.replace('\r\n', '')
349
326
        if fmt != BZR_BRANCH_FORMAT:
350
327
            raise BzrError('sorry, branch format %r not supported' % fmt,
351
328
                           ['use a different bzr version',
352
329
                            'or remove the .bzr directory and "bzr init" again'])
353
330
 
354
 
    def get_root_id(self):
355
 
        """Return the id of this branches root"""
356
 
        inv = self.read_working_inventory()
357
 
        return inv.root.file_id
358
331
 
359
 
    def set_root_id(self, file_id):
360
 
        inv = self.read_working_inventory()
361
 
        orig_root_id = inv.root.file_id
362
 
        del inv._byid[inv.root.file_id]
363
 
        inv.root.file_id = file_id
364
 
        inv._byid[inv.root.file_id] = inv.root
365
 
        for fid in inv:
366
 
            entry = inv[fid]
367
 
            if entry.parent_id in (None, orig_root_id):
368
 
                entry.parent_id = inv.root.file_id
369
 
        self._write_inventory(inv)
370
332
 
371
333
    def read_working_inventory(self):
372
334
        """Read the working inventory."""
373
335
        from bzrlib.inventory import Inventory
 
336
        from bzrlib.xml import unpack_xml
 
337
        from time import time
 
338
        before = time()
374
339
        self.lock_read()
375
340
        try:
376
341
            # ElementTree does its own conversion from UTF-8, so open in
377
342
            # binary.
378
 
            f = self.controlfile('inventory', 'rb')
379
 
            return bzrlib.xml.serializer_v4.read_inventory(f)
 
343
            inv = unpack_xml(Inventory,
 
344
                                  self.controlfile('inventory', 'rb'))
 
345
            mutter("loaded inventory of %d items in %f"
 
346
                   % (len(inv), time() - before))
 
347
            return inv
380
348
        finally:
381
349
            self.unlock()
382
350
            
388
356
        will be committed to the next revision.
389
357
        """
390
358
        from bzrlib.atomicfile import AtomicFile
 
359
        from bzrlib.xml import pack_xml
391
360
        
392
361
        self.lock_write()
393
362
        try:
394
363
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
395
364
            try:
396
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
 
365
                pack_xml(inv, f)
397
366
                f.commit()
398
367
            finally:
399
368
                f.close()
407
376
                         """Inventory for the working copy.""")
408
377
 
409
378
 
410
 
    def add(self, files, ids=None):
 
379
    def add(self, files, verbose=False, ids=None):
411
380
        """Make files versioned.
412
381
 
413
 
        Note that the command line normally calls smart_add instead,
414
 
        which can automatically recurse.
 
382
        Note that the command line normally calls smart_add instead.
415
383
 
416
384
        This puts the files in the Added state, so that they will be
417
385
        recorded by the next commit.
427
395
        TODO: Perhaps have an option to add the ids even if the files do
428
396
              not (yet) exist.
429
397
 
430
 
        TODO: Perhaps yield the ids and paths as they're added.
 
398
        TODO: Perhaps return the ids of the files?  But then again it
 
399
              is easy to retrieve them if they're needed.
 
400
 
 
401
        TODO: Adding a directory should optionally recurse down and
 
402
              add all non-ignored children.  Perhaps do that in a
 
403
              higher-level method.
431
404
        """
 
405
        from bzrlib.textui import show_status
432
406
        # TODO: Re-adding a file that is removed in the working copy
433
407
        # should probably put it back with the previous ID.
434
408
        if isinstance(files, basestring):
469
443
                    file_id = gen_file_id(f)
470
444
                inv.add_path(f, kind=kind, file_id=file_id)
471
445
 
 
446
                if verbose:
 
447
                    print 'added', quotefn(f)
 
448
 
472
449
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
473
450
 
474
451
            self._write_inventory(inv)
480
457
        """Print `file` to stdout."""
481
458
        self.lock_read()
482
459
        try:
483
 
            tree = self.revision_tree(self.get_rev_id(revno))
 
460
            tree = self.revision_tree(self.lookup_revision(revno))
484
461
            # use inventory as it was in that revision
485
462
            file_id = tree.inventory.path2id(file)
486
463
            if not file_id:
487
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
 
464
                raise BzrError("%r is not present in revision %d" % (file, revno))
488
465
            tree.print_file(file_id)
489
466
        finally:
490
467
            self.unlock()
504
481
        is the opposite of add.  Removing it is consistent with most
505
482
        other tools.  Maybe an option.
506
483
        """
 
484
        from bzrlib.textui import show_status
507
485
        ## TODO: Normalize names
508
486
        ## TODO: Remove nested loops; better scalability
509
487
        if isinstance(files, basestring):
538
516
    # FIXME: this doesn't need to be a branch method
539
517
    def set_inventory(self, new_inventory_list):
540
518
        from bzrlib.inventory import Inventory, InventoryEntry
541
 
        inv = Inventory(self.get_root_id())
 
519
        inv = Inventory()
542
520
        for path, file_id, parent, kind in new_inventory_list:
543
521
            name = os.path.basename(path)
544
522
            if name == "":
566
544
        return self.working_tree().unknowns()
567
545
 
568
546
 
569
 
    def append_revision(self, *revision_ids):
 
547
    def append_revision(self, revision_id):
570
548
        from bzrlib.atomicfile import AtomicFile
571
549
 
572
 
        for revision_id in revision_ids:
573
 
            mutter("add {%s} to revision-history" % revision_id)
574
 
 
575
 
        rev_history = self.revision_history()
576
 
        rev_history.extend(revision_ids)
 
550
        mutter("add {%s} to revision-history" % revision_id)
 
551
        rev_history = self.revision_history() + [revision_id]
577
552
 
578
553
        f = AtomicFile(self.controlfilename('revision-history'))
579
554
        try:
584
559
            f.close()
585
560
 
586
561
 
587
 
    def get_revision_xml_file(self, revision_id):
588
 
        """Return XML file object for revision object."""
589
 
        if not revision_id or not isinstance(revision_id, basestring):
590
 
            raise InvalidRevisionId(revision_id)
591
 
 
592
 
        self.lock_read()
593
 
        try:
594
 
            try:
595
 
                return self.revision_store[revision_id]
596
 
            except IndexError:
597
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
598
 
        finally:
599
 
            self.unlock()
600
 
 
601
 
 
602
 
    #deprecated
603
 
    get_revision_xml = get_revision_xml_file
604
 
 
605
 
 
606
562
    def get_revision(self, revision_id):
607
563
        """Return the Revision object for a named revision"""
608
 
        xml_file = self.get_revision_xml_file(revision_id)
 
564
        from bzrlib.revision import Revision
 
565
        from bzrlib.xml import unpack_xml
609
566
 
 
567
        self.lock_read()
610
568
        try:
611
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
612
 
        except SyntaxError, e:
613
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
614
 
                                         [revision_id,
615
 
                                          str(e)])
 
569
            if not revision_id or not isinstance(revision_id, basestring):
 
570
                raise ValueError('invalid revision-id: %r' % revision_id)
 
571
            r = unpack_xml(Revision, self.revision_store[revision_id])
 
572
        finally:
 
573
            self.unlock()
616
574
            
617
575
        assert r.revision_id == revision_id
618
576
        return r
619
 
 
620
 
 
621
 
    def get_revision_delta(self, revno):
622
 
        """Return the delta for one revision.
623
 
 
624
 
        The delta is relative to its mainline predecessor, or the
625
 
        empty tree for revision 1.
626
 
        """
627
 
        assert isinstance(revno, int)
628
 
        rh = self.revision_history()
629
 
        if not (1 <= revno <= len(rh)):
630
 
            raise InvalidRevisionNumber(revno)
631
 
 
632
 
        # revno is 1-based; list is 0-based
633
 
 
634
 
        new_tree = self.revision_tree(rh[revno-1])
635
 
        if revno == 1:
636
 
            old_tree = EmptyTree()
637
 
        else:
638
 
            old_tree = self.revision_tree(rh[revno-2])
639
 
 
640
 
        return compare_trees(old_tree, new_tree)
641
 
 
642
577
        
643
578
 
644
579
    def get_revision_sha1(self, revision_id):
649
584
        # the revision, (add signatures/remove signatures) and still
650
585
        # have all hash pointers stay consistent.
651
586
        # But for now, just hash the contents.
652
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
 
587
        return sha_file(self.revision_store[revision_id])
653
588
 
654
589
 
655
590
    def get_inventory(self, inventory_id):
659
594
               parameter which can be either an integer revno or a
660
595
               string hash."""
661
596
        from bzrlib.inventory import Inventory
662
 
 
663
 
        f = self.get_inventory_xml_file(inventory_id)
664
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
665
 
 
666
 
 
667
 
    def get_inventory_xml(self, inventory_id):
668
 
        """Get inventory XML as a file object."""
669
 
        return self.inventory_store[inventory_id]
670
 
 
671
 
    get_inventory_xml_file = get_inventory_xml
 
597
        from bzrlib.xml import unpack_xml
 
598
 
 
599
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
672
600
            
673
601
 
674
602
    def get_inventory_sha1(self, inventory_id):
675
603
        """Return the sha1 hash of the inventory entry
676
604
        """
677
 
        return sha_file(self.get_inventory_xml(inventory_id))
 
605
        return sha_file(self.inventory_store[inventory_id])
678
606
 
679
607
 
680
608
    def get_revision_inventory(self, revision_id):
681
609
        """Return inventory of a past revision."""
682
 
        # bzr 0.0.6 imposes the constraint that the inventory_id
683
 
        # must be the same as its revision, so this is trivial.
684
610
        if revision_id == None:
685
611
            from bzrlib.inventory import Inventory
686
 
            return Inventory(self.get_root_id())
 
612
            return Inventory()
687
613
        else:
688
 
            return self.get_inventory(revision_id)
 
614
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
689
615
 
690
616
 
691
617
    def revision_history(self):
704
630
 
705
631
    def common_ancestor(self, other, self_revno=None, other_revno=None):
706
632
        """
707
 
        >>> from bzrlib.commit import commit
 
633
        >>> import commit
708
634
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
709
635
        >>> sb.common_ancestor(sb) == (None, None)
710
636
        True
711
 
        >>> commit(sb, "Committing first revision", verbose=False)
 
637
        >>> commit.commit(sb, "Committing first revision", verbose=False)
712
638
        >>> sb.common_ancestor(sb)[0]
713
639
        1
714
640
        >>> clone = sb.clone()
715
 
        >>> commit(sb, "Committing second revision", verbose=False)
 
641
        >>> commit.commit(sb, "Committing second revision", verbose=False)
716
642
        >>> sb.common_ancestor(sb)[0]
717
643
        2
718
644
        >>> sb.common_ancestor(clone)[0]
719
645
        1
720
 
        >>> commit(clone, "Committing divergent second revision", 
 
646
        >>> commit.commit(clone, "Committing divergent second revision", 
721
647
        ...               verbose=False)
722
648
        >>> sb.common_ancestor(clone)[0]
723
649
        1
746
672
                return r+1, my_history[r]
747
673
        return None, None
748
674
 
 
675
    def enum_history(self, direction):
 
676
        """Return (revno, revision_id) for history of branch.
 
677
 
 
678
        direction
 
679
            'forward' is from earliest to latest
 
680
            'reverse' is from latest to earliest
 
681
        """
 
682
        rh = self.revision_history()
 
683
        if direction == 'forward':
 
684
            i = 1
 
685
            for rid in rh:
 
686
                yield i, rid
 
687
                i += 1
 
688
        elif direction == 'reverse':
 
689
            i = len(rh)
 
690
            while i > 0:
 
691
                yield i, rh[i-1]
 
692
                i -= 1
 
693
        else:
 
694
            raise ValueError('invalid history direction', direction)
 
695
 
749
696
 
750
697
    def revno(self):
751
698
        """Return current revision number for this branch.
766
713
            return None
767
714
 
768
715
 
769
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
716
    def missing_revisions(self, other, stop_revision=None):
770
717
        """
771
718
        If self and other have not diverged, return a list of the revisions
772
719
        present in other, but missing from self.
805
752
        if stop_revision is None:
806
753
            stop_revision = other_len
807
754
        elif stop_revision > other_len:
808
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
755
            raise NoSuchRevision(self, stop_revision)
809
756
        
810
757
        return other_history[self_len:stop_revision]
811
758
 
812
759
 
813
760
    def update_revisions(self, other, stop_revision=None):
814
761
        """Pull in all new revisions from other branch.
 
762
        
 
763
        >>> from bzrlib.commit import commit
 
764
        >>> bzrlib.trace.silent = True
 
765
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
 
766
        >>> br1.add('foo')
 
767
        >>> br1.add('bar')
 
768
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
 
769
        >>> br2 = ScratchBranch()
 
770
        >>> br2.update_revisions(br1)
 
771
        Added 2 texts.
 
772
        Added 1 inventories.
 
773
        Added 1 revisions.
 
774
        >>> br2.revision_history()
 
775
        [u'REVISION-ID-1']
 
776
        >>> br2.update_revisions(br1)
 
777
        Added 0 texts.
 
778
        Added 0 inventories.
 
779
        Added 0 revisions.
 
780
        >>> br1.text_store.total_size() == br2.text_store.total_size()
 
781
        True
815
782
        """
816
 
        from bzrlib.fetch import greedy_fetch
817
 
 
818
 
        pb = bzrlib.ui.ui_factory.progress_bar()
 
783
        from bzrlib.progress import ProgressBar
 
784
        try:
 
785
            set
 
786
        except NameError:
 
787
            from sets import Set as set
 
788
 
 
789
        pb = ProgressBar()
 
790
 
819
791
        pb.update('comparing histories')
820
 
 
821
792
        revision_ids = self.missing_revisions(other, stop_revision)
822
793
 
823
 
        if len(revision_ids) > 0:
824
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
825
 
        else:
826
 
            count = 0
827
 
        self.append_revision(*revision_ids)
828
 
        ## note("Added %d revisions." % count)
829
 
        pb.clear()
830
 
 
831
 
    def install_revisions(self, other, revision_ids, pb):
832
794
        if hasattr(other.revision_store, "prefetch"):
833
795
            other.revision_store.prefetch(revision_ids)
834
796
        if hasattr(other.inventory_store, "prefetch"):
835
797
            inventory_ids = [other.get_revision(r).inventory_id
836
798
                             for r in revision_ids]
837
799
            other.inventory_store.prefetch(inventory_ids)
838
 
 
839
 
        if pb is None:
840
 
            pb = bzrlib.ui.ui_factory.progress_bar()
841
800
                
842
801
        revisions = []
843
802
        needed_texts = set()
844
803
        i = 0
845
 
 
846
 
        failures = set()
847
 
        for i, rev_id in enumerate(revision_ids):
848
 
            pb.update('fetching revision', i+1, len(revision_ids))
849
 
            try:
850
 
                rev = other.get_revision(rev_id)
851
 
            except bzrlib.errors.NoSuchRevision:
852
 
                failures.add(rev_id)
853
 
                continue
854
 
 
 
804
        for rev_id in revision_ids:
 
805
            i += 1
 
806
            pb.update('fetching revision', i, len(revision_ids))
 
807
            rev = other.get_revision(rev_id)
855
808
            revisions.append(rev)
856
809
            inv = other.get_inventory(str(rev.inventory_id))
857
810
            for key, entry in inv.iter_entries():
862
815
 
863
816
        pb.clear()
864
817
                    
865
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
866
 
                                                    needed_texts)
867
 
        #print "Added %d texts." % count 
 
818
        count = self.text_store.copy_multi(other.text_store, needed_texts)
 
819
        print "Added %d texts." % count 
868
820
        inventory_ids = [ f.inventory_id for f in revisions ]
869
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
870
 
                                                         inventory_ids)
871
 
        #print "Added %d inventories." % count 
 
821
        count = self.inventory_store.copy_multi(other.inventory_store, 
 
822
                                                inventory_ids)
 
823
        print "Added %d inventories." % count 
872
824
        revision_ids = [ f.revision_id for f in revisions]
873
 
 
874
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
875
 
                                                          revision_ids,
876
 
                                                          permit_failure=True)
877
 
        assert len(cp_fail) == 0 
878
 
        return count, failures
879
 
       
880
 
 
 
825
        count = self.revision_store.copy_multi(other.revision_store, 
 
826
                                               revision_ids)
 
827
        for revision_id in revision_ids:
 
828
            self.append_revision(revision_id)
 
829
        print "Added %d revisions." % count
 
830
                    
 
831
        
881
832
    def commit(self, *args, **kw):
882
833
        from bzrlib.commit import commit
883
834
        commit(self, *args, **kw)
884
835
        
885
836
 
886
 
    def lookup_revision(self, revision):
887
 
        """Return the revision identifier for a given revision specifier."""
888
 
        # XXX: I'm not sure this method belongs here; I'd rather have the
889
 
        # revision spec stuff be an UI thing, and branch blissfully unaware
890
 
        # of it.
891
 
        # Also, I'm not entirely happy with this method returning None
892
 
        # when the revision doesn't exist.
893
 
        # But I'm keeping the contract I found, because this seems to be
894
 
        # used in a lot of places - and when I do change these, I'd rather
895
 
        # figure out case-by-case which ones actually want to care about
896
 
        # revision specs (eg, they are UI-level) and which ones should trust
897
 
        # that they have a revno/revid.
898
 
        #   -- lalo@exoweb.net, 2005-09-07
899
 
        from bzrlib.errors import NoSuchRevision
900
 
        from bzrlib.revisionspec import RevisionSpec
901
 
        try:
902
 
            spec = RevisionSpec(self, revision)
903
 
        except NoSuchRevision:
904
 
            return None
905
 
        return spec.rev_id
906
 
 
907
 
 
908
 
    def revision_id_to_revno(self, revision_id):
909
 
        """Given a revision id, return its revno"""
910
 
        history = self.revision_history()
911
 
        try:
912
 
            return history.index(revision_id) + 1
913
 
        except ValueError:
914
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
915
 
 
916
 
 
917
 
    def get_rev_id(self, revno, history=None):
918
 
        """Find the revision id of the specified revno."""
 
837
    def lookup_revision(self, revno):
 
838
        """Return revision hash for revision number."""
919
839
        if revno == 0:
920
840
            return None
921
 
        if history is None:
922
 
            history = self.revision_history()
923
 
        elif revno <= 0 or revno > len(history):
924
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
925
 
        return history[revno - 1]
 
841
 
 
842
        try:
 
843
            # list is 0-based; revisions are 1-based
 
844
            return self.revision_history()[revno-1]
 
845
        except IndexError:
 
846
            raise BzrError("no such revision %s" % revno)
 
847
 
926
848
 
927
849
    def revision_tree(self, revision_id):
928
850
        """Return Tree for a revision on this branch.
929
851
 
930
852
        `revision_id` may be None for the null revision, in which case
931
853
        an `EmptyTree` is returned."""
 
854
        from bzrlib.tree import EmptyTree, RevisionTree
932
855
        # TODO: refactor this to use an existing revision object
933
856
        # so we don't need to read it in twice.
934
857
        if revision_id == None:
940
863
 
941
864
    def working_tree(self):
942
865
        """Return a `Tree` for the working copy."""
943
 
        from bzrlib.workingtree import WorkingTree
 
866
        from workingtree import WorkingTree
944
867
        return WorkingTree(self.base, self.read_working_inventory())
945
868
 
946
869
 
949
872
 
950
873
        If there are no revisions yet, return an `EmptyTree`.
951
874
        """
 
875
        from bzrlib.tree import EmptyTree, RevisionTree
952
876
        r = self.last_patch()
953
877
        if r == None:
954
878
            return EmptyTree()
992
916
 
993
917
            inv.rename(file_id, to_dir_id, to_tail)
994
918
 
 
919
            print "%s => %s" % (from_rel, to_rel)
 
920
 
995
921
            from_abs = self.abspath(from_rel)
996
922
            to_abs = self.abspath(to_rel)
997
923
            try:
1016
942
 
1017
943
        Note that to_name is only the last component of the new name;
1018
944
        this doesn't change the directory.
1019
 
 
1020
 
        This returns a list of (from_path, to_path) pairs for each
1021
 
        entry that is moved.
1022
945
        """
1023
 
        result = []
1024
946
        self.lock_write()
1025
947
        try:
1026
948
            ## TODO: Option to move IDs only
1061
983
            for f in from_paths:
1062
984
                name_tail = splitpath(f)[-1]
1063
985
                dest_path = appendpath(to_name, name_tail)
1064
 
                result.append((f, dest_path))
 
986
                print "%s => %s" % (f, dest_path)
1065
987
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1066
988
                try:
1067
989
                    os.rename(self.abspath(f), self.abspath(dest_path))
1073
995
        finally:
1074
996
            self.unlock()
1075
997
 
1076
 
        return result
1077
 
 
1078
998
 
1079
999
    def revert(self, filenames, old_tree=None, backups=True):
1080
1000
        """Restore selected files to the versions from a previous tree.
1162
1082
            self.unlock()
1163
1083
 
1164
1084
 
1165
 
    def get_parent(self):
1166
 
        """Return the parent location of the branch.
1167
 
 
1168
 
        This is the default location for push/pull/missing.  The usual
1169
 
        pattern is that the user can override it by specifying a
1170
 
        location.
1171
 
        """
1172
 
        import errno
1173
 
        _locs = ['parent', 'pull', 'x-pull']
1174
 
        for l in _locs:
1175
 
            try:
1176
 
                return self.controlfile(l, 'r').read().strip('\n')
1177
 
            except IOError, e:
1178
 
                if e.errno != errno.ENOENT:
1179
 
                    raise
1180
 
        return None
1181
 
 
1182
 
 
1183
 
    def set_parent(self, url):
1184
 
        # TODO: Maybe delete old location files?
1185
 
        from bzrlib.atomicfile import AtomicFile
1186
 
        self.lock_write()
1187
 
        try:
1188
 
            f = AtomicFile(self.controlfilename('parent'))
1189
 
            try:
1190
 
                f.write(url + '\n')
1191
 
                f.commit()
1192
 
            finally:
1193
 
                f.close()
1194
 
        finally:
1195
 
            self.unlock()
1196
 
 
1197
 
    def check_revno(self, revno):
1198
 
        """\
1199
 
        Check whether a revno corresponds to any revision.
1200
 
        Zero (the NULL revision) is considered valid.
1201
 
        """
1202
 
        if revno != 0:
1203
 
            self.check_real_revno(revno)
1204
 
            
1205
 
    def check_real_revno(self, revno):
1206
 
        """\
1207
 
        Check whether a revno corresponds to a real revision.
1208
 
        Zero (the NULL revision) is considered invalid
1209
 
        """
1210
 
        if revno < 1 or revno > self.revno():
1211
 
            raise InvalidRevisionNumber(revno)
1212
 
        
1213
 
        
1214
 
 
1215
 
 
1216
 
class ScratchBranch(LocalBranch):
 
1085
 
 
1086
class ScratchBranch(Branch):
1217
1087
    """Special test class: a branch that cleans up after itself.
1218
1088
 
1219
1089
    >>> b = ScratchBranch()
1236
1106
        if base is None:
1237
1107
            base = mkdtemp()
1238
1108
            init = True
1239
 
        LocalBranch.__init__(self, base, init=init)
 
1109
        Branch.__init__(self, base, init=init)
1240
1110
        for d in dirs:
1241
1111
            os.mkdir(self.abspath(d))
1242
1112
            
1259
1129
        os.rmdir(base)
1260
1130
        copytree(self.base, base, symlinks=True)
1261
1131
        return ScratchBranch(base=base)
1262
 
 
1263
 
 
1264
1132
        
1265
1133
    def __del__(self):
1266
1134
        self.destroy()
1330
1198
 
1331
1199
    s = hexlify(rand_bytes(8))
1332
1200
    return '-'.join((name, compact_date(time()), s))
1333
 
 
1334
 
 
1335
 
def gen_root_id():
1336
 
    """Return a new tree-root file id."""
1337
 
    return gen_file_id('TREE_ROOT')
1338
 
 
1339
 
 
1340
 
def copy_branch(branch_from, to_location, revision=None):
1341
 
    """Copy branch_from into the existing directory to_location.
1342
 
 
1343
 
    revision
1344
 
        If not None, only revisions up to this point will be copied.
1345
 
        The head of the new branch will be that revision.
1346
 
 
1347
 
    to_location
1348
 
        The name of a local directory that exists but is empty.
1349
 
    """
1350
 
    from bzrlib.merge import merge
1351
 
    from bzrlib.revisionspec import RevisionSpec
1352
 
 
1353
 
    assert isinstance(branch_from, Branch)
1354
 
    assert isinstance(to_location, basestring)
1355
 
    
1356
 
    br_to = Branch(to_location, init=True)
1357
 
    br_to.set_root_id(branch_from.get_root_id())
1358
 
    if revision is None:
1359
 
        revno = branch_from.revno()
1360
 
    else:
1361
 
        revno, rev_id = RevisionSpec(branch_from, revision)
1362
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1363
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1364
 
          check_clean=False, ignore_zero=True)
1365
 
    
1366
 
    from_location = branch_from.base
1367
 
    br_to.set_parent(branch_from.base)
1368