~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-07-22 23:43:26 UTC
  • Revision ID: mbp@sourcefrog.net-20050722234326-13104af9d837ed2b
todo

Show diffs side-by-side

added added

removed removed

Lines of Context:
150
150
    _lock_count = None
151
151
    _lock = None
152
152
    
 
153
    # Map some sort of prefix into a namespace
 
154
    # stuff like "revno:10", "revid:", etc.
 
155
    # This should match a prefix with a function which accepts
 
156
    REVISION_NAMESPACES = {}
 
157
 
153
158
    def __init__(self, base, init=False, find_root=True):
154
159
        """Create new branch object at a particular location.
155
160
 
307
312
            self.controlfile(f, 'w').write('')
308
313
        mutter('created control directory in ' + self.base)
309
314
 
310
 
        pack_xml(Inventory(), self.controlfile('inventory','w'))
 
315
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
311
316
 
312
317
 
313
318
    def _check_format(self):
328
333
                           ['use a different bzr version',
329
334
                            'or remove the .bzr directory and "bzr init" again'])
330
335
 
 
336
    def get_root_id(self):
 
337
        """Return the id of this branches root"""
 
338
        inv = self.read_working_inventory()
 
339
        return inv.root.file_id
331
340
 
 
341
    def set_root_id(self, file_id):
 
342
        inv = self.read_working_inventory()
 
343
        orig_root_id = inv.root.file_id
 
344
        del inv._byid[inv.root.file_id]
 
345
        inv.root.file_id = file_id
 
346
        inv._byid[inv.root.file_id] = inv.root
 
347
        for fid in inv:
 
348
            entry = inv[fid]
 
349
            if entry.parent_id in (None, orig_root_id):
 
350
                entry.parent_id = inv.root.file_id
 
351
        self._write_inventory(inv)
332
352
 
333
353
    def read_working_inventory(self):
334
354
        """Read the working inventory."""
341
361
            # ElementTree does its own conversion from UTF-8, so open in
342
362
            # binary.
343
363
            inv = unpack_xml(Inventory,
344
 
                                  self.controlfile('inventory', 'rb'))
 
364
                             self.controlfile('inventory', 'rb'))
345
365
            mutter("loaded inventory of %d items in %f"
346
366
                   % (len(inv), time() - before))
347
367
            return inv
461
481
            # use inventory as it was in that revision
462
482
            file_id = tree.inventory.path2id(file)
463
483
            if not file_id:
464
 
                raise BzrError("%r is not present in revision %d" % (file, revno))
 
484
                raise BzrError("%r is not present in revision %s" % (file, revno))
465
485
            tree.print_file(file_id)
466
486
        finally:
467
487
            self.unlock()
516
536
    # FIXME: this doesn't need to be a branch method
517
537
    def set_inventory(self, new_inventory_list):
518
538
        from bzrlib.inventory import Inventory, InventoryEntry
519
 
        inv = Inventory()
 
539
        inv = Inventory(self.get_root_id())
520
540
        for path, file_id, parent, kind in new_inventory_list:
521
541
            name = os.path.basename(path)
522
542
            if name == "":
544
564
        return self.working_tree().unknowns()
545
565
 
546
566
 
547
 
    def append_revision(self, revision_id):
 
567
    def append_revision(self, *revision_ids):
548
568
        from bzrlib.atomicfile import AtomicFile
549
569
 
550
 
        mutter("add {%s} to revision-history" % revision_id)
551
 
        rev_history = self.revision_history() + [revision_id]
 
570
        for revision_id in revision_ids:
 
571
            mutter("add {%s} to revision-history" % revision_id)
 
572
 
 
573
        rev_history = self.revision_history()
 
574
        rev_history.extend(revision_ids)
552
575
 
553
576
        f = AtomicFile(self.controlfilename('revision-history'))
554
577
        try:
611
634
        # must be the same as its revision, so this is trivial.
612
635
        if revision_id == None:
613
636
            from bzrlib.inventory import Inventory
614
 
            return Inventory()
 
637
            return Inventory(self.get_root_id())
615
638
        else:
616
639
            return self.get_inventory(revision_id)
617
640
 
783
806
        True
784
807
        """
