import unittest import logging import os from sh import ls from shutil import copyfile from ..proj import Proj from ..cd import cd from ..git.clone import Clone from ..git.workdir import Workdir from ..series import Series from linaroseries import LinaroSeries from linaroseries import linaroSeriesFromBranchname from template import RNTemplate from ..rninput import yninput from ..colors import * # Abstract base class for a release-notes series # Every RNSeries has a templateRN instance and a workdir. The template # instance is used for the base template. class RNSeries(object): # rnremote=u'http://git.linaro.org/toolchain/release-notes.git' # use ssh:// so that we can push to the remote. rnremote = u'ssh://git@git.linaro.org/toolchain/release-notes.git' _series = 'components/toolchain/binaries/README.textile.series' # @rnrepo - path to the existing releases notes repository if there is one. # If there isn't one a new one will be cloned. # @track_series - an instance of a LinaroSeries to track. # @next_series - an instance of a LinaroSeries that is the next series. def __init__(self, proj, rnrepo=None, track_series=None, next_series=None, headless=False): self.proj = proj # Create the release-notes repository clone. The Clone constructor # will throw an exception if proj is not a Proj. That is an # unrecoverable error so don't catch it here. The Clone constructor # will also throw and exception if rnrepo is not a string. if rnrepo: logging.info('RNSeries() clone already exists. Using existing.') self.rn_clone = Clone(proj, clonedir=rnrepo) else: self.rn_clone = Clone(proj, remote=RNSeries.rnremote) # TODO: Write a testcase that exposes this. if not track_series: raise TypeError("Input variable track_series is required.") # TODO: Write a testcase that exposes this. if not isinstance(track_series, LinaroSeries): raise TypeError("Input variable track_series not of type Series.") # TODO: Write a testcase that exposes this. if not next_series: raise TypeError("Input variable next_series is required.") # TODO: Write a testcase that exposes this. if not isinstance(next_series, LinaroSeries): raise TypeError("Input variable next_series not of type Series.") self.track_series = track_series self.next_series = next_series # Default to None, as this is set in update_template_readmes self.rn_template = None self.rn_series = None def update_templ_readmes(self, headless=False): """ Prompt the user to update the Template README.textile files. The first time this is called it creates the Template repository. This must be called before update_rnseries_reamdes(). Parameters ---------- headless=False : bool Automatically decline to edit any of the README.textile files. This is useful for automated testing. If headless==True then the template readmes will not be updated. Only the template repository will be created. """ templ_branchname = self.next_series.shorttype() templ_branchname + templ_branchname + "_" + self.next_series.get_dir() if not self.rn_template: logging.info("Creating the RNTemplate instance.") self.rn_template = RNTemplate( self.proj, self.rn_clone, branchname_suffix=templ_branchname) logging.info( "Requesting updates to the TemplateReadme and series files.") self.rn_template.update_readme(headless=headless) self.rn_template.update_series(headless=headless) logging.info( "Commiting README.textile and README.textile.series files to" "template repository.") return self.rn_template.commit() def update_series_readmes(self, headless=False): """ Prompt the user to update the series README.textile.series file. The first time this is called it creates the series workdir repository. The update_template_reamdes() function must be called before this is called. Exceptions ---------- RuntimeError Returned if update_template_readmes() hasn't been called yet. Parameters ---------- headless=False : bool Automatically decline to edit any of the README.textile files. This is useful for automated testing. If headless==True then the template readmes will not be updated. Only the template repository will be created. """ # The RNTemplate instance is necessary as we might need to derive # the SeriesRN from it. if not self.rn_template: raise RuntimeError( "The rn_template doesn't yet exist. Call" " update_template_readmes() first.") remote_track = self.rn_clone.remote_branchname( self.track_series.branchname()) if not self.rn_series: logging.info("Looking for branch %s" % self.track_series.branchname()) # The -rc1 candidate in an RNSeries will always track a snapshot # (per the rules in toNextCandidate()) and snapshots don't have # branches in the release-notes repository. Subsequenct candidates # (-rc2, -rcN) (and releases) will track a previous candidate which # will have a branch in the release-notes repository. For -rc1 # candidate series we need to determine where to derive the # release-notes. if not self.rn_clone.branch_exists(remote_track): # The -rc1 of the first GCC X.1 series needs to have the # release-notes derived from the template README.series.textile. # The -rc1 of subsequent series needs to have the release-notes # derived from the previous release in order to maintain # continuity of the release-notes across every series in the # GCC release. # We look to see if there is a previous release in the series # and if we don't find one we derive from the template. found_previous=None prev_release = self.next_series # Look two years back because that's the length of a supported # release. next_minor = self.next_series.package.version.minor for x in xrange(1,25): # Search for the nearest previous release. We need to # iterate through the minor variations blindly # (unfortunately) because the release process doesn't follow # a strict schedule with regard to date and minor number. prev_release=prev_release.toPreviousMonthsRelease() while True: try: logging.info("looking for previous release branch " "'%s'" % prev_release.branchname()) if self.rn_clone.remote_branch_exists( prev_release.branchname()): logging.info ("previous release branch " "'%s' exists." % prev_release.branchname()) found_previous=prev_release break prev_release.package.version.decrement_minor() except ValueError: break # Early exit if we've found a release. if found_previous: break # Restore the starting 'minor' for the next month. prev_release.package.version.minor=next_minor if not found_previous: track=self.rn_template.getbranch() print_info( "Deriving your release-notes from template branch " "%s%s%s%s" % (BOLD, RED, track, NC)) else: # TODO: if we find a release series we need to increment the # spin an search for that branch to determine whether there # was a release respin. Keep incrementing until we discover # no new spins and then we can select the previously # identified spin. logging.warning("Fixme: If the previous release had a spin " "we aren't yet able to detect it yet.") track=found_previous.branchname() print_info( "Deriving your release-notes from release branch " "%s%s%s%s" % (BOLD, RED, track, NC)) self.rn_series = Workdir( proj=self.proj, clone=self.rn_clone, workdir=self.next_series.shorttype(), track=track, branchname=self.next_series.branchname()) else: print_info( "Deriving your release-notes from branch %s%s%s%s" % (BOLD, RED, remote_track, NC)) self.rn_series = Workdir( proj=self.proj, clone=self.rn_clone, workdir=self.next_series.shorttype(), track=remote_track, branchname=self.next_series.branchname()) self.starting_commit=self.rn_series.rev_list( self.rn_series.getbranch(),1) # TODO: Detect if the user changed the template README.textile or # template README.textile.series and ask if they want to merge the # changes. if not headless: answer = yninput( "Would you like to update the %s README.textile.series" " file?" % self.next_series.longlowertype(), "N") if answer == "y": self.rn_series.edit(self._series) # TODO: If there are multiple commits, the subsequent calls should use # commit --amend. Add an 'amend' parameter to GitRepo::commit(). commits=self.rn_series.rev_list(self.rn_series.getbranch(), versus=self.starting_commit) return self.rn_series.commit( "Added NEWS items for %s." % self.next_series.branchname()) def push_readme_updates(self): pass # TODO: Don't forget to push template and workdir changes. # self.rn_template.pushToBranch("origin/master") # TODO: Should the workdir know where to push to? # self.rn_series.pushToBranch(self.next_series.branchname()) class TestSeriesRN(unittest.TestCase): pass if __name__ == '__main__': logging.basicConfig(level="INFO") unittest.main()