~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-07-18 13:12:43 UTC
  • Revision ID: mbp@sourcefrog.net-20050718131243-44532527fd065b31
- update convertinv to work with current weave code

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
 
302
307
            os.mkdir(self.controlfilename(d))
303
308
        for f in ('revision-history', 'merged-patches',
304
309
                  'pending-merged-patches', 'branch-name',
305
 
                  'branch-lock'):
 
310
                  'branch-lock',
 
311
                  'pending-merges'):
306
312
            self.controlfile(f, 'w').write('')
307
313
        mutter('created control directory in ' + self.base)
308
314
 
309
 
        pack_xml(Inventory(), self.controlfile('inventory','w'))
 
315
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
310
316
 
311
317
 
312
318
    def _check_format(self):
327
333
                           ['use a different bzr version',
328
334
                            'or remove the .bzr directory and "bzr init" again'])
329
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
330
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)
331
352
 
332
353
    def read_working_inventory(self):
333
354
        """Read the working inventory."""
460
481
            # use inventory as it was in that revision
461
482
            file_id = tree.inventory.path2id(file)
462
483
            if not file_id:
463
 
                raise BzrError("%r is not present in revision %d" % (file, revno))
 
484
                raise BzrError("%r is not present in revision %s" % (file, revno))
464
485
            tree.print_file(file_id)
465
486
        finally:
466
487
            self.unlock()
515
536
    # FIXME: this doesn't need to be a branch method
516
537
    def set_inventory(self, new_inventory_list):
517
538
        from bzrlib.inventory import Inventory, InventoryEntry
518
 
        inv = Inventory()
 
539
        inv = Inventory(self.get_root_id())
519
540
        for path, file_id, parent, kind in new_inventory_list:
520
541
            name = os.path.basename(path)
521
542
            if name == "":
543
564
        return self.working_tree().unknowns()
544
565
 
545
566
 
546
 
    def append_revision(self, revision_id):
 
567
    def append_revision(self, *revision_ids):
547
568
        from bzrlib.atomicfile import AtomicFile
548
569
 
549
 
        mutter("add {%s} to revision-history" % revision_id)
550
 
        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)
551
575
 
552
576
        f = AtomicFile(self.controlfilename('revision-history'))
553
577
        try:
606
630
 
607
631
    def get_revision_inventory(self, revision_id):
608
632
        """Return inventory of a past revision."""
 
633
        # bzr 0.0.6 imposes the constraint that the inventory_id
 
634
        # must be the same as its revision, so this is trivial.
609
635
        if revision_id == None:
610
636
            from bzrlib.inventory import Inventory
611
 
            return Inventory()
 
637
            return Inventory(self.get_root_id())
612
638
        else:
613
 
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
 
639
            return self.get_inventory(revision_id)
614
640
 
615
641
 
616
642
    def revision_history(self):
833
859
        commit(self, *args, **kw)
834
860
        
835
861
 
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
 
 
 
862
    def lookup_revision(self, revision):
 
863
        """Return the revision identifier for a given revision information."""
 
864
        revno, info = self.get_revision_info(revision)
 
865
        return info
 
866
 
 
867
    def get_revision_info(self, revision):
 
868
        """Return (revno, revision id) for revision identifier.
 
869
 
 
870
        revision can be an integer, in which case it is assumed to be revno (though
 
871
            this will translate negative values into positive ones)
 
872
        revision can also be a string, in which case it is parsed for something like
 
873
            'date:' or 'revid:' etc.
 
874
        """
 
875
        if revision is None:
 
876
            return 0, None
 
877
        revno = None
 
878
        try:# Convert to int if possible
 
879
            revision = int(revision)
 
880
        except ValueError:
 
881
            pass
 
882
        revs = self.revision_history()
 
883
        if isinstance(revision, int):
 
884
            if revision == 0:
 
885
                return 0, None
 
886
            # Mabye we should do this first, but we don't need it if revision == 0
 
887
            if revision < 0:
 
888
                revno = len(revs) + revision + 1
 
889
            else:
 
890
                revno = revision
 
