/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-06-29 04:11:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050629041140-6b17e65a23ffdf47
Merge John's log patch:

implements bzr log --forward --verbose
optimizes so that only logs to be printed are read (rather than reading
all and filtering out unwanted).

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
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
27
 
import bzrlib.errors
28
 
from bzrlib.textui import show_status
29
 
from bzrlib.revision import Revision
30
 
from bzrlib.xml import unpack_xml
31
 
from bzrlib.delta import compare_trees
32
 
from bzrlib.tree import EmptyTree, RevisionTree
33
 
        
 
24
from bzrlib.errors import BzrError
 
25
 
34
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
27
## TODO: Maybe include checks for common corruption of newlines, etc?
36
28
 
37
29
 
38
 
# TODO: Some operations like log might retrieve the same revisions
39
 
# repeatedly to calculate deltas.  We could perhaps have a weakref
40
 
# cache in memory to make this faster.
41
 
 
42
30
 
43
31
def find_branch(f, **args):
44
32
    if f and (f.startswith('http://') or f.startswith('https://')):
130
118
        Exception.__init__(self, "These branches have diverged.")
131
119
 
132
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
 
133
129
######################################################################
134
130
# branch objects
135
131
 
154
150
    _lock_count = None
155
151
    _lock = None
156
152
    
157
 
    # Map some sort of prefix into a namespace
158
 
    # stuff like "revno:10", "revid:", etc.
159
 
    # This should match a prefix with a function which accepts
160
 
    REVISION_NAMESPACES = {}
161
 
 
162
153
    def __init__(self, base, init=False, find_root=True):
163
154
        """Create new branch object at a particular location.
164
155
 
311
302
            os.mkdir(self.controlfilename(d))
312
303
        for f in ('revision-history', 'merged-patches',
313
304
                  'pending-merged-patches', 'branch-name',
314
 
                  'branch-lock',
315
 
                  'pending-merges'):
 
305
                  'branch-lock'):
316
306
            self.controlfile(f, 'w').write('')
317
307
        mutter('created control directory in ' + self.base)
318
308
 
319
 
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
 
309
        pack_xml(Inventory(), self.controlfile('inventory','w'))
320
310
 
321
311
 
322
312
    def _check_format(self):
337
327
                           ['use a different bzr version',
338
328
                            'or remove the .bzr directory and "bzr init" again'])
339
329
 
340
 
    def get_root_id(self):
341
 
        """Return the id of this branches root"""
342
 
        inv = self.read_working_inventory()
343
 
        return inv.root.file_id
344
330
 
345
 
    def set_root_id(self, file_id):
346
 
        inv = self.read_working_inventory()
347
 
        orig_root_id = inv.root.file_id
348
 
        del inv._byid[inv.root.file_id]
349
 
        inv.root.file_id = file_id
350
 
        inv._byid[inv.root.file_id] = inv.root
351
 
        for fid in inv:
352
 
            entry = inv[fid]
353
 
            if entry.parent_id in (None, orig_root_id):
354
 
                entry.parent_id = inv.root.file_id
355
 
        self._write_inventory(inv)
356
331
 
357
332
    def read_working_inventory(self):
358
333
        """Read the working inventory."""
365
340
            # ElementTree does its own conversion from UTF-8, so open in
366
341
            # binary.
367
342
            inv = unpack_xml(Inventory,
368
 
                             self.controlfile('inventory', 'rb'))
 
343
                                  self.controlfile('inventory', 'rb'))
369
344
            mutter("loaded inventory of %d items in %f"
370
345
                   % (len(inv), time() - before))
371
346
            return inv
426
401
              add all non-ignored children.  Perhaps do that in a
427
402
              higher-level method.
428
403
        """
 
404
        from bzrlib.textui import show_status
429
405
        # TODO: Re-adding a file that is removed in the working copy
430
406
        # should probably put it back with the previous ID.
431
407
        if isinstance(files, basestring):
484
460
            # use inventory as it was in that revision