785
808
        from bzrlib.progress import ProgressBar
786
 
        try:
787
 
            set
788
 
        except NameError:
789
 
            from sets import Set as set
790
809
 
791
810
        pb = ProgressBar()
792
811
 
836
855
        commit(self, *args, **kw)
837
856
        
838
857
 
839
 
    def lookup_revision(self, revno):
840
 
        """Return revision hash for revision number."""
841
 
        if revno == 0:
842
 
            return None
843
 
 
844
 
        try:
845
 
            # list is 0-based; revisions are 1-based
846
 
            return self.revision_history()[revno-1]
847
 
        except IndexError:
848
 
            raise BzrError("no such revision %s" % revno)
849
 
 
 
858
    def lookup_revision(self, revision):
 
859
        """Return the revision identifier for a given revision information."""
 
860
        revno, info = self.get_revision_info(revision)
 
861
        return info
 
862
 
 
863
    def get_revision_info(self, revision):
 
864
        """Return (revno, revision id) for revision identifier.
 
865
 
 
866
        revision can be an integer, in which case it is assumed to be revno (though
 
867
            this will translate negative values into positive ones)
 
868
        revision can also be a string, in which case it is parsed for something like
 
869
            'date:' or 'revid:' etc.
 
870
        """
 
871
        if revision is None:
 
872
            return 0, None
 
873
        revno = None
 
874
        try:# Convert to int if possible
 
875
            revision = int(revision)
 
876
        except ValueError:
 
877
            pass
 
878
        revs = self.revision_history()
 
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
 
883
            if revision < 0:
 
884
                revno = len(revs) + revision + 1
 
885
            else:
 
886
                revno = revision
 
887
        elif isinstance(revision, basestring):
 
888
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
 
889
                if revision.startswith(prefix):
 
890
                    revno = func(self, revs, revision)
 
891
                    break
 
892
            else:
 
893
                raise BzrError('No namespace registered for string: %r' % revision)
 
894
 
 
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]
 
898
 
 
899
    def _namespace_revno(self, revs, revision):
 
900
        """Lookup a revision by revision number"""
 
901
        assert revision.startswith('revno:')
 
902
        try:
 
903
            return int(revision[6:])
 
904
        except ValueError:
 
905
            return None
 
906
    REVISION_NAMESPACES['revno:'] = _namespace_revno
 
907
 
 
908
    def _namespace_revid(self, revs, revision):
 
909
        assert revision.startswith('revid:')
 
910
        try:
 
911
            return revs.index(revision[6:]) + 1
 
912
        except ValueError:
 
913
            return None
 
914
    REVISION_NAMESPACES['revid:'] = _namespace_revid
 
915
 
 
916
    def _namespace_last(self, revs, revision):
 
917
        assert revision.startswith('last:')
 
918
        try:
 
919
            offset = int(revision[5:])
 
920
        except ValueError:
 
921
            return None
 
922
        else:
 
923
            if offset <= 0:
 
924
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
925
            return len(revs) - offset + 1
 
926
    REVISION_NAMESPACES['last:'] = _namespace_last
 
927
 
 
928
    def _namespace_tag(self, revs, revision):
 
929
        assert revision.startswith('tag:')
 
930
        raise BzrError('tag: namespace registered, but not implemented.')
 
931
    REVISION_NAMESPACES['tag:'] = _namespace_tag
 
932
 
 
933
    def _namespace_date(self, revs, revision):
 
934
        assert revision.startswith('date:')
 
935
        import datetime
 
936
        # Spec for date revisions:
 
937
        #   date:value
 
938
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
939
        #   it can also start with a '+/-/='. '+' says match the first
 
940
        #   entry after the given date. '-' is match the first entry before the date
 