891
        elif isinstance(revision, basestring):
 
892
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
 
893
                if revision.startswith(prefix):
 
894
                    revno = func(self, revs, revision)
 
895
                    break
 
896
            else:
 
897
                raise BzrError('No namespace registered for string: %r' % revision)
 
898
 
 
899
        if revno is None or revno <= 0 or revno > len(revs):
 
900
            raise BzrError("no such revision %s" % revision)
 
901
        return revno, revs[revno-1]
 
902
 
 
903
    def _namespace_revno(self, revs, revision):
 
904
        """Lookup a revision by revision number"""
 
905
        assert revision.startswith('revno:')
 
906
        try:
 
907
            return int(revision[6:])
 
908
        except ValueError:
 
909
            return None
 
910
    REVISION_NAMESPACES['revno:'] = _namespace_revno
 
911
 
 
912
    def _namespace_revid(self, revs, revision):
 
913
        assert revision.startswith('revid:')
 
914
        try:
 
915
            return revs.index(revision[6:]) + 1
 
916
        except ValueError:
 
917
            return None
 
918
    REVISION_NAMESPACES['revid:'] = _namespace_revid
 
919
 
 
920
    def _namespace_last(self, revs, revision):
 
921
        assert revision.startswith('last:')
 
922
        try:
 
923
            offset = int(revision[5:])
 
924
        except ValueError:
 
925
            return None
 
926
        else:
 
927
            if offset <= 0:
 
928
                raise BzrError('You must supply a positive value for --revision last:XXX')
 
929
            return len(revs) - offset + 1
 
930
    REVISION_NAMESPACES['last:'] = _namespace_last
 
931
 
 
932
    def _namespace_tag(self, revs, revision):
 
933
        assert revision.startswith('tag:')
 
934
        raise BzrError('tag: namespace registered, but not implemented.')
 
935
    REVISION_NAMESPACES['tag:'] = _namespace_tag
 
936
 
 
937
    def _namespace_date(self, revs, revision):
 
938
        assert revision.startswith('date:')
 
939
        import datetime
 
940
        # Spec for date revisions:
 
941
        #   date:value
 
942
        #   value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
 
943
        #   it can also start with a '+/-/='. '+' says match the first
 
944
        #   entry after the given date. '-' is match the first entry before the date
 
945
        #   '=' is match the first entry after, but still on the given date.
 
946
        #
 
947
        #   +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
 
948
        #   -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
 
949
        #   =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
 
950
        #       May 13th, 2005 at 0:00
 
951
        #
 
952
        #   So the proper way of saying 'give me all entries for today' is:
 
953
        #       -r {date:+today}:{date:-tomorrow}
 
954
        #   The default is '=' when not supplied
 
955
        val = revision[5:]
 
956
        match_style = '='
 
957
        if val[:1] in ('+', '-', '='):
 
958
            match_style = val[:1]
 
959
            val = val[1:]
 
960
 
 
961
        today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
 
962
        if val.lower() == 'yesterday':
 
963
            dt = today - datetime.timedelta(days=1)
 
964
        elif val.lower() == 'today':
 
965
            dt = today
 
966
        elif val.lower() == 'tomorrow':
 
967
            dt = today + datetime.timedelta(days=1)
 
968
        else:
 
969
            import re
 
970
            # This should be done outside the function to avoid recompiling it.
 
971
            _date_re = re.compile(
 
972
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
 
973
                    r'(,|T)?\s*'
 
974
                    r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
 
975
                )
 
976
            m = _date_re.match(val)
 
977
            if not m or (not m.group('date') and not m.group('time')):
 
978
                raise BzrError('Invalid revision date %r' % revision)
 
979
 
 
980
            if m.group('date'):
 
981
                year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
 
982
            else:
 
983
                year, month, day = today.year, today.month, today.day
 
984
            if m.group('time'):
 
985
                hour = int(m.group('hour'))
 
986
                minute = int(m.group('minute'))
 
987
                if m.group('second'):
 
988
                    second = int(m.group('second'))
 
989
                else:
 
990
                    second = 0
 
991
            else:
 