485
461
            file_id = tree.inventory.path2id(file)
486
462
            if not file_id:
487
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
 
463
                raise BzrError("%r is not present in revision %d" % (file, revno))
488
464
            tree.print_file(file_id)
489
465
        finally:
490
466
            self.unlock()
504
480
        is the opposite of add.  Removing it is consistent with most
505
481
        other tools.  Maybe an option.
506
482
        """
 
483
        from bzrlib.textui import show_status
507
484
        ## TODO: Normalize names
508
485
        ## TODO: Remove nested loops; better scalability
509
486
        if isinstance(files, basestring):
538
515
    # FIXME: this doesn't need to be a branch method
539
516
    def set_inventory(self, new_inventory_list):
540
517
        from bzrlib.inventory import Inventory, InventoryEntry
541
 
        inv = Inventory(self.get_root_id())
 
518
        inv = Inventory()
542
519
        for path, file_id, parent, kind in new_inventory_list:
543
520
            name = os.path.basename(path)
544
521
            if name == "":
566
543
        return self.working_tree().unknowns()
567
544
 
568
545
 
569
 
    def append_revision(self, *revision_ids):
 
546
    def append_revision(self, revision_id):
570
547
        from bzrlib.atomicfile import AtomicFile
571
548
 
572
 
        for revision_id in revision_ids:
573
 
            mutter("add {%s} to revision-history" % revision_id)
574
 
 
575
 
        rev_history = self.revision_history()
576
 
        rev_history.extend(revision_ids)
 
549
        mutter("add {%s} to revision-history" % revision_id)
 
550
        rev_history = self.revision_history() + [revision_id]
577
551
 
578
552
        f = AtomicFile(self.controlfilename('revision-history'))
579
553
        try:
584
558
            f.close()
585
559
 
586
560
 
587
 
    def get_revision_xml(self, revision_id):
588
 
        """Return XML file object for revision object."""
589
 
        if not revision_id or not isinstance(revision_id, basestring):
590
 
            raise InvalidRevisionId(revision_id)
591
 
 
592
 
        self.lock_read()
593
 
        try:
594
 
            try:
595
 
                return self.revision_store[revision_id]
596
 
            except IndexError:
597
 
                raise bzrlib.errors.NoSuchRevision(revision_id)
598
 
        finally:
599
 
            self.unlock()
600
 
 
601
 
 
602
561
    def get_revision(self, revision_id):
603
562
        """Return the Revision object for a named revision"""
604
 
        xml_file = self.get_revision_xml(revision_id)
 
563
        from bzrlib.revision import Revision
 
564
        from bzrlib.xml import unpack_xml
605
565
 
 
566
        self.lock_read()
606
567
        try:
607
 
            r = unpack_xml(Revision, xml_file)
608
 
        except SyntaxError, e:
609
 
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
610
 
                                         [revision_id,
611
 
                                          str(e)])
 
568
            if not revision_id or not isinstance(revision_id, basestring):
 
569
                raise ValueError('invalid revision-id: %r' % revision_id)
 
570
            r = unpack_xml(Revision, self.revision_store[revision_id])
 
571
        finally:
 
572
            self.unlock()
612
573
            
613
574
        assert r.revision_id == revision_id
614
575
        return r
615
 
 
616
 
 
617
 
    def get_revision_delta(self, revno):
618
 
        """Return the delta for one revision.
619
 
 
620
 
        The delta is relative to its mainline predecessor, or the
621
 
        empty tree for revision 1.
