~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Aaron Bentley
  • Date: 2005-07-29 17:19:16 UTC
  • mto: (1092.1.41) (1185.3.4) (974.1.47)
  • mto: This revision was merged to the branch mainline in revision 1020.
  • Revision ID: abentley@panoramicfeedback.com-20050729171916-322fd81b451d2e3e
Added merge-type parameter to merge.

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 IndexError:
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
 
 
810
        pb = ProgressBar()
 
811
 
811
812
        pb.update('comparing histories')
812
 
 
813
 
        try:
814
 
            revision_ids = self.missing_revisions(other, stop_revision)
815
 
        except DivergedBranches, e:
816
 
            try:
817
 
                if stop_revision is None:
818
 
                    end_revision = other.last_patch()
819
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
820
 
                                                         end_revision, other)
821
 
                assert self.last_patch() not in revision_ids
822
 
            except bzrlib.errors.NotAncestor:
823
 
                raise e
824
 
 
825
 
        if len(revision_ids) > 0:
826
 
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
827
 
        else:
828
 
            count = 0
829
 
        self.append_revision(*revision_ids)
830
 
        ## note("Added %d revisions." % count)
831
 
        pb.clear()
832
 
 
833
 
    def install_revisions(self, other, revision_ids, pb):
 
813
        revision_ids = self.missing_revisions(other, stop_revision)
 
814
 
834
815
        if hasattr(other.revision_store, "prefetch"):
835
816
            other.revision_store.prefetch(revision_ids)
836
817
        if hasattr(other.inventory_store, "prefetch"):
837
818
            inventory_ids = [other.get_revision(r).inventory_id
838
819
                             for r in revision_ids]
839
820
            other.inventory_store.prefetch(inventory_ids)
840
 
 
841
 
        if pb is None:
842
 
            pb = bzrlib.ui.ui_factory.progress_bar()
843
821
                
844
822
        revisions = []
845
823
        needed_texts = set()
846
824
        i = 0
847
 
 
848
 
        failures = set()
849
 
        for i, rev_id in enumerate(revision_ids):
850
 
            pb.update('fetching revision', i+1, len(revision_ids))
851
 
            try:
852
 
                rev = other.get_revision(rev_id)
853
 
            except bzrlib.errors.NoSuchRevision:
854
 
                failures.add(rev_id)
855
 
                continue
856
 
 
 
825
        for rev_id in revision_ids:
 
826
            i += 1
 
827
            pb.update('fetching revision', i, len(revision_ids))
 
828
            rev = other.get_revision(rev_id)
857
829
            revisions.append(rev)
858
830
            inv = other.get_inventory(str(rev.inventory_id))
859
831
            for key, entry in inv.iter_entries():
864
836
 
865
837
        pb.clear()
866
838
                    
867
 
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
868
 
                                                    needed_texts)
869
 
        #print "Added %d texts." % count 
 
839
        count = self.text_store.copy_multi(other.text_store, needed_texts)
 
840
        print "Added %d texts." % count 
870
841
        inventory_ids = [ f.inventory_id for f in revisions ]
871
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
872
 
                                                         inventory_ids)
873
 
        #print "Added %d inventories." % count 
 
842
        count = self.inventory_store.copy_multi(other.inventory_store, 
 
843
                                                inventory_ids)
 
844
        print "Added %d inventories." % count 
874
845
        revision_ids = [ f.revision_id for f in revisions]
875
 
 
876
 
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
877
 
                                                          revision_ids,
878
 
                                                          permit_failure=True)
879
 
        assert len(cp_fail) == 0 
880
 
        return count, failures
881
 
       
882
 
 
 
846
        count = self.revision_store.copy_multi(other.revision_store, 
 
847
                                               revision_ids)
 
848
        for revision_id in revision_ids:
 
849
            self.append_revision(revision_id)
 
850
        print "Added %d revisions." % count
 
851
                    
 
852
        
883
853
    def commit(self, *args, **kw):
884
854
        from bzrlib.commit import commit
885
855
        commit(self, *args, **kw)
887
857
 
