summaryrefslogtreecommitdiff
path: root/linaropy/rn/rnseries.py
blob: a594d684ce51504d33e2421e412f836cb98a7c92 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
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.  Potentially also
    #           an existing Clone instance.
    # @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.')
            if isinstance(rnrepo, Clone):
                self.rn_clone = rnrepo
            else:
                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.create_template(
                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 find_series(self, track, series, headless=False):
        """
        Find the series that this one should be derived from.

        This isn't always simply deriving from the template.  If this is not
        the first release/candidate in a series then the release notes should
        be a continuation of the previous series.

        Parameters
        ----------
        headless=False : bool
        """
        return series

    def edit_series_readmes(self, series, headless=False):

        if not self.rn_series:
            remote_track = self.rn_clone.remote_branchname(
                series.branchname())
            
            self.rn_series = Workdir(
                proj=self.proj,
                clone=self.rn_clone,
                workdir=series.shorttype(),
                track=remote_track,
                branchname=series.branchname())

        if not headless:
            answer = yninput(
                "Would you like to update the %s README.textile.series"
                " file?" % series.longlowertype(), "N")
            if answer == "y":
                self.rn_series.edit(self._series)

        return self.rn_series.commit(
            "Added NEWS items for %s." % series.branchname())

    def update_series_readmes(self, track_series, next_series, 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.

        """
        # 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 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 isinstance(next_series, LinaroSeries):
            raise TypeError("Input variable next_series not of type Series.")

        self.track_series = track_series
        self.next_series = next_series

        # 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, 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()