992
                hour, minute, second = 0,0,0
 
993
 
 
994
            dt = datetime.datetime(year=year, month=month, day=day,
 
995
                    hour=hour, minute=minute, second=second)
 
996
        first = dt
 
997
        last = None
 
998
        reversed = False
 
999
        if match_style == '-':
 
1000
            reversed = True
 
1001
        elif match_style == '=':
 
1002
            last = dt + datetime.timedelta(days=1)
 
1003
 
 
1004
        if reversed:
 
1005
            for i in range(len(revs)-1, -1, -1):
 
1006
                r = self.get_revision(revs[i])
 
1007
                # TODO: Handle timezone.
 
1008
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1009
                if first >= dt and (last is None or dt >= last):
 
1010
                    return i+1
 
1011
        else:
 
1012
            for i in range(len(revs)):
 
1013
                r = self.get_revision(revs[i])
 
1014
                # TODO: Handle timezone.
 
1015
                dt = datetime.datetime.fromtimestamp(r.timestamp)
 
1016
                if first <= dt and (last is None or dt <= last):
 
1017
                    return i+1
 
1018
    REVISION_NAMESPACES['date:'] = _namespace_date
847
1019
 
848
1020
    def revision_tree(self, revision_id):
849
1021
        """Return Tree for a revision on this branch.
854
1026
        # TODO: refactor this to use an existing revision object
855
1027
        # so we don't need to read it in twice.
856
1028
        if revision_id == None:
857
 
            return EmptyTree()
 
1029
            return EmptyTree(self.get_root_id())
858
1030
        else:
859
1031
            inv = self.get_revision_inventory(revision_id)
860
1032
            return RevisionTree(self.text_store, inv)
874
1046
        from bzrlib.tree import EmptyTree, RevisionTree
875
1047
        r = self.last_patch()
876
1048
        if r == None:
877
 
            return EmptyTree()
 
1049
            return EmptyTree(self.get_root_id())
878
1050
        else:
879
1051
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
880
1052
 
1039
1211
                f.close()
1040
1212
 
1041
1213
 
 
1214
    def pending_merges(self):
 
1215
        """Return a list of pending merges.
 
1216
 
 
1217
        These are revisions that have been merged into the working
 
1218
        directory but not yet committed.
 
1219
        """
 
1220
        cfn = self.controlfilename('pending-merges')
 
1221
        if not os.path.exists(cfn):
 
1222
            return []
 
1223
        p = []
 
1224
        for l in self.controlfile('pending-merges', 'r').readlines():
 
1225
            p.append(l.rstrip('\n'))
 
1226
        return p
 
1227
 
 
1228
 
 
1229
    def add_pending_merge(self, revision_id):
 
1230
        from bzrlib.revision import validate_revision_id
 
1231
 
 
1232
        validate_revision_id(revision_id)
 
1233
 
 
1234
        p = self.pending_merges()
 
1235
        if revision_id in p:
 
1236
            return
 
1237
        p.append(revision_id)
 
1238
        self.set_pending_merges(p)
 
1239
 
 
1240
 
 
1241
    def set_pending_merges(self, rev_list):
 
1242
        from bzrlib.atomicfile import AtomicFile
 
1243
        self.lock_write()
 
1244
        try:
 
1245
            f = AtomicFile(self.controlfilename('pending-merges'))
 
1246
            try:
 
1247
                for l in rev_list:
 
1248
                    print >>f, l
 
1249
                f.commit()
 
1250
            finally:
 
1251
                f.close()
 
1252
        finally:
 
1253
            self.unlock()
 
1254
 
 
1255
 
1042
1256
 
1043
1257
class ScratchBranch(Branch):
1044
1258
    """Special test class: a branch that cleans up after itself.
1155
1369
 
1156
1370
    s = hexlify(rand_bytes(8))
1157
1371
    return '-'.join((name, compact_date(time()), s))
 
1372
 
 
1373
 
 
1374
def gen_root_id():
 
1375
    """Return a new tree-root file id."""
 
1376
    return gen_file_id('TREE_ROOT')
 
1377