888
858
    def lookup_revision(self, revision):
889
859
        """Return the revision identifier for a given revision information."""
890
 
        revno, info = self._get_revision_info(revision)
 
860
        revno, info = self.get_revision_info(revision)
891
861
        return info
892
862
 
893
 
 
894
 
    def revision_id_to_revno(self, revision_id):
895
 
        """Given a revision id, return its revno"""
896
 
        history = self.revision_history()
897
 
        try:
898
 
            return history.index(revision_id) + 1
899
 
        except ValueError:
900
 
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
901
 
 
902
 
 
903
863
    def get_revision_info(self, revision):
904
864
        """Return (revno, revision id) for revision identifier.
905
865
 
908
868
        revision can also be a string, in which case it is parsed for something like
909
869
            'date:' or 'revid:' etc.
910
870
        """
911
 
        revno, rev_id = self._get_revision_info(revision)
912
 
        if revno is None:
913
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
914
 
        return revno, rev_id
915
 
 
916
 
    def get_rev_id(self, revno, history=None):
917
 
        """Find the revision id of the specified revno."""
918
 
        if revno == 0:
919
 
            return None
920
 
        if history is None:
921
 
            history = self.revision_history()
922
 
        elif revno <= 0 or revno > len(history):
923
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
924
 
        return history[revno - 1]
925
 
 
926
 
    def _get_revision_info(self, revision):
927
 
        """Return (revno, revision id) for revision specifier.
928
 
 
929
 
        revision can be an integer, in which case it is assumed to be revno
930
 
        (though this will translate negative values into positive ones)
931
 
        revision can also be a string, in which case it is parsed for something
932
 
        like 'date:' or 'revid:' etc.
933
 
 
934
 
        A revid is always returned.  If it is None, the specifier referred to
935
 
        the null revision.  If the revid does not occur in the revision
936
 
        history, revno will be None.
937
 
        """
938
 
        
939
871
        if revision is None:
940
872
            return 0, None
941
873
        revno = None
945
877
            pass
946
878
        revs = self.revision_history()
947
879
        if isinstance(revision, int):
 
880
            if revision == 0:
 
881
                return 0, None
 
882
            # Mabye we should do this first, but we don't need it if revision == 0
948
883
            if revision < 0:
949
884
                revno = len(revs) + revision + 1
950
885
            else:
951
886
                revno = revision
952
 
            rev_id = self.get_rev_id(revno, revs)
953
887
        elif isinstance(revision, basestring):
954
888
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
955
889
                if revision.startswith(prefix):
956
 
                    result = func(self, revs, revision)
957
 
                    if len(result) > 1:
958
 
                        revno, rev_id = result
959
 
                    else:
960
 
                        revno = result[0]
961
 
                        rev_id = self.get_rev_id(revno, revs)
 
890
                    revno = func(self, revs, revision)
962
891
                    break
963
892
            else:
964
 
                raise BzrError('No namespace registered for string: %r' %
965
 
                               revision)
966
 
        else:
967
 
            raise TypeError('Unhandled revision type %s' % revision)
 
893
                raise BzrError('No namespace registered for string: %r' % revision)
968
894
 
969
 
        if revno is None:
970
 
            if rev_id is None:
971
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
972
 
        return revno, rev_id
 
895
        if revno is None or revno <= 0 or revno > len(revs):
 
896
            raise BzrError("no such revision %s" % revision)
 
897
        return revno, revs[revno-1]
973
898
 
974
899
    def _namespace_revno(self, revs, revision):
975
900
        """Lookup a revision by revision number"""
976
901
        assert revision.startswith('revno:')
977
902
        try:
978
 
            return (int(revision[6:]),)
 
903
            return int(revision[6:])
979
904
        except ValueError:
980
905
            return None
981
906
    REVISION_NAMESPACES['revno:'] = _namespace_revno
982
907
 
983
908
    def _namespace_revid(self, revs, revision):
984
909
        assert revision.startswith('revid:')
985
 
        rev_id = revision[len('revid:'):]
986
910
        try:
