~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-09-22 13:32:02 UTC
  • Revision ID: mbp@sourcefrog.net-20050922133202-347cfd35d2941dd5
- simple weave-based annotate code (not complete)

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
 
24
24
import bzrlib
25
25
from bzrlib.trace import mutter, note
26
 
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes, 
27
 
                            rename, splitpath, sha_file, appendpath, 
28
 
                            file_kind)
 
26
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
 
27
     splitpath, \
 
28
     sha_file, appendpath, file_kind
 
29
 
29
30
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
31
                           NoSuchRevision, HistoryMissing, NotBranchError,
31
 
                           DivergedBranches, LockError, UnlistableStore,
32
 
                           UnlistableBranch)
 
32
                           LockError)
33
33
from bzrlib.textui import show_status
34
 
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
 
34
from bzrlib.revision import Revision, validate_revision_id
35
35
from bzrlib.delta import compare_trees
36
36
from bzrlib.tree import EmptyTree, RevisionTree
37
37
from bzrlib.inventory import Inventory
38
38
from bzrlib.weavestore import WeaveStore
39
 
from bzrlib.store import copy_all, ImmutableStore
 
39
from bzrlib.store import ImmutableStore
40
40
import bzrlib.xml5
41
41
import bzrlib.ui
42
42
 
51
51
# cache in memory to make this faster.  In general anything can be
52
52
# cached in memory between lock and unlock operations.
53
53
 
54
 
def find_branch(*ignored, **ignored_too):
55
 
    # XXX: leave this here for about one release, then remove it
56
 
    raise NotImplementedError('find_branch() is not supported anymore, '
57
 
                              'please use one of the new branch constructors')
 
54
# TODO: please move the revision-string syntax stuff out of the branch
 
55
# object; it's clutter
 
56
 
 
57
 
 
58
def find_branch(f, **args):
 
59
    if f and (f.startswith('http://') or f.startswith('https://')):
 
60
        import remotebranch 
 
61
        return remotebranch.RemoteBranch(f, **args)
 
62
    else:
 
63
        return Branch(f, **args)
 
64
 
 
65
 
 
66
def find_cached_branch(f, cache_root, **args):
 
67
    from remotebranch import RemoteBranch
 
68
    br = find_branch(f, **args)
 
69
    def cacheify(br, store_name):
 
70
        from meta_store import CachedStore
 
71
        cache_path = os.path.join(cache_root, store_name)
 
72
        os.mkdir(cache_path)
 
73
        new_store = CachedStore(getattr(br, store_name), cache_path)
 
74
        setattr(br, store_name, new_store)
 
75
 
 
76
    if isinstance(br, RemoteBranch):
 
77
        cacheify(br, 'inventory_store')
 
78
        cacheify(br, 'text_store')
 
79
        cacheify(br, 'revision_store')
 
80
    return br
 
81
 
58
82
 
59
83
def _relpath(base, path):
60
84
    """Return path relative to base, or raise exception.
116
140
 
117
141
 
118
142
 
 
143
# XXX: move into bzrlib.errors; subclass BzrError    
 
144
class DivergedBranches(Exception):
 
145
    def __init__(self, branch1, branch2):
 
146
        self.branch1 = branch1
 
147
        self.branch2 = branch2
 
148
        Exception.__init__(self, "These branches have diverged.")
 
149
 
119
150
 
120
151
######################################################################
121
152
# branch objects
124
155
    """Branch holding a history of revisions.
125
156
 
126
157
    base
127
 
        Base directory/url of the branch.
128
 
    """
129
 
    base = None
130
 
 
131
 
    def __init__(self, *ignored, **ignored_too):
132
 
        raise NotImplementedError('The Branch class is abstract')
133
 
 
134
 
    @staticmethod
135
 
    def open_downlevel(base):
136
 
        """Open a branch which may be of an old format.
137
 
        
138
 
        Only local branches are supported."""
139
 
        return LocalBranch(base, find_root=False, relax_version_check=True)
140
 
        
141
 
    @staticmethod
142
 
    def open(base):
143
 
        """Open an existing branch, rooted at 'base' (url)"""
144
 
        if base and (base.startswith('http://') or base.startswith('https://')):
145
 
            from bzrlib.remotebranch import RemoteBranch
146
 
            return RemoteBranch(base, find_root=False)
147
 
        else:
148
 
            return LocalBranch(base, find_root=False)
149
 
 
150
 
    @staticmethod
151
 
    def open_containing(url):
152
 
        """Open an existing branch which contains url.
