~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-06-28 05:33:40 UTC
  • Revision ID: mbp@sourcefrog.net-20050628053340-ea73b03fbcde9c46
- Remove XMLMixin class in favour of simple pack_xml, unpack_xml functions
  called as needed.  

- Avoid importing xml and ElementTree library unless needed.

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
 
 
158
153
    def __init__(self, base, init=False, find_root=True):
159
154
        """Create new branch object at a particular location.
160
155
 
307
302
            os.mkdir(self.controlfilename(d))
308
303
        for f in ('revision-history', 'merged-patches',
309
304
                  'pending-merged-patches', 'branch-name',
310
 
                  'branch-lock',
311
 
                  'pending-merges'):
 
305
                  'branch-lock'):
312
306
            self.controlfile(f, 'w').write('')
313
307
        mutter('created control directory in ' + self.base)
314
308
 
315
 
        pack_xml(Inventory(gen_root_id()), self.controlfile('inventory','w'))
 
309
        pack_xml(Inventory(), self.controlfile('inventory','w'))
316
310
 
317
311
 
318
312
    def _check_format(self):
333
327
                           ['use a different bzr version',
334
328
                            'or remove the .bzr directory and "bzr init" again'])
335
329
 
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
340
330
 
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)
352
331
 
353
332
    def read_working_inventory(self):
354
333
        """Read the working inventory."""
361
340
            # ElementTree does its own conversion from UTF-8, so open in
362
341
            # binary.
363
342
            inv = unpack_xml(Inventory,
364
 
                             self.controlfile('inventory', 'rb'))
 
343
                                  self.controlfile('inventory', 'rb'))
365
344
            mutter("loaded inventory of %d items in %f"
366
345
                   % (len(inv), time() - before))
367
346
            return inv
481
460
            # use inventory as it was in that revision
482
461
            file_id = tree.inventory.path2id(file)
483
462
            if not file_id:
484
 
                raise BzrError("%r is not present in revision %s" % (file, revno))
 
463
                raise BzrError("%r is not present in revision %d" % (file, revno))
485
464
            tree.print_file(file_id)
486
465
        finally:
487
466
            self.unlock()
536
515
    # FIXME: this doesn't need to be a branch method
537
516
    def set_inventory(self, new_inventory_list):
538
517
        from bzrlib.inventory import Inventory, InventoryEntry
539
 
        inv = Inventory(self.get_root_id())
 
518
        inv = Inventory()
540
519
        for path, file_id, parent, kind in new_inventory_list:
541
520
            name = os.path.basename(path)
542
521
            if name == "":
564
543
        return self.working_tree().unknowns()
565
544
 
566
545
 
567
 
    def append_revision(self, *revision_ids):
 
546
    def append_revision(self, revision_id):
568
547
        from bzrlib.atomicfile import AtomicFile
569
548
 
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)
 
549
        mutter("add {%s} to revision-history" % revision_id)
 
550
        rev_history = self.revision_history() + [revision_id]
575
551
 
576
552
        f = AtomicFile(self.controlfilename('revision-history'))
577
553
        try:
630
606
 
631
607
    def get_revision_inventory(self, revision_id):
632
608
        """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.
635
609
        if revision_id == None:
636
610
            from bzrlib.inventory import Inventory
637
 
            return Inventory(self.get_root_id())
 
611
            return Inventory()
638
612
        else:
639
 
            return self.get_inventory(revision_id)
 
613
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
640
614
 
641
615
 
642
616
    def revision_history(self):
806
780
        True
807
781
        """
808
782
        from bzrlib.progress import ProgressBar
 
783
        try:
 
784
            set
 
785
        except NameError:
 
786
            from sets import Set as set
809
787
 
810
788
        pb = ProgressBar()
811
789
 
855
833
        commit(self, *args, **kw)
856
834
        
857
835
 
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
 
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
 
1015
847
 
1016
848
    def revision_tree(self, revision_id):
1017
849
        """Return Tree for a revision on this branch.
1022
854
        # TODO: refactor this to use an existing revision object
1023
855
        # so we don't need to read it in twice.
1024
856
        if revision_id == None:
1025
 
            return EmptyTree(self.get_root_id())
 
857
            return EmptyTree()
1026
858
        else:
1027
859
            inv = self.get_revision_inventory(revision_id)
1028
860
            return RevisionTree(self.text_store, inv)
1042
874
        from bzrlib.tree import EmptyTree, RevisionTree
1043
875
        r = self.last_patch()
1044
876
        if r == None:
1045
 
            return EmptyTree(self.get_root_id())
 
877
            return EmptyTree()
1046
878
        else:
1047
879
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
1048
880
 
1207
1039
                f.close()
1208
1040
 
1209
1041
 
1210
 
    def pending_merges(self):
1211
 
        """Return a list of pending merges.
1212
 
 
1213
 
        These are revisions that have been merged into the working
1214
 
        directory but not yet committed.
1215
 
        """
1216
 
        cfn = self.controlfilename('pending-merges')
1217
 
        if not os.path.exists(cfn):
1218
 
            return []
1219
 
        p = []
1220
 
        for l in self.controlfile('pending-merges', 'r').readlines():
1221
 
            p.append(l.rstrip('\n'))
1222
 
        return p
1223
 
 
1224
 
 
1225
 
    def add_pending_merge(self, revision_id):
1226
 
        from bzrlib.revision import validate_revision_id
1227
 
 
1228
 
        validate_revision_id(revision_id)
1229
 
 
1230
 
        p = self.pending_merges()
1231
 
        if revision_id in p:
1232
 
            return
1233
 
        p.append(revision_id)
1234
 
        self.set_pending_merges(p)
1235
 
 
1236
 
 
1237
 
    def set_pending_merges(self, rev_list):
1238
 
        from bzrlib.atomicfile import AtomicFile
1239
 
        self.lock_write()
1240
 
        try:
1241
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1242
 
            try:
1243
 
                for l in rev_list:
1244
 
                    print >>f, l
1245
 
                f.commit()
1246
 
            finally:
1247
 
                f.close()
1248
 
        finally:
1249
 
            self.unlock()
1250
 
 
1251
 
 
1252
1042
 
1253
1043
class ScratchBranch(Branch):
1254
1044
    """Special test class: a branch that cleans up after itself.
1365
1155
 
1366
1156
    s = hexlify(rand_bytes(8))
1367
1157
    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