Changeset 886 in tailor


Ignore:
Timestamp:
10/05/05 10:40:19 (8 years ago)
Author:
Aaron Kaplan <kaplan@…>
Hash name:
20051005084019-d21f0-26289ac884f2cb7cb8dc68d8e63c4aff569bbe50
Message:

Recognize CVS tags

File:
1 edited

Legend:

Unmodified
Added
Removed
  • vcpx/cvs.py

    r885 r886  
    4646    return cmp(r1, r2) 
    4747 
    48  
    49 def changesets_from_cvslog(log, module): 
     48def cvs_revs_same_branch(rev1, rev2): 
     49    """True iff the two normalized revision numbers are on the same branch.""" 
     50 
     51    # Odd-length revisions are branch numbers, even-length ones 
     52    # are revision numbers. 
     53 
     54    # Two branch numbers can't be on the same branch unless they're identical. 
     55    if len(rev1) % 2 and len(rev2) % 2: 
     56        return rev1 == rev2 
     57 
     58    # Two revision numbers are on the same branch if they 
     59    # agree up to the last number. 
     60    if len(rev1) % 2 == 0 and len(rev2) % 2 == 0: 
     61        return rev1[0:-1] == rev2[0:-1] 
     62 
     63    # One branch number, one revision number.  If by removing the last number 
     64    # of one you get the other, then they're on the same branch, regardless of 
     65    # which is longer.  E.g. revision 1.2 is the root of the branch 1.2.2; 
     66    # revision 1.2.2.2 is directly on the branch 1.2.2. 
     67    if rev1[0:-1] == rev2: 
     68        return True 
     69 
     70    if rev2[0:-1] == rev1: 
     71        return True 
     72 
     73    return False 
     74 
     75def is_branch(rev): 
     76    """True iff the given (normalized) revision number is a branch number""" 
     77    if len(rev) % 2: 
     78        return True 
     79 
     80def rev2branch(rev): 
     81    """Return the branch on which this (normalized) revision lies""" 
     82    assert not is_branch(rev) 
     83    return rev[0:-1] 
     84 
     85 
     86def changesets_from_cvslog(log, module, branch, entries, since): 
    5087    """ 
    5188    Parse CVS log. 
     
    5491    from datetime import timedelta 
    5592 
    56     collected = ChangeSetCollector(log, module) 
     93    collected = ChangeSetCollector(log, module, branch, entries, since) 
    5794    collapsed = [] 
    5895 
     
    69106        if (last and last.author == cs.author and last.log == cs.log and 
    70107            abs(lastts - cs.date) < threshold and 
     108            not last.tags and 
    71109            not [e for e in cs.entries 
    72110                 if e.name in [n.name for n in last.entries 
     
    125163    inter_sep = '=' * 77 + '\n' 
    126164 
    127     def __init__(self, log, module): 
     165    def __init__(self, log, module, branch, entries, since): 
    128166        """ 
    129167        Initialize a ChangeSetCollector instance. 
     
    141179        """The CVS module name.""" 
    142180 
    143         self.__parseCvsLog() 
     181        self.__parseCvsLog(entries, since, branch) 
    144182 
    145183    def __iter__(self): 
     
    262300        return (date, author, changelog, entry, rev, state, newentry) 
    263301 
    264     def __parseCvsLog(self): 
     302    def __parseCvsLog(self, entries, since, branch): 
    265303        """Parse a complete CVS log.""" 
    266304 
     305        from changes import Changeset 
    267306        from os.path import split, join 
    268307        import sre 
     308        from datetime import timedelta 
     309        from time import strptime 
     310        from datetime import datetime 
    269311 
    270312        revcount_regex = sre.compile('\\bselected revisions:\\s*(\\d+)\\b') 
     
    272314        self.__currentdir = None 
    273315 
     316        file2rev2tags = {} 
     317        tagcounts = {} 
     318        branchnum = None 
    274319        while 1: 
    275320            l = self.__readline() 
     
    284329 
    285330            entry = join(self.__currentdir, split(l[10:-1])[1][:-2]) 
     331            while l and not l.startswith('head: '): 
     332                l = self.__readline() 
     333            assert l, "Missed 'head:' line" 
     334            if branch is None: 
     335                branchnum = normalize_cvs_rev(l[6:-1]) 
     336                branchnum = rev2branch(branchnum) 
     337 
     338            while l and not l == 'symbolic names:\n': 
     339                l = self.__readline() 
     340 
     341            assert l, "Missed 'symbolic names:' line" 
     342 
     343            l = self.__readline() 
     344            rev2tags = {} 
     345            while l.startswith('\t'): 
     346                tag,revision = l[1:-1].split(': ') 
     347                tagcounts[tag] = tagcounts.get(tag,0) + 1 
     348                revision = normalize_cvs_rev(revision) 
     349                rev2tags.setdefault(revision,[]).append(tag) 
     350                if tag == branch: 
     351                    branchnum = revision 
     352 
     353                l = self.__readline() 
     354 
     355            # branchnum may still be None, if this file doesn't exist 
     356            # on the requested branch. 
     357 
     358            # filter out branch tags, and tags for revisions that are 
     359            # on other branches. 
     360            for revision in rev2tags.keys(): 
     361                if is_branch(revision) or \ 
     362                   not branchnum or \ 
     363                   not cvs_revs_same_branch(revision,branchnum): 
     364                    del rev2tags[revision] 
     365 
     366            file2rev2tags[entry] = rev2tags 
    286367 
    287368            expected_revisions = None 
    288             while 1: 
    289                 l = self.__readline() 
    290                 if l in (self.inter_sep, self.intra_sep): 
    291                     break 
    292  
     369            while l not in (self.inter_sep, self.intra_sep): 
    293370                m = revcount_regex.search(l) 
    294371                if m is not None: 
    295372                    expected_revisions = int(m.group(1)) 
     373                l = self.__readline() 
    296374 
    297375            last = previous = None 
     
    328406                      ( expected_revisions, found_revisions ) 
    329407 
    330     # end of __parseCvsLog() 
     408        # Determine the current revision of each live 
     409        # (i.e. non-deleted) entry. 
     410        state = dict(entries.getFileVersions()) 
     411 
     412        # before stepping through changes, see if the initial state is 
     413        # taggable.  If so, add an initial changeset that does nothing 
     414        # but tag, using the date of the last revision tailor imported 
     415        # on its previous run.  There's no way to tell when the tag 
     416        # was really applied, so we don't know if it was seen on the 
     417        # last run or not.  Before applying the tag on the other end, 
     418        # we'll have to check whether it's already been applied. 
     419        tags = self.__getApplicableTags(state, file2rev2tags, tagcounts) 
     420        if tags: 
     421            if since == None: 
     422                # I think this could only happen if the CVS repo was 
     423                # tagged before any files were added to it.  We could 
     424                # probably get a better date by looking at when the 
     425                # files were added, but who cares. 
     426                timestamp = datetime(1900,1,1) 
     427            else: 
     428                # "since" is a revision name read from the state file, 
     429                # which means it was originally generated by 
     430                # getGlobalCVSRevision.  The format string "%Y-%m-%d 
     431                # %H:%M:%S" matches the format generated by the implicit 
     432                # call to timestamp.__str__() in getGlobalCVSRevision. 
     433                y,m,d,hh,mm,ss,d1,d2,d3 = strptime(since, "%Y-%m-%d %H:%M:%S") 
     434                timestamp = datetime(y,m,d,hh,mm,ss) 
     435            author = "unknown tagger" 
     436            changelog = "tag %s %s" % (timestamp, tags) 
     437            key = (timestamp, author, changelog) 
     438            self.changesets[key] = Changeset(_getGlobalCVSRevision(timestamp, 
     439                                                                   author), 
     440                                             timestamp,author,changelog, 
     441                                             tags=tags) 
     442 
     443        # Walk through the changesets, identifying ones that result in 
     444        # a state with a tag.  Add that info to the changeset. 
     445        for cs in self.__iter__(): 
     446            self.__updateState(state, cs) 
     447            cs.tags = self.__getApplicableTags(state, file2rev2tags, tagcounts) 
     448 
     449    def __getApplicableTags(self,state,taginfo,expectedcounts): 
     450        # state:   a dictionary mapping filename->revision 
     451        # 
     452        # taginfo: a two-level dictionary mapping 
     453        #          tagname->revision->list of tags. 
     454        # 
     455        # expectedcounts: a dictionary mapping tagname->number of 
     456        #                 files tagged with that name. 
     457        observedcounts = {} 
     458        possibletags = [] 
     459        for filename, revno in state.iteritems(): 
     460            filetags = taginfo[filename].get(revno,[]) 
     461            if len(possibletags) == 0: 
     462                # first iteration of loop 
     463                possibletags = filetags 
     464 
     465            # Intersection of possibletags and filetags.  I'm 
     466            # avoiding using python sets to preserve python 2.3 
     467            # compatibility. 
     468            possibletags = [t for t in possibletags if t in filetags] 
     469            for t in filetags: 
     470                 observedcounts[t] = observedcounts.get(t,0) + 1 
     471 
     472            if len(possibletags) == 0: 
     473                break 
     474 
     475        # All currently existing files carry the tags in possibletags. 
     476        # But that doesn't mean that the tags correspond to this 
     477        # state--we might need to create additional files before 
     478        # tagging. 
     479        possibletags = [t for t in possibletags if 
     480                        observedcounts[t] == expectedcounts[t]] 
     481 
     482        return possibletags 
     483 
     484    def __updateState(self,state, changeset): 
     485        for e in changeset.entries: 
     486            if e.action_kind in (e.ADDED, e.UPDATED): 
     487                state[e.name] = normalize_cvs_rev(e.new_revision) 
     488            elif e.action_kind == e.DELETED: 
     489                if state.has_key(e.name): 
     490                    del state[e.name] 
     491            elif e.action_kind == e.RENAMED: 
     492                if state.has_key(e.name): 
     493                    del state[e.old_name] 
     494                state[e.name] = normalize_cvs_rev(e.new_revision) 
    331495 
    332496 
     
    355519                                      self.repository.name)) 
    356520 
    357         branch = '' 
     521        branch = None 
    358522        fname = join(self.basedir, 'CVS', 'Tag') 
    359523        if exists(fname): 
     
    362526                branch=tag[1:-1] 
    363527 
    364         cmd = self.repository.command("-f", "-d", "%(repository)s", "rlog", 
    365                                       "-N") 
     528        cmd = self.repository.command("-f", "-d", "%(repository)s", "rlog") 
    366529 
    367530        if not sincerev or sincerev in ("INITIAL", "HEAD"): 
     
    423586 
    424587        log = reader(log) 
    425         return changesets_from_cvslog(log, self.repository.module) 
     588        return changesets_from_cvslog(log, self.repository.module, branch, 
     589                                      CvsEntries(self.repository.rootdir), 
     590                                      since) 
    426591 
    427592    def _checkoutUpstreamRevision(self, revision): 
     
    560725 
    561726        return latest 
     727 
     728    def getFileVersions(self, prefix=''): 
     729        """Return a set of (entry name, version number) pairs.""" 
     730 
     731        from os.path import join 
     732 
     733        pairs = [(prefix+e.filename, normalize_cvs_rev(e.cvs_version)) 
     734                 for e in self.files.values()] 
     735 
     736        for dirname, entries in self.directories.iteritems(): 
     737            pairs += [(prefix+filename, version) 
     738                      for filename, version in 
     739                      entries.getFileVersions("%s/" % dirname)] 
     740 
     741        return pairs 
Note: See TracChangeset for help on using the changeset viewer.