153
 
        
154
 
        This probes for a branch at url, and searches upwards from there.
155
 
        """
156
 
        if url and (url.startswith('http://') or url.startswith('https://')):
157
 
            from bzrlib.remotebranch import RemoteBranch
158
 
            return RemoteBranch(url)
159
 
        else:
160
 
            return LocalBranch(url)
161
 
 
162
 
    @staticmethod
163
 
    def initialize(base):
164
 
        """Create a new branch, rooted at 'base' (url)"""
165
 
        if base and (base.startswith('http://') or base.startswith('https://')):
166
 
            from bzrlib.remotebranch import RemoteBranch
167
 
            return RemoteBranch(base, init=True)
168
 
        else:
169
 
            return LocalBranch(base, init=True)
170
 
 
171
 
    def setup_caching(self, cache_root):
172
 
        """Subclasses that care about caching should override this, and set
173
 
        up cached stores located under cache_root.
174
 
        """
175
 
 
176
 
 
177
 
class LocalBranch(Branch):
178
 
    """A branch stored in the actual filesystem.
179
 
 
180
 
    Note that it's "local" in the context of the filesystem; it doesn't
181
 
    really matter if it's on an nfs/smb/afs/coda/... share, as long as
182
 
    it's writable, and can be accessed via the normal filesystem API.
 
158
        Base directory of the branch.
183
159
 
184
160
    _lock_mode
185
161
        None, or 'r' or 'w'
191
167
    _lock
192
168
        Lock object from bzrlib.lock.
193
169
    """
194
 
    # We actually expect this class to be somewhat short-lived; part of its
195
 
    # purpose is to try to isolate what bits of the branch logic are tied to
196
 
    # filesystem access, so that in a later step, we can extricate them to
197
 
    # a separarte ("storage") class.
 
170
    base = None
198
171
    _lock_mode = None
199
172
    _lock_count = None
200
173
    _lock = None
205
178
    # This should match a prefix with a function which accepts
206
179
    REVISION_NAMESPACES = {}
207
180
 
208
 
    def push_stores(self, branch_to):
209
 
        """Copy the content of this branches store to branch_to."""
210
 
        if (self._branch_format != branch_to._branch_format
211
 
            or self._branch_format != 4):
212
 
            from bzrlib.fetch import greedy_fetch
213
 
            mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
214
 
                   self, self._branch_format, branch_to, branch_to._branch_format)
215
 
            greedy_fetch(to_branch=branch_to, from_branch=self,
216
 
                         revision=self.last_revision())
217
 
            return
218
 
 
219
 
        store_pairs = ((self.text_store,      branch_to.text_store),
220
 
                       (self.inventory_store, branch_to.inventory_store),
221
 
                       (self.revision_store,  branch_to.revision_store))
222
 
        try:
223
 
            for from_store, to_store in store_pairs: 
224
 
                copy_all(from_store, to_store)
225
 
        except UnlistableStore:
226
 
            raise UnlistableBranch(from_store)
227
 
 
228
181
    def __init__(self, base, init=False, find_root=True,
229
182
                 relax_version_check=False):
230
183
        """Create new branch object at a particular location.
231
184
 
232
 
        base -- Base directory for the branch. May be a file:// url.
 
185
        base -- Base directory for the branch.
233
186
        
234
187
        init -- If True, create new control files in a previously
235
188
             unversioned directory.  If False, the branch must already
252
205
        elif find_root:
253
206
            self.base = find_branch_root(base)
254
207
        else:
255
 
            if base.startswith("file://"):
256
 
                base = base[7:]
257
208
            self.base = os.path.realpath(base)
258
209
            if not isdir(self.controlfilename('.')):
259
210
                raise NotBranchError('not a bzr branch: %s' % quotefn(base),
260
211
                                     ['use "bzr init" to initialize a '
261
212
                                      'new working tree'])
