~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-07-18 14:28:13 UTC
  • Revision ID: mbp@sourcefrog.net-20050718142813-253ec02b9636bd98
- add stubbed-out test for clashing replace and delete

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
 
import bzrlib.errors
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://')):
108
89
    It is not necessary that f exists.
109
90
 
110
91
    Basically we keep looking up until we find the control directory or
111
 
    run into the root.  If there isn't one, raises NotBranchError.
112
 
    """
 
92
    run into the root."""
113
93
    if f == None:
114
94
        f = os.getcwd()
115
95
    elif hasattr(os.path, 'realpath'):
128
108
        head, tail = os.path.split(f)
129
109
        if head == f:
130
110
            # reached the root, whatever that may be
131
 
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
 
111
            raise BzrError('%r is not in a branch' % orig_f)
132
112
        f = head
133
 
 
134
 
 
135
 
 
136
 
# XXX: move into bzrlib.errors; subclass BzrError    
 
113
    
137
114
class DivergedBranches(Exception):
138
115
    def __init__(self, branch1, branch2):
139
116
        self.branch1 = branch1
141
118
        Exception.__init__(self, "These branches have diverged.")
142
119
 
143
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)
 
127
 
 
128
 
144
129
######################################################################
145
130
# branch objects
146
131
 
219
204
            self._lock.unlock()
220
205
 
221
206
 
 
207
 
222
208
    def lock_write(self):
223
209
        if self._lock_mode:
224
210
            if self._lock_mode != 'w':
234
220
            self._lock_count = 1
235
221
 
236
222
 
 
223
 
237
224
    def lock_read(self):
238
225
        if self._lock_mode:
239
226
            assert self._lock_mode in ('r', 'w'), \
246
233
            self._lock_mode = 'r'
247
234
            self._lock_count = 1
248
235
                        
 
236
 
 
237
            
249
238
    def unlock(self):
250
239
        if not self._lock_mode:
251
240
            from errors import LockError
258
247
            self._lock = None
259
248
            self._lock_mode = self._lock_count = None
260
249
 
 
250
 
261
251
    def abspath(self, name):
262
252
        """Return absolute filename for something in the branch"""
263
253
        return os.path.join(self.base, name)
264
254
 
 
255
 
265
256
    def relpath(self, path):
266
257
        """Return path relative to this branch of something inside it.
267
258
 
268
259
        Raises an error if path is not in this branch."""
269
260
        return _relpath(self.base, path)
270
261
 
 
262
 
271
263
    def controlfilename(self, file_or_path):
272
264
        """Return location relative to branch."""
273
265
        if isinstance(file_or_path, basestring):
300
292
        else:
301
293
            raise BzrError("invalid controlfile mode %r" % mode)
302
294
 
 
295
 
 
296
 
303
297
    def _make_control(self):
304
298
        from bzrlib.inventory import Inventory
 
299
        from bzrlib.xml import pack_xml
305
300
        
306
301
        os.mkdir(self.controlfilename([]))