987
 
            return revs.index(rev_id) + 1, rev_id
 
911
            return revs.index(revision[6:]) + 1
988
912
        except ValueError:
989
 
            return None, rev_id
 
913
            return None
990
914
    REVISION_NAMESPACES['revid:'] = _namespace_revid
991
915
 
992
916
    def _namespace_last(self, revs, revision):
994
918
        try:
995
919
            offset = int(revision[5:])
996
920
        except ValueError:
997
 
            return (None,)
 
921
            return None
998
922
        else:
999
923
            if offset <= 0:
1000
924
                raise BzrError('You must supply a positive value for --revision last:XXX')
1001
 
            return (len(revs) - offset + 1,)
 
925
            return len(revs) - offset + 1
1002
926
    REVISION_NAMESPACES['last:'] = _namespace_last
1003
927
 
1004
928
    def _namespace_tag(self, revs, revision):
1079
1003
                # TODO: Handle timezone.
1080
1004
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1081
1005
                if first >= dt and (last is None or dt >= last):
1082
 
                    return (i+1,)
 
1006
                    return i+1
1083
1007
        else:
1084
1008
            for i in range(len(revs)):
1085
1009
                r = self.get_revision(revs[i])
1086
1010
                # TODO: Handle timezone.
1087
1011
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1088
1012
                if first <= dt and (last is None or dt <= last):
1089
 
                    return (i+1,)
 
1013
                    return i+1
1090
1014
    REVISION_NAMESPACES['date:'] = _namespace_date
1091
1015
 
1092
1016
    def revision_tree(self, revision_id):
1094
1018
 
1095
1019
        `revision_id` may be None for the null revision, in which case
1096
1020
        an `EmptyTree` is returned."""
 
1021
        from bzrlib.tree import EmptyTree, RevisionTree
1097
1022
        # TODO: refactor this to use an existing revision object
1098
1023
        # so we don't need to read it in twice.
1099
1024
        if revision_id == None:
1100
 
            return EmptyTree()
 
1025
            return EmptyTree(self.get_root_id())
1101
1026
        else:
1102
1027
            inv = self.get_revision_inventory(revision_id)
1103
1028
            return RevisionTree(self.text_store, inv)
1114
1039
 
1115
1040
        If there are no revisions yet, return an `EmptyTree`.
1116
1041
        """
 
1042
        from bzrlib.tree import EmptyTree, RevisionTree
1117
1043
        r = self.last_patch()
1118
1044
        if r == None:
1119
 
            return EmptyTree()
 
1045
            return EmptyTree(self.get_root_id())
1120
1046
        else:
1121
1047
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1122
1048
 
1157
1083
 
1158
1084
            inv.rename(file_id, to_dir_id, to_tail)
1159
1085
 
 
1086
            print "%s => %s" % (from_rel, to_rel)
 
1087
 
1160
1088
            from_abs = self.abspath(from_rel)
1161
1089
            to_abs = self.abspath(to_rel)
1162
1090
            try:
1181
1109
 
1182
1110
        Note that to_name is only the last component of the new name;
1183
1111
        this doesn't change the directory.
1184
 
 
1185
 
        This returns a list of (from_path, to_path) pairs for each
1186
 
        entry that is moved.
1187
1112
        """
1188
 
        result = []
1189
1113
        self.lock_write()
1190
1114
        try:
1191
1115
            ## TODO: Option to move IDs only
1226
1150
            for f in from_paths:
1227
1151
                name_tail = splitpath(f)[-1]
1228
1152
                dest_path = appendpath(to_name, name_tail)
1229
 
                result.append((f, dest_path))
 
1153
                print "%s => %s" % (f, dest_path)
1230
1154
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1231
1155
                try:
1232
1156
                    os.rename(self.abspath(f), self.abspath(dest_path))
1238
1162
        finally:
1239
1163
            self.unlock()
1240
1164
 
1241
 
        return result
1242
 
 
1243
1165
 
1244
1166
    def revert(self, filenames, old_tree=None, backups=True):
1245
1167
        """Restore selected files to the versions from a previous tree.