262
213
        self._check_format(relax_version_check)
263
 
        cfn = self.controlfilename
 
214
        cfn = self.controlfilename
264
215
        if self._branch_format == 4:
265
216
            self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
217
            self.text_store = ImmutableStore(cfn('text-store'))
267
 
        elif self._branch_format == 5:
268
 
            self.control_weaves = WeaveStore(cfn([]))
269
 
            self.weave_store = WeaveStore(cfn('weaves'))
270
 
            if init:
271
 
                # FIXME: Unify with make_control_files
272
 
                self.control_weaves.put_empty_weave('inventory')
273
 
                self.control_weaves.put_empty_weave('ancestry')
 
218
        elif self._branch_format == 5:
 
219
            self.control_weaves = WeaveStore(cfn([]))
 
220
            self.weave_store = WeaveStore(cfn('weaves'))
274
221
        self.revision_store = ImmutableStore(cfn('revision-store'))
275
222
 
276
223
 
283
230
 
284
231
    def __del__(self):
285
232
        if self._lock_mode or self._lock:
286
 
            # XXX: This should show something every time, and be suitable for
287
 
            # headless operation and embedding
288
233
            warn("branch %r was not explicitly unlocked" % self)
289
234
            self._lock.unlock()
290
235
 
 
236
 
291
237
    def lock_write(self):
292
238
        if self._lock_mode:
293
239
            if self._lock_mode != 'w':
388
334
        # simplicity.
389
335
        f = self.controlfile('inventory','w')
390
336
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
 
337
        
391
338
 
392
339
 
393
340
    def _check_format(self, relax_version_check):
399
346
        In the future, we might need different in-memory Branch
400
347
        classes to support downlevel branches.  But not yet.
401
348
        """
402
 
        try:
403
 
            fmt = self.controlfile('branch-format', 'r').read()
 
349
        try:
 
350
            fmt = self.controlfile('branch-format', 'r').read()
404
351
        except IOError, e:
405
352
            if e.errno == errno.ENOENT:
406
353
                raise NotBranchError(self.base)
414
361
 
415
362
        if (not relax_version_check
416
363
            and self._branch_format != 5):
417
 
            raise BzrError('sorry, branch format %r not supported' % fmt,
418
 
                           ['use a different bzr version',
419
 
                            'or remove the .bzr directory and "bzr init" again'])
 
364
            raise BzrError('sorry, branch format "%s" not supported; ' 
 
365
                           'use a different bzr version, '
 
366
                           'or run "bzr upgrade"'
 
367
                           % fmt.rstrip('\n\r'))
 
368
        
420
369
 
421
370
    def get_root_id(self):
422
371
        """Return the id of this branches root"""
546
495
        """Print `file` to stdout."""
547
496
        self.lock_read()
548
497
        try:
549
 
            tree = self.revision_tree(self.get_rev_id(revno))
 
498
            tree = self.revision_tree(self.lookup_revision(revno))
550
499
            # use inventory as it was in that revision
551
500
            file_id = tree.inventory.path2id(file)
552
501
            if not file_id:
655
604
 
656
605
        This does not necessarily imply the revision is merge
657
606
        or on the mainline."""
658
 
        return (revision_id is None
659
 
                or revision_id in self.revision_store)
 
607
        return revision_id in self.revision_store
660
608
 
661
609
 
662
610
    def get_revision_xml_file(self, revision_id):
668
616
        try:
669
617
            try:
670
618
                return self.revision_store[revision_id]
671
 
            except (IndexError, KeyError):
 
619
            except IndexError:
672
620
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
673
621
        finally:
674
622
            self.unlock()
714
662
 
715
663
        return compare_trees(old_tree, new_tree)
716
664
 
 
665
        
717
666
 
718
667
    def get_revision_sha1(self, revision_id):
719
668
        """Hash the stored value of a revision, and return it."""
722
671
 
723
672
    def _get_ancestry_weave(self):
724
673
        return self.control_weaves.get_weave('ancestry')
725
 
        
 
674
        
726
675
 
727
676
    def get_ancestry(self, revision_id):
728
677
        """Return a list of revision-ids integrated by a revision.
729
678
        """