622
 
        """
623
 
        assert isinstance(revno, int)
624
 
        rh = self.revision_history()
625
 
        if not (1 <= revno <= len(rh)):
626
 
            raise InvalidRevisionNumber(revno)
627
 
 
628
 
        # revno is 1-based; list is 0-based
629
 
 
630
 
        new_tree = self.revision_tree(rh[revno-1])
631
 
        if revno == 1:
632
 
            old_tree = EmptyTree()
633
 
        else:
634
 
            old_tree = self.revision_tree(rh[revno-2])
635
 
 
636
 
        return compare_trees(old_tree, new_tree)
637
 
 
638
576
        
639
577
 
640
578
    def get_revision_sha1(self, revision_id):
645
583
        # the revision, (add signatures/remove signatures) and still
646
584
        # have all hash pointers stay consistent.
647
585
        # But for now, just hash the contents.
648
 
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
 
586
        return sha_file(self.revision_store[revision_id])
649
587
 
650
588
 
651
589
    def get_inventory(self, inventory_id):
668
606
 
669
607
    def get_revision_inventory(self, revision_id):
670
608
        """Return inventory of a past revision."""
671
 
        # bzr 0.0.6 imposes the constraint that the inventory_id
672
 
        # must be the same as its revision, so this is trivial.
673
609
        if revision_id == None:
674
610
            from bzrlib.inventory import Inventory
675
 
            return Inventory(self.get_root_id())
 
611
            return Inventory()
676
612
        else:
677
 
            return self.get_inventory(revision_id)
 
613
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
678
614
 
679
615
 
680
616
    def revision_history(self):
735
671
                return r+1, my_history[r]
736
672
        return None, None
737
673
 
 
674
    def enum_history(self, direction):
 
675
        """Return (revno, revision_id) for history of branch.
 
676
 
 
677
        direction
 
678
            'forward' is from earliest to latest
 
679
            'reverse' is from latest to earliest
 
680
        """
 
681
        rh = self.revision_history()
 
682
        if direction == 'forward':
 
683
            i = 1
 
684
            for rid in rh:
 
685
                yield i, rid
 
686
                i += 1
 
687
        elif direction == 'reverse':
 
688
            i = len(rh)
 
689
            while i > 0:
 
690
                yield i, rh[i-1]
 
691
                i -= 1
 
692
        else:
 
693
            raise ValueError('invalid history direction', direction)
 
694
 
738
695
 
739
696
    def revno(self):
740
697
        """Return current revision number for this branch.
823
780
        True
824
781
        """
825
782
        from bzrlib.progress import ProgressBar
 
783
        try:
 
784
            set
 
785
        except NameError:
 
786
            from sets import Set as set
826
787
 
827
788
        pb = ProgressBar()
828
789
 
872
833
        commit(self, *args, **kw)
873
834
        
874
835
 
875
 
    def lookup_revision(self, revision):
876
 
        """Return the revision identifier for a given revision information."""
877
 
        revno, info = self.get_revision_info(revision)
878
 
        return info
879
 
 
880
 
    def get_revision_info(self, revision):
881
 
        """Return (revno, revision id) for revision identifier.
882
 
 
883
 
        revision can be an integer, in which case it is assumed to be revno (though
884
 
            this will translate negative values into positive ones)
885
 
        revision can also be a string, in which case it is parsed for something like
886
 
            'date:' or 'revid:' etc.
