diff --git a/docs/concepts.rst b/docs/concepts.rst index 4c78286..0ba1b88 100644 --- a/docs/concepts.rst +++ b/docs/concepts.rst @@ -214,6 +214,28 @@ for details on formatting/etc. * ``1.6.0``: feature #22 * ``1.5.1``: bug #20 +* **Bugs listed before the first release are treated as though they have the + 'major' keyword.** This is chiefly because it makes no sense to have a + "bugfix release" as one's first-ever release - you can't fix something that's + not public! Then once the changelog parser passes that initial release, + normal rules start to apply again. + + * Input:: + + * :release:`0.1.1` + * :bug:`3` The feature had bugs :( + * :release:`0.1.0 ` + * :feature:`2` Our first ever feature + * :bug:`1` Explicitly marked bug, even though that is silly + * Implicit issue/entry here (becomes a bug by default) + + * Result: + + * ``0.1.1``: bug #3 only, since it's the only bug after the first + release. + * ``0.1.0``: everything else - the implicit bug, the explicit bug #1, + and the feature #2. + Major releases ============== diff --git a/releases/models.py b/releases/models.py index 9dbebae..5647fb8 100644 --- a/releases/models.py +++ b/releases/models.py @@ -51,6 +51,19 @@ class Issue(nodes.Element): def spec(self): return self.get('spec', None) + def minor_releases(self, lines): + """ + Return all minor release line labels found in ``lines``. + """ + # TODO: yea deffo need a real object for 'lines', heh. E.g. we do a + # very similar test for "do you have any actual releases yet?" + # elsewhere. (This may be fodder for changing how we roll up + # pre-major-release features though...?) + return [ + key for key, value in six.iteritems(lines) + if any(x for x in value if not x.startswith('unreleased')) + ] + def default_spec(self, lines): """ Given the current release-lines structure, return a default Spec. @@ -79,14 +92,11 @@ class Issue(nodes.Element): if True: default = Spec(">={0}".format(max(lines.keys()))) else: - # TODO: yea deffo need a real object for 'lines', heh. E.g. we do a - # very similar test for "do you have any actual releases yet?" - # elsewhere. (This may be fodder for changing how we roll up - # pre-major-release features though...?) - default = Spec(">={0}".format(max( - key for key, value in six.iteritems(lines) - if any(x for x in value if not x.startswith('unreleased')) - ))) + # Can only meaningfully limit to minor release buckets if they + # actually exist yet. + buckets = self.minor_releases(lines) + if buckets: + default = Spec(">={0}".format(max(buckets))) return default def add_to_lines(self, lines): @@ -121,7 +131,13 @@ class Issue(nodes.Element): # and only exists for features to go into. if bugfix_buckets: buckets.append('unreleased_bugfix') - if self.is_featurelike or self.backported: + # Obtain list of minor releases to check for "haven't had ANY + # releases yet" corner case, in which case ALL issues get thrown in + # unreleased_feature for the first release to consume. + # NOTE: assumes first release is a minor or major one, + # but...really? why would your first release be a bugfix one?? + no_releases = not self.minor_releases(lines) + if self.is_featurelike or self.backported or no_releases: buckets.append('unreleased_feature') # Now that we know which buckets are appropriate, add ourself to # all of them. TODO: or just...do it above...instead... diff --git a/tests/organization.py b/tests/organization.py index f89e9ae..e01f9ca 100644 --- a/tests/organization.py +++ b/tests/organization.py @@ -384,6 +384,22 @@ class organization(Spec): # TODO: consider removing that entirely; arguably needing it is a bug? expect_releases(entries, expected, skip_initial=True) + def all_bugs_before_first_release_act_featurelike(self): + b1 = b(1) + f2 = f(2) + b3 = b(3) + implicit = list_item('', paragraph('', '', raw('', 'whatever'))) + changelog = changelog2dict(releases( + '0.1.1', b3, '0.1.0', f2, b1, implicit, + skip_initial=True + )) + first = changelog['0.1.0'] + second = changelog['0.1.1'] + assert b1 in first + assert f2 in first + eq_(len(first), 3) # Meh, hard to assert about the implicit one + eq_(second, [b3]) + def specs_and_keywords_play_together_nicely(self): b1 = b(1) b2 = b(2, major=True, spec='1.0+')