730
679
        # strip newlines
731
 
        if revision_id is None:
732
 
            return [None]
733
 
        w = self._get_ancestry_weave()
734
 
        return [None] + [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
 
680
        w = self._get_ancestry_weave()
 
681
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
735
682
 
736
683
 
737
684
    def get_inventory_weave(self):
783
730
 
784
731
    def common_ancestor(self, other, self_revno=None, other_revno=None):
785
732
        """
786
 
        >>> from bzrlib.commit import commit
 
733
        >>> import commit
787
734
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
788
735
        >>> sb.common_ancestor(sb) == (None, None)
789
736
        True
790
 
        >>> commit(sb, "Committing first revision", verbose=False)
 
737
        >>> commit.commit(sb, "Committing first revision")
791
738
        >>> sb.common_ancestor(sb)[0]
792
739
        1
793
740
        >>> clone = sb.clone()
794
 
        >>> commit(sb, "Committing second revision", verbose=False)
 
741
        >>> commit.commit(sb, "Committing second revision")
795
742
        >>> sb.common_ancestor(sb)[0]
796
743
        2
797
744
        >>> sb.common_ancestor(clone)[0]
798
745
        1
799
 
        >>> commit(clone, "Committing divergent second revision", 
800
 
        ...               verbose=False)
 
746
        >>> commit.commit(clone, "Committing divergent second revision")
801
747
        >>> sb.common_ancestor(clone)[0]
802
748
        1
803
749
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
891
837
            assert isinstance(stop_revision, int)
892
838
            if stop_revision > other_len:
893
839
                raise bzrlib.errors.NoSuchRevision(self, stop_revision)
 
840
        
894
841
        return other_history[self_len:stop_revision]
895
842
 
896
 
    def update_revisions(self, other, stop_revision=None):
897
 
        """Pull in new perfect-fit revisions."""
 
843
 
 
844
    def update_revisions(self, other, stop_revno=None):
 
845
        """Pull in new perfect-fit revisions.
 
846
        """
898
847
        from bzrlib.fetch import greedy_fetch
899
 
        from bzrlib.revision import get_intervening_revisions
900
 
        if stop_revision is None:
901
 
            stop_revision = other.last_revision()
 
848
 
 
849
        if stop_revno:
 
850
            stop_revision = other.lookup_revision(stop_revno)
 
851
        else:
 
852
            stop_revision = None
902
853
        greedy_fetch(to_branch=self, from_branch=other,
903
854
                     revision=stop_revision)
904
 
        pullable_revs = self.missing_revisions(
905
 
            other, other.revision_id_to_revno(stop_revision))
 
855
 
 
856
        pullable_revs = self.missing_revisions(other, stop_revision)
 
857
 
906
858
        if pullable_revs:
907
859
            greedy_fetch(to_branch=self,
908
860
                         from_branch=other,
909
861
                         revision=pullable_revs[-1])
910
862
            self.append_revision(*pullable_revs)
911
 
    
 
863
 
912
864
 
913
865
    def commit(self, *args, **kw):
914
866
        from bzrlib.commit import Commit
915
867
        Commit().commit(self, *args, **kw)
916
 
    
 
868
        
 
869
 
 
870
    def lookup_revision(self, revision):
 
871
        """Return the revision identifier for a given revision information."""
 
872
        revno, info = self._get_revision_info(revision)
 
873
        return info
 
874
 
 
875
 
917
876
    def revision_id_to_revno(self, revision_id):
918
877
        """Given a revision id, return its revno"""
919
 
        if revision_id is None:
920
 
            return 0
921
878
        history = self.revision_history()
922
879
        try:
923
880
            return history.index(revision_id) + 1
924
881
        except ValueError:
925
882
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
926
883
 
 
884
 
 
885
    def get_revision_info(self, revision):
 
886
        """Return (revno, revision id) for revision identifier.
 
887
 
 
888
        revision can be an integer, in which case it is assumed to be revno (though
 
889
            this will translate negative values into positive ones)
 
890
        revision can also be a string, in which case it is parsed for something like
 
891
            'date:' or 'revid:' etc.
 
892
        """
 
893
        revno, rev_id = self._get_revision_info(revision)
 
894
        if revno is None:
 
895
            raise bzrlib.errors.NoSuchRevision(self, revision)
 
896
        return revno, rev_id
 
897
 
927
898
    def get_rev_id(self, revno, history=None):
928
899
        """Find the revision id of the specified revno."""
929
900
        if revno == 0:
934
905
            raise bzrlib.errors.NoSuchRevision(self, revno)
935
906
        return history[revno - 1]
936
907
 
 
908
    def _get_revision_info(self, revision):
 
909
        """Return (revno, revision id) for revision specifier.
 
910
 
 
911
        revision can be an integer, in which case it is assumed to be revno
 
912
        (though this will translate negative values into positive ones)
 
913
        revision can also be a string, in which case it is parsed for something
 
914
        like 'date:' or 'revid:' etc.
 
915
 
 
916
        A revid is always returned.  If it is None, the specifier referred to
 
917
        the null revision.  If the revid does not occur in the revision
 
918
        history, revno will be None.
 
919
        """
 
920
        
 
921
        if revision is None:
 
922
            return 0, None
 
923
        revno = None
 
924
        try:# Convert to int if possible
 
925
            revision = int(revision)
 
926
        except ValueError:
 
927
            pass
 
928
        revs = self.revision_history()
 
929
        if isinstance(revision, int):
 
930
            if revision < 0:
 
931
                revno = len(revs) + revision + 1
 
932
            else:
 
933
                revno = revision
 
934
            rev_id = self.get_rev_id(revno, revs)
 
935
        elif isinstance(revision, basestring):
 
936
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
 
937
                if revision.startswith(prefix):
 
938
                    result = func(self, revs, revision)
 
939
                    if len(result) > 1:
 
940
                        revno, rev_id = result
 
941
                    else:
 
942
                        revno = result[0]
 
943
                        rev_id = self.get_rev_id(revno, revs)
 
944
                    break
 
945
            else:
 
946
                raise BzrError('No namespace registered for string: %r' %
 
947
                               revision)
 
948
        else:
 
949
            raise TypeError('Unhandled revision type %s' % revision)
 
950
 
 
951
        if revno is None:
 
952
            if rev_id is None:
 
953
                raise bzrlib.errors.NoSuchRevision(self, revision)
 
954
        return revno, rev_id
 
955
 
 
956
    def _namespace_revno(self, revs, revision):
 
957
        """Lookup a revision by revision number"""
 
958
        assert revision.startswith('revno:')
 
959
        try:
 
960
            return (int(revision[6:]),)
 
961
        except ValueError:
 
962
            return None
 
963
    REVISION_NAMESPACES['revno:'] = _namespace_revno
 
964
 
 
965
    def _namespace_revid(self, revs, revision):
 
966
        assert revision.startswith('revid:')
 
967
        rev_id = revision[len('revid:'):]
 
968
        try:
 
969
            return revs.index(rev_id) + 1, rev_id
 
970
        except ValueError:
 
971
            return None, rev_id
 
972
    REVISION_NAMESPACES['revid:'] = _namespace_revid
 
973
 
 
974
    def _namespace_last(self, revs, revision):
 
975
        assert revision.startswith('last:')
 
976
        try:
 
977
            offset = int(revision[5:])
 
978
        except ValueError:
 
979
            return (None,)
 
980
        else:
 
981
            if offset <= 0:
 
982
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
983
            return (len(revs) - offset + 1,)
 
984
    REVISION_NAMESPACES['last:'] = _namespace_last
 
985
 
 
986
    def _namespace_tag(self, revs, revision):
 
987
        assert revision.startswith('tag:')
 
988
        raise BzrError('tag: namespace registered, but not implemented.')
 
989
    REVISION_NAMESPACES['tag:'] = _namespace_tag
 
990
 
 
991
    def _namespace_date(self, revs, revision):
 
992
        assert revision.startswith('date:')
 
993
        import datetime
 
994
        # Spec for date revisions:
 
995
        #   date:value
 
996
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
997
        #   it can also start with a '+/-/='. '+' says match the first
 
998
        #   entry after the given date. '-' is match the first entry before the date
 
999
        #   '=' is match the first entry after, but still on the given date.
 
1000
        #
 
1001
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
1002
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
1003
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
1004
        #       May 13th, 2005 at 0:00
 
1005
        #
 
1006
        #   So the proper way of saying 'give me all entries for today' is:
 
1007
        #       -r {date:+today}:{date:-tomorrow}
 
1008
        #   The default is '=' when not supplied
 
1009
        val = revision[5:]
 
1010
        match_style = '='
 
1011
        if val[:1] in ('+', '-', '='):
 
1012
            match_style = val[:1]
 
1013
            val = val[1:]
 
1014
 
 
1015
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
 
1016
        if val.lower() == 'yesterday':
 
1017
            dt = today - datetime.timedelta(days=1)
 
1018
        elif val.lower() == 'today':
 
1019
            dt = today
 
1020
        elif val.lower() == 'tomorrow':
 
1021
            dt = today + datetime.timedelta(days=1)
 
1022
        else:
 
1023
            import re
 
1024
            # This should be done outside the function to avoid recompiling it.
 
1025
            _date_re = re.compile(
 
1026
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
1027
                    r'(,|T)?\s*'
 
1028
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
1029
                )
 
1030
            m = _date_re.match(val)
 
1031
            if not m or (not m.group('date') and not m.group('time')):
 
1032
                raise BzrError('Invalid revision date %r' % revision)
 
1033
 
 
1034
            if m.group('date'):
 
1035
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
1036
            else:
 
1037
                year, month, day = today.year, today.month, today.day
 
1038
            if m.group('time'):
 
1039
                hour = int(m.group('hour'))
 
1040
                minute = int(m.group('minute'))
 
1041
                if m.group('second'):
 
1042
                    second = int(m.group('second'))
 
1043
                else:
 
1044
                    second = 0
 
1045
            else:
 
1046
                hour, minute, second = 0,0,0
 
1047
 
 
1048
            dt = datetime.datetime(year=year, month=month, day=day,
 
1049
                    hour=hour, minute=minute, second=second)
 
1050
        first = dt
 
1051
        last = None
 
1052
        reversed = False
 
1053
        if match_style == '-':
 
1054
            reversed = True
 
1055
        elif match_style == '=':
 
1056
            last = dt + datetime.timedelta(days=1)
 
1057
 
 
1058
        if reversed:
 
1059
            for i in range(len(revs)-1, -1, -1):
 
1060
                r = self.get_revision(revs[i])
 
1061
                # TODO: Handle timezone.
 
1062
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1063
                if first >= dt and (last is None or dt >= last):
 
1064
                    return (i+1,)
 
1065
        else:
 
1066
            for i in range(len(revs)):
 
1067
                r = self.get_revision(revs[i])
 
1068
                # TODO: Handle timezone.
 
1069
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1070
                if first <= dt and (last is None or dt <= last):
 
1071
                    return (i+1,)
 
1072
    REVISION_NAMESPACES['date:'] = _namespace_date
 
1073
 
937
1074
    def revision_tree(self, revision_id):
938
1075
        """Return Tree for a revision on this branch.
939
1076
 
950
1087
 
951
1088
    def working_tree(self):
952
1089
        """Return a `Tree` for the working copy."""
953
 
        from bzrlib.workingtree import WorkingTree
 
1090
        from workingtree import WorkingTree
954
1091
        return WorkingTree(self.base, self.read_working_inventory())
955
1092
 
956
1093
 
1000
1137
            from_abs = self.abspath(from_rel)
1001
1138
            to_abs = self.abspath(to_rel)
1002
1139
            try:
1003
 
                rename(from_abs, to_abs)
 
1140
                os.rename(from_abs, to_abs)
1004
1141
            except OSError, e:
1005
1142
                raise BzrError("failed to rename %r to %r: %s"
1006
1143
                        % (from_abs, to_abs, e[1]),
1069
1206
                result.append((f, dest_path))
1070
1207
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1071
1208
                try:
1072
 
                    rename(self.abspath(f), self.abspath(dest_path))
 
1209
                    os.rename(self.abspath(f), self.abspath(dest_path))
1073
1210
                except OSError, e:
1074
1211
                    raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1075
1212
                            ["rename rolled back"])
1215
1352
            raise InvalidRevisionNumber(revno)
1216
1353
        
1217
1354
        
1218
 
        
1219
 
 
1220
 
 
1221
 
class ScratchBranch(LocalBranch):
 
1355
 
 
1356
 
 
1357
class ScratchBranch(Branch):
1222
1358
    """Special test class: a branch that cleans up after itself.
1223
1359
 
1224
1360
    >>> b = ScratchBranch()
1241
1377
        if base is None:
1242
1378
            base = mkdtemp()
1243
1379
            init = True
1244
 
        LocalBranch.__init__(self, base, init=init)
 
1380
        Branch.__init__(self, base, init=init)
1245
1381
        for d in dirs:
1246
1382
            os.mkdir(self.abspath(d))
1247
1383
            
1253
1389
        """
1254
1390
        >>> orig = ScratchBranch(files=["file1", "file2"])
1255
1391
        >>> clone = orig.clone()
1256
 
        >>> if os.name != 'nt':
1257
 
        ...   os.path.samefile(orig.base, clone.base)
1258
 
        ... else:
1259
 
        ...   orig.base == clone.base
1260
 
        ...
 
1392
        >>> os.path.samefile(orig.base, clone.base)
1261
1393
        False
1262
1394
        >>> os.path.isfile(os.path.join(clone.base, "file1"))
1263
1395
        True
1346
1478
    return gen_file_id('TREE_ROOT')
1347
1479
 
1348
1480
 
1349
 
def copy_branch(branch_from, to_location, revision=None, basis_branch=None):
 
1481
def pull_loc(branch):
 
1482
    # TODO: Should perhaps just make attribute be 'base' in
 
1483
    # RemoteBranch and Branch?
 
1484
    if hasattr(branch, "baseurl"):
 
1485
        return branch.baseurl
 
1486
    else:
 
1487
        return branch.base
 
1488
 
 
1489
 
 
1490
def copy_branch(branch_from, to_location, revision=None):
1350
1491
    """Copy branch_from into the existing directory to_location.
1351
1492
 
1352
1493
    revision
1353
1494
        If not None, only revisions up to this point will be copied.
1354
 
        The head of the new branch will be that revision.  Must be a
1355
 
        revid or None.
 
1495
        The head of the new branch will be that revision.  Can be a
 
1496
        revno or revid.
1356
1497
 
1357
1498
    to_location
1358
1499
        The name of a local directory that exists but is empty.
1359
 
 
1360
 
    revno
1361
 
        The revision to copy up to
1362
 
 
1363
 
    basis_branch
1364
 
        A local branch to copy revisions from, related to branch_from
1365
1500
    """
1366
1501
    # TODO: This could be done *much* more efficiently by just copying
1367
1502
    # all the whole weaves and revisions, rather than getting one
1368
1503
    # revision at a time.
1369
1504
    from bzrlib.merge import merge
 
1505
    from bzrlib.branch import Branch
1370
1506
 
1371
1507
    assert isinstance(branch_from, Branch)
1372
1508
    assert isinstance(to_location, basestring)
1373
1509
    
1374
 
    br_to = Branch.initialize(to_location)
1375
 
    mutter("copy branch from %s to %s", branch_from, br_to)
1376
 
    if basis_branch is not None:
1377
 
        basis_branch.push_stores(br_to)
 
1510
    br_to = Branch(to_location, init=True)
1378
1511
    br_to.set_root_id(branch_from.get_root_id())
1379
1512
    if revision is None:
1380
 
        revision = branch_from.last_revision()
1381
 
    br_to.update_revisions(branch_from, stop_revision=revision)
 
1513
        revno = None
 
1514
    else:
 
1515
        revno, rev_id = branch_from.get_revision_info(revision)
 
1516
    br_to.update_revisions(branch_from, stop_revno=revno)
1382
1517
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1383
1518
          check_clean=False, ignore_zero=True)
1384
 
    br_to.set_parent(branch_from.base)
1385
 
    mutter("copied")
1386
 
    return br_to
 
1519
    
 
1520
    from_location = pull_loc(branch_from)
 
1521
    br_to.set_parent(pull_loc(branch_from))
 
1522