887
 
        """
888
 
        if revision is None:
889
 
            return 0, None
890
 
        revno = None
891
 
        try:# Convert to int if possible
892
 
            revision = int(revision)
893
 
        except ValueError:
894
 
            pass
895
 
        revs = self.revision_history()
896
 
        if isinstance(revision, int):
897
 
            if revision == 0:
898
 
                return 0, None
899
 
            # Mabye we should do this first, but we don't need it if revision == 0
900
 
            if revision < 0:
901
 
                revno = len(revs) + revision + 1
902
 
            else:
903
 
                revno = revision
904
 
        elif isinstance(revision, basestring):
905
 
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
906
 
                if revision.startswith(prefix):
907
 
                    revno = func(self, revs, revision)
908
 
                    break
909
 
            else:
910
 
                raise BzrError('No namespace registered for string: %r' % revision)
911
 
 
912
 
        if revno is None or revno <= 0 or revno > len(revs):
913
 
            raise BzrError("no such revision %s" % revision)
914
 
        return revno, revs[revno-1]
915
 
 
916
 
    def _namespace_revno(self, revs, revision):
917
 
        """Lookup a revision by revision number"""
918
 
        assert revision.startswith('revno:')
919
 
        try:
920
 
            return int(revision[6:])
921
 
        except ValueError:
922
 
            return None
923
 
    REVISION_NAMESPACES['revno:'] = _namespace_revno
924
 
 
925
 
    def _namespace_revid(self, revs, revision):
926
 
        assert revision.startswith('revid:')
927
 
        try:
928
 
            return revs.index(revision[6:]) + 1
929
 
        except ValueError:
930
 
            return None
931
 
    REVISION_NAMESPACES['revid:'] = _namespace_revid
932
 
 
933
 
    def _namespace_last(self, revs, revision):
934
 
        assert revision.startswith('last:')
935
 
        try:
936
 
            offset = int(revision[5:])
937
 
        except ValueError:
938
 
            return None
939
 
        else:
940
 
            if offset <= 0:
941
 
                raise BzrError('You must supply a positive value for --revision last:XXX')
942
 
            return len(revs) - offset + 1
943
 
    REVISION_NAMESPACES['last:'] = _namespace_last
944
 
 
945
 
    def _namespace_tag(self, revs, revision):
946
 
        assert revision.startswith('tag:')
947
 
        raise BzrError('tag: namespace registered, but not implemented.')
948
 
    REVISION_NAMESPACES['tag:'] = _namespace_tag
949
 
 
950
 
    def _namespace_date(self, revs, revision):
951
 
        assert revision.startswith('date:')
952
 
        import datetime
953
 
        # Spec for date revisions:
954
 
        #   date:value
955
 
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
956
 
        #   it can also start with a '+/-/='. '+' says match the first
957
 
        #   entry after the given date. '-' is match the first entry before the date
958
 
        #   '=' is match the first entry after, but still on the given date.
959
 
        #
960
 
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
961
 
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
962
 
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
963
 
        #       May 13th, 2005 at 0:00
964
 
        #
965
 
        #   So the proper way of saying 'give me all entries for today' is:
966
 
        #       -r {date:+today}:{date:-tomorrow}
967
 
        #   The default is '=' when not supplied
968
 
        val = revision[5:]
969
 
        match_style = '='
970
 
        if val[:1] in ('+', '-', '='):
971
 
            match_style = val[:1]
972
 
            val = val[1:]
973
 
 
974
 
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
975
 
        if val.lower() == 'yesterday':
976
 
            dt = today - datetime.timedelta(days=1)
977
 
        elif val.lower() == 'today':
978
 
            dt = today
979
 
        elif val.lower() == 'tomorrow':
980
 
            dt = today + datetime.timedelta(days=1)
981
 
        else:
982
 
            import re
983
 
            # This should be done outside the function to avoid recompiling it.
984
 
            _date_re = re.compile(
985
 
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
986
 
                    r'(,|T)?\s*'
987
 
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
988
 
                )
989
 
            m = _date_re.match(val)
990
 
            if not m or (not m.group('date') and not m.group('time')):
991
 
                raise BzrError('Invalid revision date %r' % revision)
992
 
 
993
 
            if m.group('date'):
994
 
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
995
 
            else:
996
 
                year, month, day = today.year, today.month, today.day
997
 
            if m.group('time'):
998
 
                hour = int(m.group('hour'))
999
 
                minute = int(m.group('minute'))
1000
 
                if m.group('second'):
1001
 
                    second = int(m.group('second'))
1002
 
                else:
1003
 
                    second = 0
1004
 
            else:
1005
 
                hour, minute, second = 0,0,0
1006
 
 
1007
 
            dt = datetime.datetime(year=year, month=month, day=day,
1008
 
                    hour=hour, minute=minute, second=second)
1009
 
        first = dt
1010
 
        last = None
1011
 
        reversed = False
1012
 
        if match_style == '-':
1013
 
            reversed = True
1014
 
        elif match_style == '=':
1015
 
            last = dt + datetime.timedelta(days=1)
1016
 
 
1017
 
        if reversed:
1018
 
            for i in range(len(revs)-1, -1, -1):
1019
 
                r = self.get_revision(revs[i])
1020
 
                # TODO: Handle timezone.
1021
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1022
 
                if first >= dt and (last is None or dt >= last):
1023
 
                    return i+1
1024
 
        else:
1025
 
            for i in range(len(revs)):
1026
 
                r = self.get_revision(revs[i])
1027
 
                # TODO: Handle timezone.
1028
 
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1029
 
                if first <= dt and (last is None or dt <= last):
1030
 
                    return i+1
1031
 
    REVISION_NAMESPACES['date:'] = _namespace_date
 
836
    def lookup_revision(self, revno):
 
837
        """Return revision hash for revision number."""
 
838
        if revno == 0:
 
839
            return None
 
840
 
 
841
        try:
 
842
            # list is 0-based; revisions are 1-based
 
843
            return self.revision_history()[revno-1]
 
844
        except IndexError:
 
845
            raise BzrError("no such revision %s" % revno)
 
846
 
1032
847
 
1033
848
    def revision_tree(self, revision_id):
1034
849
        """Return Tree for a revision on this branch.