1327
1249
            self.unlock()
1328
1250
 
1329
1251
 
1330
 
    def get_parent(self):
1331
 
        """Return the parent location of the branch.
1332
 
 
1333
 
        This is the default location for push/pull/missing.  The usual
1334
 
        pattern is that the user can override it by specifying a
1335
 
        location.
1336
 
        """
1337
 
        import errno
1338
 
        _locs = ['parent', 'pull', 'x-pull']
1339
 
        for l in _locs:
1340
 
            try:
1341
 
                return self.controlfile(l, 'r').read().strip('\n')
1342
 
            except IOError, e:
1343
 
                if e.errno != errno.ENOENT:
1344
 
                    raise
1345
 
        return None
1346
 
 
1347
 
 
1348
 
    def set_parent(self, url):
1349
 
        # TODO: Maybe delete old location files?
1350
 
        from bzrlib.atomicfile import AtomicFile
1351
 
        self.lock_write()
1352
 
        try:
1353
 
            f = AtomicFile(self.controlfilename('parent'))
1354
 
            try:
1355
 
                f.write(url + '\n')
1356
 
                f.commit()
1357
 
            finally:
1358
 
                f.close()
1359
 
        finally:
1360
 
            self.unlock()
1361
 
 
1362
 
    def check_revno(self, revno):
1363
 
        """\
1364
 
        Check whether a revno corresponds to any revision.
1365
 
        Zero (the NULL revision) is considered valid.
1366
 
        """
1367
 
        if revno != 0:
1368
 
            self.check_real_revno(revno)
1369
 
            
1370
 
    def check_real_revno(self, revno):
1371
 
        """\
1372
 
        Check whether a revno corresponds to a real revision.
1373
 
        Zero (the NULL revision) is considered invalid
1374
 
        """
1375
 
        if revno < 1 or revno > self.revno():
1376
 
            raise InvalidRevisionNumber(revno)
1377
 
        
1378
 
        
1379
 
 
1380
1252
 
1381
1253
class ScratchBranch(Branch):
1382
1254
    """Special test class: a branch that cleans up after itself.
1424
1296
        os.rmdir(base)
1425
1297
        copytree(self.base, base, symlinks=True)
1426
1298
        return ScratchBranch(base=base)
1427
 
 
1428
 
 
1429
1299
        
1430
1300
    def __del__(self):
1431
1301
        self.destroy()
1501
1371
    """Return a new tree-root file id."""
1502
1372
    return gen_file_id('TREE_ROOT')
1503
1373
 
1504
 
 
1505
 
def pull_loc(branch):
1506
 
    # TODO: Should perhaps just make attribute be 'base' in
1507
 
    # RemoteBranch and Branch?
1508
 
    if hasattr(branch, "baseurl"):
1509
 
        return branch.baseurl
1510
 
    else:
1511
 
        return branch.base
1512
 
 
1513
 
 
1514
 
def copy_branch(branch_from, to_location, revision=None):
1515
 
    """Copy branch_from into the existing directory to_location.
1516
 
 
1517
 
    revision
1518
 
        If not None, only revisions up to this point will be copied.
1519
 
        The head of the new branch will be that revision.
1520
 
 
1521
 
    to_location
1522
 
        The name of a local directory that exists but is empty.
1523
 
    """
1524
 
    from bzrlib.merge import merge
1525
 
    from bzrlib.branch import Branch
1526
 
 
1527
 
    assert isinstance(branch_from, Branch)
1528
 
    assert isinstance(to_location, basestring)
1529
 
    
1530
 
    br_to = Branch(to_location, init=True)
1531
 
    br_to.set_root_id(branch_from.get_root_id())
1532
 
    if revision is None:
1533
 
        revno = branch_from.revno()
1534
 
    else:
1535
 
        revno, rev_id = branch_from.get_revision_info(revision)
1536
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1537
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1538
 
          check_clean=False, ignore_zero=True)
1539
 
    
1540
 
    from_location = pull_loc(branch_from)
1541
 
    br_to.set_parent(pull_loc(branch_from))
1542