307
302
        self.controlfile('README', 'w').write(
317
312
            self.controlfile(f, 'w').write('')
318
313
        mutter('created control directory in ' + self.base)
319
314
 
320
 
        # if we want per-tree root ids then this is the place to set
321
 
        # them; they're not needed for now and so ommitted for
322
 
        # simplicity.
323
 
        f = self.controlfile('inventory','w')
324
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
 
315
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
325
316
 
326
317
 
327
318
    def _check_format(self):
336
327
        # on Windows from Linux and so on.  I think it might be better
337
328
        # to always make all internal files in unix format.
338
329
        fmt = self.controlfile('branch-format', 'r').read()
339
 
        fmt = fmt.replace('\r\n', '\n')
 
330
        fmt.replace('\r\n', '')
340
331
        if fmt != BZR_BRANCH_FORMAT:
341
332
            raise BzrError('sorry, branch format %r not supported' % fmt,
342
333
                           ['use a different bzr version',
362
353
    def read_working_inventory(self):
363
354
        """Read the working inventory."""
364
355
        from bzrlib.inventory import Inventory
 
356
        from bzrlib.xml import unpack_xml
 
357
        from time import time
 
358
        before = time()
365
359
        self.lock_read()
366
360
        try:
367
361
            # ElementTree does its own conversion from UTF-8, so open in
368
362
            # binary.
369
 
            f = self.controlfile('inventory', 'rb')
370
 
            return bzrlib.xml.serializer_v4.read_inventory(f)
 
363
            inv = unpack_xml(Inventory,
 
364
                                  self.controlfile('inventory', 'rb'))
 
365
            mutter("loaded inventory of %d items in %f"
 
366
                   % (len(inv), time() - before))
 
367
            return inv
371
368
        finally:
372
369
            self.unlock()
373
370
            
379
376
        will be committed to the next revision.
380
377
        """
381
378
        from bzrlib.atomicfile import AtomicFile
 
379
        from bzrlib.xml import pack_xml
382
380
        
383
381
        self.lock_write()
384
382
        try:
385
383
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
386
384
            try:
387
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
 
385
                pack_xml(inv, f)
388
386
                f.commit()
389
387
            finally:
390
388
                f.close()
398
396
                         """Inventory for the working copy.""")
399
397
 
400
398
 
401
 
    def add(self, files, ids=None):
 
399
    def add(self, files, verbose=False, ids=None):
402
400
        """Make files versioned.
403
401
 
404
 
        Note that the command line normally calls smart_add instead,
405
 
        which can automatically recurse.
 
402
        Note that the command line normally calls smart_add instead.
406
403
 
407
404
        This puts the files in the Added state, so that they will be
408
405
        recorded by the next commit.
418
415
        TODO: Perhaps have an option to add the ids even if the files do
419
416
              not (yet) exist.
420
417
 
421
 
        TODO: Perhaps yield the ids and paths as they're added.
 
418
        TODO: Perhaps return the ids of the files?  But then again it
 
419
              is easy to retrieve them if they're needed.
 
420
 
 
421
        TODO: Adding a directory should optionally recurse down and
 
422
              add all non-ignored children.  Perhaps do that in a
 
423
              higher-level method.
422
424
        """
 
425
        from bzrlib.textui import show_status
423
426
        # TODO: Re-adding a file that is removed in the working copy
424
427
        # should probably put it back with the previous ID.
425
428
        if isinstance(files, basestring):
460
463
                    file_id = gen_file_id(f)
461
464
                inv.add_path(f, kind=kind, file_id=file_id)
462
465
 
 
466
                if verbose:
 
467
                    print 'added', quotefn(f)
 
468
 
463
469
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
464
470
 
465
471
            self._write_inventory(inv)
495
501
        is the opposite of add.  Removing it is consistent with most
496
502
        other tools.  Maybe an option.
497
503
        """
 
504
        from bzrlib.textui import show_status
498
505
        ## TODO: Normalize names
499
506
        ## TODO: Remove nested loops; better scalability
500
507
        if isinstance(files, basestring):
575
582
            f.close()
576
583
 
577
584
 
578
 
    def get_revision_xml_file(self, revision_id):
579
 
        """Return XML file object for revision object."""
580
 
        if not revision_id or not isinstance(revision_id, basestring):
581
 
            raise InvalidRevisionId(revision_id)
582
 
 
583
 
        self.lock_read()
584
 
        try:
585
 
            try:
586
 
                return self.revision_store[revision_id]
587
 
            except KeyError:
588
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
589
 
        finally:
590
 
            self.unlock()
591
 
 
592
 
 
593
 
    #deprecated
594
 
    get_revision_xml = get_revision_xml_file
595
 
 
596
 
 
597
585
    def get_revision(self, revision_id):
598
586
        """Return the Revision object for a named revision"""
599
 
        xml_file = self.get_revision_xml_file(revision_id)
 
587
        from bzrlib.revision import Revision
 
588
        from bzrlib.xml import unpack_xml
600
589
 
 
590
        self.lock_read()
601
591
        try:
602
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
603
 
        except SyntaxError, e:
604
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
605
 
                                         [revision_id,
606
 
                                          str(e)])
 
592
            if not revision_id or not isinstance(revision_id, basestring):
 
593
                raise ValueError('invalid revision-id: %r' % revision_id)
 
594
            r = unpack_xml(Revision, self.revision_store[revision_id])
 
595
        finally:
 
596
            self.unlock()
607
597
            
608
598
        assert r.revision_id == revision_id
609
599
        return r
610
 
 
611
 
 
612
 
    def get_revision_delta(self, revno):
613
 
        """Return the delta for one revision.
614
 
 
615
 
        The delta is relative to its mainline predecessor, or the
616
 
        empty tree for revision 1.
617
 
        """
618
 
        assert isinstance(revno, int)
619
 
        rh = self.revision_history()
620
 
        if not (1 <= revno <= len(rh)):
621
 
            raise InvalidRevisionNumber(revno)
622
 
 
623
 
        # revno is 1-based; list is 0-based
624
 
 
625
 
        new_tree = self.revision_tree(rh[revno-1])
626
 
        if revno == 1:
627
 
            old_tree = EmptyTree()
628
 
        else:
629
 
            old_tree = self.revision_tree(rh[revno-2])
630
 
 
631
 
        return compare_trees(old_tree, new_tree)
632
 
 
633
600
        
634
601
 
635
602
    def get_revision_sha1(self, revision_id):
640
607
        # the revision, (add signatures/remove signatures) and still
641
608
        # have all hash pointers stay consistent.
642
609
        # But for now, just hash the contents.
643
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
 
610
        return sha_file(self.revision_store[revision_id])
644
611
 
645
612
 
646
613
    def get_inventory(self, inventory_id):
650
617
               parameter which can be either an integer revno or a
651
618
               string hash."""
652
619
        from bzrlib.inventory import Inventory
653
 
 
654
 
        f = self.get_inventory_xml_file(inventory_id)
655
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
656
 
 
657
 
 
658
 
    def get_inventory_xml(self, inventory_id):
659
 
        """Get inventory XML as a file object."""
660
 
        return self.inventory_store[inventory_id]
661
 
 
662
 
    get_inventory_xml_file = get_inventory_xml
 
620
        from bzrlib.xml import unpack_xml
 
621
 
 
622
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
663
623
            
664
624
 
665
625
    def get_inventory_sha1(self, inventory_id):
666
626
        """Return the sha1 hash of the inventory entry
667
627
        """
668
 
        return sha_file(self.get_inventory_xml(inventory_id))
 
628
        return sha_file(self.inventory_store[inventory_id])
669
629
 
670
630
 
671
631
    def get_revision_inventory(self, revision_id):
737
697
                return r+1, my_history[r]
738
698
        return None, None
739
699
 
 
700
    def enum_history(self, direction):
 
701
        """Return (revno, revision_id) for history of branch.
 
702
 
 
703
        direction
 
704
            'forward' is from earliest to latest
 
705
            'reverse' is from latest to earliest
 
706
        """
 
707
        rh = self.revision_history()
 
708
        if direction == 'forward':
 
709
            i = 1
 
710
            for rid in rh:
 
711
                yield i, rid
 
712
                i += 1
 
713
        elif direction == 'reverse':
 
714
            i = len(rh)
 
715
            while i > 0:
 
716
                yield i, rh[i-1]
 
717
                i -= 1
 
718
        else:
 
719
            raise ValueError('invalid history direction', direction)
 
720
 
740
721
 
741
722
    def revno(self):
742
723
        """Return current revision number for this branch.
757
738
            return None
758
739
 
759
740
 
760
 
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
 
741
    def missing_revisions(self, other, stop_revision=None):
761
742
        """
762
743
        If self and other have not diverged, return a list of the revisions
763
744
        present in other, but missing from self.
796
777
        if stop_revision is None:
797
778
            stop_revision = other_len
798
779
        elif stop_revision > other_len:
799
 
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
780
            raise NoSuchRevision(self, stop_revision)
800
781
        
801
782
        return other_history[self_len:stop_revision]
802
783
 
803
784
 
804
785
    def update_revisions(self, other, stop_revision=None):
805
786
        """Pull in all new revisions from other branch.
 
787
        
 
788
        >>> from bzrlib.commit import commit
 
789
        >>> bzrlib.trace.silent = True
 
790
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
 
791
        >>> br1.add('foo')
 
792
        >>> br1.add('bar')
 
793
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
 
794
        >>> br2 = ScratchBranch()
 
795
        >>> br2.update_revisions(br1)
 
796
        Added 2 texts.
 
797
        Added 1 inventories.
 
798
        Added 1 revisions.
 
799
        >>> br2.revision_history()
 
800
        [u'REVISION-ID-1']
 
801
        >>> br2.update_revisions(br1)
 
802
        Added 0 texts.
 
803
        Added 0 inventories.
 
804
        Added 0 revisions.
 
805
        >>> br1.text_store.total_size() == br2.text_store.total_size()
 
806
        True
806
807
        """
807
 
        from bzrlib.fetch import greedy_fetch
808
 
        from bzrlib.revision import get_intervening_revisions
809
 
 
810
 
        pb = bzrlib.ui.ui_factory.progress_bar()
 
808
        from bzrlib.progress import ProgressBar
 
809
        try:
 
810
            set
 
811
        except NameError:
 
812
            from sets import Set as set
 
813
 
 
814
        pb = ProgressBar()
 
815
 
811
816
        pb.update('comparing histories')
812
 
        if stop_revision is None:
813
 
            other_revision = other.last_patch()
814
 
        else:
815
 
            other_revision = other.lookup_revision(stop_revision)
816
 
        count = greedy_fetch(self, other, other_revision, pb)[0]
817
 
        try:
818
 
            revision_ids = self.missing_revisions(other, stop_revision)
819
 
        except DivergedBranches, e:
820
 
            try:
821
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
822
 
                                                         other_revision, self)
823
 
                assert self.last_patch() not in revision_ids
824
 
            except bzrlib.errors.NotAncestor:
825
 
                raise e
826
 
 
827
 
        self.append_revision(*revision_ids)
828
 
        pb.clear()
829
 
 
830
 
    def install_revisions(self, other, revision_ids, pb):
 
817
        revision_ids = self.missing_revisions(other, stop_revision)
 
818
 
831
819
        if hasattr(other.revision_store, "prefetch"):
832
820
            other.revision_store.prefetch(revision_ids)
833
821
        if hasattr(other.inventory_store, "prefetch"):
834
 
            inventory_ids = []
835
 
            for rev_id in revision_ids:
836
 
                try:
837
 
                    revision = other.get_revision(rev_id).inventory_id
838
 
                    inventory_ids.append(revision)
839
 
                except bzrlib.errors.NoSuchRevision:
840
 
                    pass
 
822
            inventory_ids = [other.get_revision(r).inventory_id
 
823
                             for r in revision_ids]
841
824
            other.inventory_store.prefetch(inventory_ids)
842
 
 
843
 
        if pb is None:
844
 
            pb = bzrlib.ui.ui_factory.progress_bar()
845
825
                
846
826
        revisions = []
847
827
        needed_texts = set()
848
828
        i = 0
849
 
 
850
 
        failures = set()
851
 
        for i, rev_id in enumerate(revision_ids):
852
 
            pb.update('fetching revision', i+1, len(revision_ids))
853
 
            try:
854
 
                rev = other.get_revision(rev_id)
855
 
            except bzrlib.errors.NoSuchRevision:
856
 
                failures.add(rev_id)
857
 
                continue
858
 
 
 
829
        for rev_id in revision_ids:
 
830
            i += 1
 
831
            pb.update('fetching revision', i, len(revision_ids))
 
832
            rev = other.get_revision(rev_id)
859
833
            revisions.append(rev)
860
834
            inv = other.get_inventory(str(rev.inventory_id))
861
835
            for key, entry in inv.iter_entries():
866
840
 
867
841
        pb.clear()
868
842
                    
869
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
870
 
                                                    needed_texts)
871
 
        #print "Added %d texts." % count 
 
843
        count = self.text_store.copy_multi(other.text_store, needed_texts)
 
844
        print "Added %d texts." % count 
872
845
        inventory_ids = [ f.inventory_id for f in revisions ]
873
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
874
 
                                                         inventory_ids)
875
 
        #print "Added %d inventories." % count 
 
846
        count = self.inventory_store.copy_multi(other.inventory_store, 
 
847
                                                inventory_ids)
 
848
        print "Added %d inventories." % count 
876
849
        revision_ids = [ f.revision_id for f in revisions]
877
 
 
878
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
879
 
                                                          revision_ids,
880
 
                                                          permit_failure=True)
881
 
        assert len(cp_fail) == 0 
882
 
        return count, failures
883
 
       
884
 
 
 
850
        count = self.revision_store.copy_multi(other.revision_store, 
 
851
                                               revision_ids)
 
852
        for revision_id in revision_ids:
 
853
            self.append_revision(revision_id)
 
854
        print "Added %d revisions." % count
 
855
                    
 
856
        
885
857
    def commit(self, *args, **kw):
886
858
        from bzrlib.commit import commit
887
859
        commit(self, *args, **kw)
889
861
 
890
862
    def lookup_revision(self, revision):
891
863
        """Return the revision identifier for a given revision information."""
892
 
        revno, info = self._get_revision_info(revision)
 
864
        revno, info = self.get_revision_info(revision)
893
865
        return info
894
866
 
895
 
 
896
 
    def revision_id_to_revno(self, revision_id):
897
 
        """Given a revision id, return its revno"""
898
 
        history = self.revision_history()
899
 
        try:
900
 
            return history.index(revision_id) + 1
901
 
        except ValueError:
902
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
903
 
 
904
 
 
905
867
    def get_revision_info(self, revision):
906
868
        """Return (revno, revision id) for revision identifier.
907
869
 
910
872
        revision can also be a string, in which case it is parsed for something like
911
873
            'date:' or 'revid:' etc.
912
874
        """
913
 
        revno, rev_id = self._get_revision_info(revision)
914
 
        if revno is None:
915
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
916
 
        return revno, rev_id
917
 
 
918
 
    def get_rev_id(self, revno, history=None):
919
 
        """Find the revision id of the specified revno."""
920
 
        if revno == 0:
921
 
            return None
922
 
        if history is None:
923
 
            history = self.revision_history()
924
 
        elif revno <= 0 or revno > len(history):
925
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
926
 
        return history[revno - 1]
927
 
 
928
 
    def _get_revision_info(self, revision):
929
 
        """Return (revno, revision id) for revision specifier.
930
 
 
931
 
        revision can be an integer, in which case it is assumed to be revno
932
 
        (though this will translate negative values into positive ones)
933
 
        revision can also be a string, in which case it is parsed for something
934
 
        like 'date:' or 'revid:' etc.
935
 
 
936
 
        A revid is always returned.  If it is None, the specifier referred to
937
 
        the null revision.  If the revid does not occur in the revision
938
 
        history, revno will be None.
939
 
        """
940
 
        
941
875
        if revision is None:
942
876
            return 0, None
943
877
        revno = None
947
881
            pass
948
882
        revs = self.revision_history()
949
883
        if isinstance(revision, int):
 
884
            if revision == 0:
 
885
                return 0, None
 
886
            # Mabye we should do this first, but we don't need it if revision == 0
950
887
            if revision < 0:
951
888
                revno = len(revs) + revision + 1
952
889
            else:
953
890
                revno = revision
954
 
            rev_id = self.get_rev_id(revno, revs)
955
891
        elif isinstance(revision, basestring):
956
892
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
957
893
                if revision.startswith(prefix):
958
 
                    result = func(self, revs, revision)
959
 
                    if len(result) > 1:
960
 
                        revno, rev_id = result
961
 
                    else:
962
 
                        revno = result[0]
963
 
                        rev_id = self.get_rev_id(revno, revs)
 
894
                    revno = func(self, revs, revision)
964
895
                    break
965
896
            else:
966
 
                raise BzrError('No namespace registered for string: %r' %
967
 
                               revision)
968
 
        else:
969
 
            raise TypeError('Unhandled revision type %s' % revision)
 
897
                raise BzrError('No namespace registered for string: %r' % revision)
970
898
 
971
 
        if revno is None:
972
 
            if rev_id is None:
973
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
974
 
        return revno, rev_id
 
899
        if revno is None or revno <= 0 or revno > len(revs):
 
900
            raise BzrError("no such revision %s" % revision)
 
901
        return revno, revs[revno-1]
975
902
 
976
903
    def _namespace_revno(self, revs, revision):
977
904
        """Lookup a revision by revision number"""
978
905
        assert revision.startswith('revno:')
979
906
        try:
980
 
            return (int(revision[6:]),)
 
907
            return int(revision[6:])
981
908
        except ValueError:
982
909
            return None
983
910
    REVISION_NAMESPACES['revno:'] = _namespace_revno
984
911
 
985
912
    def _namespace_revid(self, revs, revision):
986
913
        assert revision.startswith('revid:')
987
 
        rev_id = revision[len('revid:'):]
988
914
        try:
989
 
            return revs.index(rev_id) + 1, rev_id
 
915
            return revs.index(revision[6:]) + 1
990
916
        except ValueError:
991
 
            return None, rev_id
 
917
            return None
992
918
    REVISION_NAMESPACES['revid:'] = _namespace_revid
993
919
 
994
920
    def _namespace_last(self, revs, revision):
996
922
        try:
997
923
            offset = int(revision[5:])
998
924
        except ValueError:
999
 
            return (None,)
 
925
            return None
1000
926
        else:
1001
927
            if offset <= 0:
1002
928
                raise BzrError('You must supply a positive value for --revision last:XXX')
1003
 
            return (len(revs) - offset + 1,)
 
929
            return len(revs) - offset + 1
1004
930
    REVISION_NAMESPACES['last:'] = _namespace_last
1005
931
 
1006
932
    def _namespace_tag(self, revs, revision):
1081
1007
                # TODO: Handle timezone.
1082
1008
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1083
1009
                if first >= dt and (last is None or dt >= last):
1084
 
                    return (i+1,)
 
1010
                    return i+1
1085
1011
        else:
1086
1012
            for i in range(len(revs)):
1087
1013
                r = self.get_revision(revs[i])
1088
1014
                # TODO: Handle timezone.
1089
1015
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1090
1016
                if first <= dt and (last is None or dt <= last):
1091
 
                    return (i+1,)
 
1017
                    return i+1
1092
1018
    REVISION_NAMESPACES['date:'] = _namespace_date
1093
1019
 
1094
 
 
1095
 
    def _namespace_ancestor(self, revs, revision):
1096
 
        from revision import common_ancestor, MultipleRevisionSources
1097
 
        other_branch = find_branch(_trim_namespace('ancestor', revision))
1098
 
        revision_a = self.last_patch()
1099
 
        revision_b = other_branch.last_patch()
1100
 
        for r, b in ((revision_a, self), (revision_b, other_branch)):
1101
 
            if r is None:
1102
 
                raise bzrlib.errors.NoCommits(b)
1103
 
        revision_source = MultipleRevisionSources(self, other_branch)
1104
 
        result = common_ancestor(revision_a, revision_b, revision_source)
1105
 
        try:
1106
 
            revno = self.revision_id_to_revno(result)
1107
 
        except bzrlib.errors.NoSuchRevision:
1108
 
            revno = None
1109
 
        return revno,result
1110
 
        
1111
 
 
1112
 
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1113
 
 
1114
1020
    def revision_tree(self, revision_id):
1115
1021
        """Return Tree for a revision on this branch.
1116
1022
 
1117
1023
        `revision_id` may be None for the null revision, in which case
1118
1024
        an `EmptyTree` is returned."""
 
1025
        from bzrlib.tree import EmptyTree, RevisionTree
1119
1026
        # TODO: refactor this to use an existing revision object
1120
1027
        # so we don't need to read it in twice.
1121
1028
        if revision_id == None:
1122
 
            return EmptyTree()
 
1029
            return EmptyTree(self.get_root_id())
1123
1030
        else:
1124
1031
            inv = self.get_revision_inventory(revision_id)
1125
1032
            return RevisionTree(self.text_store, inv)
1136
1043
 
1137
1044
        If there are no revisions yet, return an `EmptyTree`.
1138
1045
        """
 
1046
        from bzrlib.tree import EmptyTree, RevisionTree
1139
1047
        r = self.last_patch()
1140
1048
        if r == None:
1141
 
            return EmptyTree()
 
1049
            return EmptyTree(self.get_root_id())
1142
1050
        else:
1143
1051
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1144
1052
 
1179
1087
 
1180
1088
            inv.rename(file_id, to_dir_id, to_tail)
1181
1089
 
 
1090
            print "%s => %s" % (from_rel, to_rel)
 
1091
 
1182
1092
            from_abs = self.abspath(from_rel)
1183
1093
            to_abs = self.abspath(to_rel)
1184
1094
            try:
1203
1113
 
1204
1114
        Note that to_name is only the last component of the new name;
1205
1115
        this doesn't change the directory.
1206
 
 
1207
 
        This returns a list of (from_path, to_path) pairs for each
1208
 
        entry that is moved.
1209
1116
        """
1210
 
        result = []
1211
1117
        self.lock_write()
1212
1118
        try:
1213
1119
            ## TODO: Option to move IDs only
1248
1154
            for f in from_paths:
1249
1155
                name_tail = splitpath(f)[-1]
1250
1156
                dest_path = appendpath(to_name, name_tail)
1251
 
                result.append((f, dest_path))
 
1157
                print "%s => %s" % (f, dest_path)
1252
1158
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1253
1159
                try:
1254
1160
                    os.rename(self.abspath(f), self.abspath(dest_path))
1260
1166
        finally:
1261
1167
            self.unlock()
1262
1168
 
1263
 
        return result
1264
 
 
1265
1169
 
1266
1170
    def revert(self, filenames, old_tree=None, backups=True):
1267
1171
        """Restore selected files to the versions from a previous tree.
1349
1253
            self.unlock()
1350
1254
 
1351
1255
 
1352
 
    def get_parent(self):
1353
 
        """Return the parent location of the branch.
1354
 
 
1355
 
        This is the default location for push/pull/missing.  The usual
1356
 
        pattern is that the user can override it by specifying a
1357
 
        location.
1358
 
        """
1359
 
        import errno
1360
 
        _locs = ['parent', 'pull', 'x-pull']
1361
 
        for l in _locs:
1362
 
            try:
1363
 
                return self.controlfile(l, 'r').read().strip('\n')
1364
 
            except IOError, e:
1365
 
                if e.errno != errno.ENOENT:
1366
 
                    raise
1367
 
        return None
1368
 
 
1369
 
 
1370
 
    def set_parent(self, url):
1371
 
        # TODO: Maybe delete old location files?
1372
 
        from bzrlib.atomicfile import AtomicFile
1373
 
        self.lock_write()
1374
 
        try:
1375
 
            f = AtomicFile(self.controlfilename('parent'))
1376
 
            try:
1377
 
                f.write(url + '\n')
1378
 
                f.commit()
1379
 
            finally:
1380
 
                f.close()
1381
 
        finally:
1382
 
            self.unlock()
1383
 
 
1384
 
    def check_revno(self, revno):
1385
 
        """\
1386
 
        Check whether a revno corresponds to any revision.
1387
 
        Zero (the NULL revision) is considered valid.
1388
 
        """
1389
 
        if revno != 0:
1390
 
            self.check_real_revno(revno)
1391
 
            
1392
 
    def check_real_revno(self, revno):
1393
 
        """\
1394
 
        Check whether a revno corresponds to a real revision.
1395
 
        Zero (the NULL revision) is considered invalid
1396
 
        """
1397
 
        if revno < 1 or revno > self.revno():
1398
 
            raise InvalidRevisionNumber(revno)
1399
 
        
1400
 
        
1401
 
 
1402
1256
 
1403
1257
class ScratchBranch(Branch):
1404
1258
    """Special test class: a branch that cleans up after itself.
1446
1300
        os.rmdir(base)
1447
1301
        copytree(self.base, base, symlinks=True)
1448
1302
        return ScratchBranch(base=base)
1449
 
 
1450
 
 
1451
1303
        
1452
1304
    def __del__(self):
1453
1305
        self.destroy()
1523
1375
    """Return a new tree-root file id."""
1524
1376
    return gen_file_id('TREE_ROOT')
1525
1377
 
1526
 
 
1527
 
def pull_loc(branch):
1528
 
    # TODO: Should perhaps just make attribute be 'base' in
1529
 
    # RemoteBranch and Branch?
1530
 
    if hasattr(branch, "baseurl"):
1531
 
        return branch.baseurl
1532
 
    else:
1533
 
        return branch.base
1534
 
 
1535
 
 
1536
 
def copy_branch(branch_from, to_location, revision=None):
1537
 
    """Copy branch_from into the existing directory to_location.
1538
 
 
1539
 
    revision
1540
 
        If not None, only revisions up to this point will be copied.
1541
 
        The head of the new branch will be that revision.
1542
 
 
1543
 
    to_location
1544
 
        The name of a local directory that exists but is empty.
1545
 
    """
1546
 
    from bzrlib.merge import merge
1547
 
    from bzrlib.branch import Branch
1548
 
 
1549
 
    assert isinstance(branch_from, Branch)
1550
 
    assert isinstance(to_location, basestring)
1551
 
    
1552
 
    br_to = Branch(to_location, init=True)
1553
 
    br_to.set_root_id(branch_from.get_root_id())
1554
 
    if revision is None:
1555
 
        revno = branch_from.revno()
1556
 
    else:
1557
 
        revno, rev_id = branch_from.get_revision_info(revision)
1558
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1559
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1560
 
          check_clean=False, ignore_zero=True)
1561
 
    
1562
 
    from_location = pull_loc(branch_from)
1563
 
    br_to.set_parent(pull_loc(branch_from))
1564
 
    return br_to
1565
 
 
1566
 
def _trim_namespace(namespace, spec):
1567
 
    full_namespace = namespace + ':'
1568
 
    assert spec.startswith(full_namespace)
1569
 
    return spec[len(full_namespace):]