1035
850
 
1036
851
        `revision_id` may be None for the null revision, in which case
1037
852
        an `EmptyTree` is returned."""
 
853
        from bzrlib.tree import EmptyTree, RevisionTree
1038
854
        # TODO: refactor this to use an existing revision object
1039
855
        # so we don't need to read it in twice.
1040
856
        if revision_id == None:
1055
871
 
1056
872
        If there are no revisions yet, return an `EmptyTree`.
1057
873
        """
 
874
        from bzrlib.tree import EmptyTree, RevisionTree
1058
875
        r = self.last_patch()
1059
876
        if r == None:
1060
877
            return EmptyTree()
1222
1039
                f.close()
1223
1040
 
1224
1041
 
1225
 
    def pending_merges(self):
1226
 
        """Return a list of pending merges.
1227
 
 
1228
 
        These are revisions that have been merged into the working
1229
 
        directory but not yet committed.
1230
 
        """
1231
 
        cfn = self.controlfilename('pending-merges')
1232
 
        if not os.path.exists(cfn):
1233
 
            return []
1234
 
        p = []
1235
 
        for l in self.controlfile('pending-merges', 'r').readlines():
1236
 
            p.append(l.rstrip('\n'))
1237
 
        return p
1238
 
 
1239
 
 
1240
 
    def add_pending_merge(self, revision_id):
1241
 
        from bzrlib.revision import validate_revision_id
1242
 
 
1243
 
        validate_revision_id(revision_id)
1244
 
 
1245
 
        p = self.pending_merges()
1246
 
        if revision_id in p:
1247
 
            return
1248
 
        p.append(revision_id)
1249
 
        self.set_pending_merges(p)
1250
 
 
1251
 
 
1252
 
    def set_pending_merges(self, rev_list):
1253
 
        from bzrlib.atomicfile import AtomicFile
1254
 
        self.lock_write()
1255
 
        try:
1256
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1257
 
            try:
1258
 
                for l in rev_list:
1259
 
                    print >>f, l
1260
 
                f.commit()
1261
 
            finally:
1262
 
                f.close()
1263
 
        finally:
1264
 
            self.unlock()
1265
 
 
1266
 
 
1267
1042
 
1268
1043
class ScratchBranch(Branch):
1269
1044
    """Special test class: a branch that cleans up after itself.
1380
1155
 
1381
1156
    s = hexlify(rand_bytes(8))
1382
1157
    return '-'.join((name, compact_date(time()), s))
1383
 
 
1384
 
 
1385
 
def gen_root_id():
1386
 
    """Return a new tree-root file id."""
1387
 
    return gen_file_id('TREE_ROOT')
1388