941
        #   '=' is match the first entry after, but still on the given date.
 
942
        #
 
943
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
944
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
945
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
946
        #       May 13th, 2005 at 0:00
 
947
        #
 
948
        #   So the proper way of saying 'give me all entries for today' is:
 
949
        #       -r {date:+today}:{date:-tomorrow}
 
950
        #   The default is '=' when not supplied
 
951
        val = revision[5:]
 
952
        match_style = '='
 
953
        if val[:1] in ('+', '-', '='):
 
954
            match_style = val[:1]
 
955
            val = val[1:]
 
956
 
 
957
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
 
958
        if val.lower() == 'yesterday':
 
959
            dt = today - datetime.timedelta(days=1)
 
960
        elif val.lower() == 'today':
 
961
            dt = today
 
962
        elif val.lower() == 'tomorrow':
 
963
            dt = today + datetime.timedelta(days=1)
 
964
        else:
 
965
            import re
 
966
            # This should be done outside the function to avoid recompiling it.
 
967
            _date_re = re.compile(
 
968
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
969
                    r'(,|T)?\s*'
 
970
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
971
                )
 
972
            m = _date_re.match(val)
 
973
            if not m or (not m.group('date') and not m.group('time')):
 
974
                raise BzrError('Invalid revision date %r' % revision)
 
975
 
 
976
            if m.group('date'):
 
977
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
978
            else:
 
979
                year, month, day = today.year, today.month, today.day
 
980
            if m.group('time'):
 
981
                hour = int(m.group('hour'))
 
982
                minute = int(m.group('minute'))
 
983
                if m.group('second'):
 
984
                    second = int(m.group('second'))
 
985
                else:
 
986
                    second = 0
 
987
            else:
 
988
                hour, minute, second = 0,0,0
 
989
 
 
990
            dt = datetime.datetime(year=year, month=month, day=day,
 
991
                    hour=hour, minute=minute, second=second)
 
992
        first = dt
 
993
        last = None
 
994
        reversed = False
 
995
        if match_style == '-':
 
996
            reversed = True
 
997
        elif match_style == '=':
 
998
            last = dt + datetime.timedelta(days=1)
 
999
 
 
1000
        if reversed:
 
1001
            for i in range(len(revs)-1, -1, -1):
 
1002
                r = self.get_revision(revs[i])
 
1003
                # TODO: Handle timezone.
 
1004
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1005
                if first >= dt and (last is None or dt >= last):
 
1006
                    return i+1
 
1007
        else:
 
1008
            for i in range(len(revs)):
 
1009
                r = self.get_revision(revs[i])
 
1010
                # TODO: Handle timezone.
 
1011
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1012
                if first <= dt and (last is None or dt <= last):
 
1013
                    return i+1
 
1014
    REVISION_NAMESPACES['date:'] = _namespace_date
850
1015
 
851
1016
    def revision_tree(self, revision_id):
852
1017
        """Return Tree for a revision on this branch.
857
1022
        # TODO: refactor this to use an existing revision object
858
1023
        # so we don't need to read it in twice.
859
1024
        if revision_id == None:
860
 
            return EmptyTree()
 
1025
            return EmptyTree(self.get_root_id())
861
1026
        else:
862
1027
            inv = self.get_revision_inventory(revision_id)
863
1028
            return RevisionTree(self.text_store, inv)
877
1042
        from bzrlib.tree import EmptyTree, RevisionTree
878
1043
        r = self.last_patch()
879
1044
        if r == None:
880
 
            return EmptyTree()
 
1045
            return EmptyTree(self.get_root_id())
881
1046
        else:
882
1047
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
883
1048
 
1200
1365
 
1201
1366
    s = hexlify(rand_bytes(8))
1202
1367
    return '-'.join((name, compact_date(time()), s))
 
1368
 
 
1369
 
 
1370
def gen_root_id():
 
1371
    """Return a new tree-root file id."""
 
1372
    return gen_file_id('TREE_ROOT')
 
1373