Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: I4d66bc8da65514468dc5d56b9003a639b6f48ac8
This commit is contained in:
Tony Breeds
2017-09-12 16:10:26 -06:00
parent ff2ca65e5c
commit 0f5d0ee6b7
91 changed files with 14 additions and 6756 deletions

View File

@@ -1,7 +0,0 @@
[run]
branch = True
source = reno
omit = reno/openstack/*
[report]
ignore_errors = True

56
.gitignore vendored
View File

@@ -1,56 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
.eggs
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage*
.tox
nosetests.xml
.testrepository
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?
/cover/
/releasenotes/notes/reno.cache

View File

@@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/reno.git

View File

@@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@@ -1,14 +0,0 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
https://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
https://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/reno

View File

@@ -1,4 +0,0 @@
reno Style Commandments
===============================================
Read the OpenStack Style Commandments https://docs.openstack.org/developer/hacking/

176
LICENSE
View File

@@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@@ -1,6 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

14
README Normal file
View File

@@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@@ -1,58 +0,0 @@
=========================================
reno: A New Way to Manage Release Notes
=========================================
Reno is a release notes manager designed with high throughput in mind,
supporting fast distributed development teams without introducing
additional development processes. Our goal is to encourage detailed
and accurate release notes for every release.
Reno uses git to store its data, along side the code being
described. This means release notes can be written when the code
changes are fresh, so no details are forgotten. It also means that
release notes can go through the same review process used for managing
code and other documentation changes.
Reno stores each release note in a separate file to enable a large
number of developers to work on multiple patches simultaneously, all
targeting the same branch, without worrying about merge
conflicts. This cuts down on the need to rebase or otherwise manually
resolve conflicts, and keeps a development team moving quickly.
Reno also supports multiple branches, allowing release notes to be
back-ported from master to maintenance branches together with the
code for bug fixes.
Reno organizes notes into logical groups based on whether they
describe new features, bug fixes, known issues, or other topics of
interest to the user. Contributors categorize individual notes as they
are added, and reno combines them before publishing.
Notes can be styled using reStructuredText directives, and reno's
Sphinx integration makes it easy to incorporate release notes into
automated documentation builds.
Notes are automatically associated with the release version based on
the git tags applied to the repository, so it is not necessary to
track changes manually using a bug tracker or other tool, or to worry
that an important change will be missed when the release notes are
written by hand all at one time, just before a release.
Modifications to notes are incorporated when the notes are shown in
their original location in the history. This feature makes it possible
to correct typos or otherwise fix a published release note after a
release is made, but have the new note content associated with the
original version number. Notes also can be deleted, eliminating them
from future documentation builds.
Project Meta-data
=================
.. .. image:: https://governance.openstack.org/badges/reno.svg
:target: https://governance.openstack.org/reference/tags/index.html
* Free software: Apache license
* Documentation: https://docs.openstack.org/reno/latest/
* Source: https://git.openstack.org/cgit/openstack/reno
* Bugs: https://bugs.launchpad.net/reno
* IRC: #openstack-release on freenode

View File

@@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
# oslosphinx uses reno and reno uses oslosphinx. Make oslosphinx for
# reno optional to break the build cycle
try:
import openstackdocstheme
except:
has_theme = False
else:
has_theme = True
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
# 'sphinx.ext.intersphinx',
'reno.sphinxext',
]
if has_theme:
extensions.append('openstackdocstheme')
html_theme = 'openstackdocs'
# openstackdocstheme options
repository_name = 'openstack/reno'
bug_project = 'reno'
bug_tag = ''
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'reno'
copyright = u'2013, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# Do not warn about non-local image URI
suppress_warnings = ['image.nonlocal_uri']
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = ['static']
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
# intersphinx_mapping = {'http://docs.python.org/': None}

View File

@@ -1,5 +0,0 @@
============
Contributing
============
.. include:: ../../../CONTRIBUTING.rst

View File

@@ -1,12 +0,0 @@
.. include:: ../../README.rst
Contents
========
.. toctree::
:maxdepth: 2
user/index
install/index
contributor/index
releasenotes/index

View File

@@ -1,15 +0,0 @@
============
Installation
============
At the command line::
$ pip install reno
Sphinx Extension
================
To use the Sphinx extension built into reno, install the ``[sphinx]``
extra dependencies::
$ pip install 'reno[sphinx]'

View File

@@ -1,12 +0,0 @@
===============
Release Notes
===============
.. release-notes:: Unreleased
.. release-notes:: Mainline
:branch: origin/master
.. release-notes:: Newton Series
:branch: origin/stable/newton
:earliest-version: 1.9.0

View File

@@ -1,52 +0,0 @@
=====================================
Design Constraints and Requirements
=====================================
Managing release notes for a complex project over a long period of
time with many releases can be time consuming and error prone. Reno
helps automate the hard parts by devising a way to store the notes
inside the git repository where they can be tagged as part of the
release.
We had several design inputs:
* Release notes should be part of the git history, so as fixes in
master are back-ported to older branches the notes can go with the
code change.
* Release notes may need to change over time, as typos are found,
logical errors or confusing language needs to be fixed, or as more
information becomes available (CVE numbers, etc.).
* Release notes should be peer-reviewed, as with other documentation
and code changes.
* Notes are mutable in that a clone today vs a clone tomorrow might
have different release notes about the same change.
* Notes are immutable in that for a given git hash/tag the release
notes will be the same. Tagging a commit will change the version
description but that is all.
* We want to avoid merge issues when shepherding in a lot of
release-note-worthy changes, which we expect to happen on stable
branches always, and at release times on master branches.
* We want writing a release note to be straight-forward.
* We do not want release notes to be custom ordered within a release,
but we do want the ordering to be predictable and consistent.
* We must be able to entirely remove a release note.
* We must not make things progressively slow down to a crawl over
years of usage.
* Release note authors shouldn't need to know any special values for
naming their notes files (i.e., no change id or SHA value that has
special meaning).
* It would be nice if it was somewhat easy to identify the file
containing a release note on a particular topic.
* Release notes should be grouped by type in the output document.
1. New features
2. Known issues
3. Upgrade notes
4. Security fixes
5. Bugs fixes
6. Other
We want to eventually provide the ability to create a release notes
file for a given release and add it to the source distribution for the
project. As a first step, we are going to settle for publishing
release notes in the documentation for a project.

View File

@@ -1,17 +0,0 @@
==========
Examples
==========
Input file
==========
.. literalinclude:: ../../../examples/notes/add-complex-example-6b5927c246456896.yaml
:caption: examples/notes/add-complex-example-6b5927c246456896.yaml
:language: yaml
Rendered
========
.. release-notes::
:relnotessubdir: examples
:earliest-version: 1.0.0

View File

@@ -1,11 +0,0 @@
=================
reno User Guide
=================
.. toctree::
:maxdepth: 2
design
usage
sphinxext
examples

View File

@@ -1,88 +0,0 @@
==================
Sphinx Extension
==================
In addition to the command line tool, reno includes a Sphinx extension
for incorporating release notes for a project in its documentation
automatically.
Enable the extension by adding ``'reno.sphinxext'`` to the
``extensions`` list in the Sphinx project ``conf.py`` file.
.. rst:directive:: release-notes
The ``release-notes`` directive accepts the same inputs as the
``report`` subcommand, and inserts the report inline into the
current document where Sphinx then processes it to create HTML,
PDF, or other output formats.
If the directive has a body, it is used to create a title entry
with ``=`` over and under lines (the typical heading style for the
top-level heading in a document).
Options:
*branch*
The name of the branch to scan. Defaults to the current branch.
*reporoot*
The path to the repository root directory. Defaults to the
directory where ``sphinx-build`` is being run.
*relnotessubdir*
The path under ``reporoot`` where the release notes are. Defaults
to ``releasenotes``.
*notesdir*
The path under ``relnotessubdir`` where the release notes
are. Defaults to ``notes``.
*version*
A comma separated list of versions to include in the notes. The
default is to include all versions found on ``branch``.
*collapse-pre-releases*
A flag indicating that notes attached to pre-release versions
should be incorporated into the notes for the final release,
after the final release is tagged.
*earliest-version*
A string containing the version number of the earliest version to
be included. For example, when scanning a branch, this is
typically set to the version used to create the branch to limit
the output to only versions on that branch.
*ignore-notes*
A string containing a comma-delimited list of filenames or UIDs
for notes that should be ignored by the scanner. It is most
useful to set this when a note is edited on the wrong branch,
making it appear to be part of a release that it is not.
Examples
========
The release notes for the "current" branch, with "Release Notes" as a
title.
.. code-block:: rest
.. release-notes:: Release Notes
The release notes for the "stable/liberty" branch, with a separate
title.
.. code-block:: rest
=======================
Liberty Release Notes
=======================
.. release-notes::
:branch: stable/liberty
The release notes for version "1.0.0".
.. code-block:: rest
.. release-notes:: 1.0.0 Release Notes
:version: 1.0.0

View File

@@ -1,325 +0,0 @@
========
Usage
========
Creating New Release Notes
==========================
The ``reno`` command line tool is used to create a new release note
file in the correct format and with a unique name. The ``new``
subcommand combines a random suffix with a "slug" value to create
the file with a unique name that is easy to identify again later.
::
$ reno new slug-goes-here
Created new notes file in releasenotes/notes/slug-goes-here-95915aaedd3c48d8.yaml
Within OpenStack projects, ``reno`` is often run via tox instead of
being installed globally. For example
::
$ tox -e venv -- reno new slug-goes-here
venv develop-inst-nodeps: /mnt/projects/release-notes-generation/reno
venv runtests: commands[0] | reno new slug-goes-here
Created new notes file in releasenotes/notes/slug-goes-here-95915aaedd3c48d8.yaml
venv: commands succeeded
congratulations :)
$ git status
Untracked files:
(use "git add <file>..." to include in what will be committed)
releasenotes/notes/slug-goes-here-95915aaedd3c48d8.yaml
The ``--edit`` option opens the new note in a text editor.
::
$ reno new slug-goes-here --edit
... Opens the editor set in the EDITOR environment variable, editing the new file ...
Created new notes file in releasenotes/notes/slug-goes-here-95915aaedd3c48d8.yaml
By default, the new note is created under ``./releasenotes/notes``.
The ``--rel-notes-dir`` command-line flag changes the parent directory
(the ``notes`` subdirectory is always appended). It's also possible to
set a custom template to create notes (see `Configuring Reno`_ ).
Editing a Release Note
======================
The note file is a YAML file with several sections. All of the text is
interpreted as having `reStructuredText`_ formatting. The permitted
sections are configurable (see below) but default to the following
list:
prelude
General comments about the release. Prelude sections from all notes in a
release are combined, in note order, to produce a single prelude
introducing that release. This section is always included, regardless
of what sections are configured.
features
A list of new major features in the release.
issues
A list of known issues in the release. For example, if a new driver
is experimental or known to not work in some cases, it should be
mentioned here.
upgrade
A list of upgrade notes in the release. For example, if a database
schema alteration is needed.
deprecations
A list of features, APIs, configuration options to be deprecated in the
release. Deprecations should not be used for something that is removed in the
release, use upgrade section instead. Deprecation should allow time for users
to make necessary changes for the removal to happen in a future release.
critical
A list of *fixed* critical bugs.
security
A list of *fixed* security issues.
fixes
A list of other *fixed* bugs.
other
Other notes that are important but do not fall into any of the given
categories.
Any sections that would be blank should be left out of the note file
entirely.
.. code-block:: yaml
---
prelude: >
Replace this text with content to appear at the
top of the section for this release.
features:
- List new features here, or remove this section.
issues:
- List known issues here, or remove this section.
upgrade:
- List upgrade notes here, or remove this section.
deprecations:
- List deprecation notes here, or remove this section
critical:
- Add critical notes here, or remove this section.
security:
- Add security notes here, or remove this section.
fixes:
- Add normal bug fixes here, or remove this section.
other:
- Add other notes here, or remove this section.
Note File Syntax
----------------
Release notes may include embedded `reStructuredText`_, including simple
inline markup like emphasis and pre-formatted text as well as complex
body structures such as nested lists and tables. To use these
formatting features, the note must be escaped from the YAML parser.
The default template sets up the ``prelude`` section to use ``>`` so
that line breaks in the text are removed. This escaping mechanism is
not needed for the bullet items in the other sections of the template.
To escape the text of any section and *retain* the newlines, prefix
the value with ``|``. For example:
.. literalinclude:: ../../../examples/notes/add-complex-example-6b5927c246456896.yaml
:language: yaml
See :doc:`examples` for the rendered version of the note.
.. _reStructuredText: http://www.sphinx-doc.org/en/stable/rest.html
Generating a Report
===================
Run ``reno report <path-to-git-repository>`` to generate a report
containing the release notes. The ``--branch`` argument can be used to
generate a report for a specific branch (the default is the branch
that is checked out). To limit the report to a subset of the available
versions on the branch, use the ``--version`` option (it can be
repeated).
Notes are output in the order they are found when scanning the git
history of the branch using topological ordering. This is
deterministic, but not necessarily predictable or mutable.
Checking Notes
==============
Run ``reno lint <path-to-git-repository>`` to test the existing
release notes files against some rules for catching common
mistakes. The command exits with an error code if there are any
mistakes, so it can be used in a build pipeline to force some
correctness.
Configuring Reno
================
Reno looks for an optional config file, either ``config.yaml`` in the release
notes directory or ``reno.yaml`` in the root directory. If the values in the
configuration file do not apply to the command being run, they are ignored. For
example, some reno commands take inputs controlling the branch, earliest
revision, and other common parameters that control which notes are included in
the output. Because they are commonly set options, a configuration file may be
the most convenient way to manage the values consistently.
.. code-block:: yaml
---
branch: master
earliest_version: 12.0.0
collapse_pre_releases: false
stop_at_branch_base: true
sections:
# The prelude section is implicitly included.
- [features, New Features]
- [issues, Known Issues]
- [upgrade, Upgrade Notes]
- [api, API Changes]
- [security, Security Issues]
- [fixes, Bug Fixes]
# Change prelude_section_name to 'release_summary' from default value
# 'prelude'.
prelude_section_name: release_summary
template: |
<template-used-to-create-new-notes>
...
Many of the settings in the configuration file can be overridden by
using command-line switches. For example:
- ``--branch``
- ``--earliest-version``
- ``--collapse-pre-releases``/``--no-collapse-pre-releases``
- ``--ignore-cache``
- ``--stop-at-branch-base``/``--no-stop-at-branch-base``
The following options are configurable:
`notesdir`
The notes subdirectory within the `relnotesdir` where the notes live.
Defaults to ``notes``.
`collapse_pre_releases`
Should pre-release versions be merged into the final release of the same
number (`1.0.0.0a1` notes appear under `1.0.0`).
Defaults to ``True``.
`stop_at_branch_base`
Should the scanner stop at the base of a branch (True) or go ahead and scan
the entire history (False)?
Defaults to ``True``.
`branch`
The git branch to scan. If a stable branch is specified but does not exist,
reno attempts to automatically convert that to an "end-of-life" tag. For
example, ``origin/stable/liberty`` would be converted to ``liberty-eol``.
Defaults to the "current" branch checked out.
`earliest_version`
The earliest version to be included. This is usually the lowest version
number, and is meant to be the oldest version. If unset, all versions will be
scanned.
Defaults to ``None``.
`template`
The template used by reno new to create a note.
`release_tag_re`
The regex pattern used to match the repo tags representing a valid release
version. The pattern is compiled with the verbose and unicode flags enabled.
Defaults to ``((?:[\d.ab]|rc)+)``.
`pre_release_tag_re`
The regex pattern used to check if a valid release version tag is also a
valid pre-release version. The pattern is compiled with the verbose and
unicode flags enabled. The pattern must define a group called `pre_release`
that matches the pre-release part of the tag and any separator, e.g for
pre-release version `12.0.0.0rc1` the default RE pattern will identify
`.0rc1` as the value of the group 'pre_release'.
Defaults to ``(?P<pre_release>\.\d+(?:[ab]|rc)+\d*)$``.
`branch_name_re`
The pattern for names for branches that are relevant when scanning history to
determine where to stop, to find the "base" of a branch. Other branches are
ignored.
Defaults to ``stable/.+``.
`sections`
The identifiers and names of permitted sections in the release notes, in the
order in which the final report will be generated. A prelude section will
always be automatically inserted before the first element of this list.
`prelude_section_name`
The name of the prelude section in the note template. Note that the
value for this must be a single word, but can have underscores. The
value is displayed in titlecase in the report after replacing
underscores with spaces.
Defaults to ``prelude``
`ignore_null_merges`
OpenStack used to use null-merges to bring final release tags from
stable branches back into the master branch. This confuses the
regular traversal because it makes that stable branch appear to be
part of master and/or the later stable branch. This option allows us
to ignore those.
When this option is set to True, any merge commits with no changes
and in which the second or later parent is tagged are considered
"null-merges" that bring the tag information into the current branch
but nothing else.
Defaults to ``True``.
`ignore_notes`
A list of filenames or UIDs for notes that should be ignored by the
reno scanner. It is most useful to set this when a note is edited on
the wrong branch, making it appear to be part of a release that it
is not.
.. warning::
Setting the option in the main configuration file makes it apply
to all branches. To ignore a note in the HTML build, use the
``ignore-notes`` parameter to the ``release-notes`` sphinx
directive.
Debugging
=========
The true location of formatting errors in release notes may be masked
because of the way release notes are included into sphinx documents.
To generate the release notes manually, so that they can be put into a
sphinx document directly for debugging, use the ``report`` command.
.. code-block:: console
$ reno report .
Within OpenStack
================
The OpenStack project maintains separate instructions for configuring
the CI jobs and other project-specific settings used for reno. Refer
to the `Managing Release Notes
<https://docs.openstack.org/project-team-guide/release-management.html#managing-release-notes>`__
section of the Project Team Guide for details.

View File

@@ -1,37 +0,0 @@
---
prelude: |
This paragraph will
retain its newlines
when the value is passed to the
reStructuredText parser, which
will then merge them into
a single paragraph without
breaks.
| These
| lines
| are prefixed
| with | so the reStructuredText
| parser will retain
| the line breaks.
features:
This note is a simple string, and does not retain its
formatting when it is rendered in HTML. rst markup here
may break the YAML parser, since the string is not escaped.
fixes:
- Use YAML lists to add multiple items to the same section.
- Another fix could be listed here.
other:
- |
This bullet item includes a paragraph and a nested list,
which works because the content of the YAML list item
is an escaped string block with reStructuredText formatting.
* list item 1
* list item 2
.. code-block:: text
This example is also rendered
correctly on multiple lines
as a pre-formatted block.

View File

@@ -1,8 +0,0 @@
---
features:
- |
Reno now supports having a ``config.yaml`` file in your release notes
directory. It will search for file in the directory specified by
``--rel-notes-dir`` and parse it. It will apply whatever options are
valid for that particular command. If an option is not relevant to a
particular sub-command, it will not attempt to apply them.

View File

@@ -1,3 +0,0 @@
---
features:
- Added a new section for deprecations that occur during a release

View File

@@ -1,6 +0,0 @@
---
features:
- Add the ability to limit queries by stopping at an "earliest
version". This is intended to be used when scanning a branch, for
example, to stop at a point when the branch was created and not
include all of the history from the parent branch.

View File

@@ -1,5 +0,0 @@
---
features:
- |
Add a ``lint`` command for checking the contents and names of the
release notes files against some basic validation rules.

View File

@@ -1,3 +0,0 @@
---
features:
- Add the ``--verbose``, ``-v``, and ``-q`` options to the command line tool for producing different levels of debug output.

View File

@@ -1,8 +0,0 @@
---
fixes:
- |
Fix a problem with branch references so that it is now possible to
use a local tracking branch name when the branch only exists on
the 'origin' remote. For example, this allows references to
'stable/ocata' when there is no local branch with that name but
there is an 'origin/stable/ocata' branch.

View File

@@ -1,8 +0,0 @@
---
fixes:
- |
Fix a problem caused by failing to process multiple files with the
same UID portion of the filename. Ignore existing cases as long as
there is a corrective patch to remove them. Prevent new cases from
being introduced. See https://bugs.launchpad.net/reno/+bug/1688042
for details.

View File

@@ -1,12 +0,0 @@
---
features:
- |
Explicitly allow reno to scan starting from a tag by specifying the
tag where a branch name would otherwise be used.
- |
Add logic to allow reno to detect a branch that has been marked as
end-of-life using the OpenStack community's process of tagging the
HEAD of a stable/foo branch foo-eol before deleting the
branch. This means that references to "stable/foo" are translated
to "foo-eol" when the branch does not exist, and that Sphinx
directives do not need to be manually updated.

View File

@@ -1,6 +0,0 @@
---
fixes:
- Resolves `a bug <https://bugs.launchpad.net/reno/+bug/1537451>`__
with properly detecting pre-release versions in the existing
history of a repository that resulted in some release notes not
appearing in the report output.

View File

@@ -1,4 +0,0 @@
---
features:
- Add a flag to collapse pre-release notes into their final release,
if the final release tag is present.

View File

@@ -1,15 +0,0 @@
---
features:
- |
Add a configuration option ``branch_name_re`` to hold a regular expression
for choosing "interesting" branches when trying to automatically detect
how far back the scanner should look. The default is ``stable/.+``, which
works for the OpenStack practice of creating branches named after the
stable series of releases.
fixes:
- |
Fixes the logic for determining how far back in history to look when
scanning a given branch. Reno now looks for the base of the "previous"
branch, as determined by looking at branches matching ``branch_name_re``
in lexical order. This may not work if branches are created using
version numbers as their names.

View File

@@ -1,7 +0,0 @@
---
features:
- |
Add a configuration option ``sections`` to hold the list of
permitted section identifiers and corresponding display names.
This also determines the order in which sections are collated.

View File

@@ -1,15 +0,0 @@
---
features:
- |
Add the ability to specify regular expressions to a define a
customised versioning scheme for release tags and pre-release tags.
By default this change supports the current versioning scheme used by
OpenStack.
To customise, update the config.yaml file with the appropriate values.
For example, for tags with versions like ``v1.0.0`` and pre-release
versions like ``v1.0.0rc1`` the following could be added to config.yaml::
release_tag_re: 'v\d\.\d\.\d(rc\d+)?'
pre_release_tag_re: '(?P<pre_release>rc\d+$)'

View File

@@ -1,5 +0,0 @@
---
features:
- Set the default value of the reporoot argument
for all command line programs to "." and make
it an optional parameter.

View File

@@ -1,5 +0,0 @@
---
prelude: >
This release includes a significant rewrite of the internal logic of
reno to access git data through the dulwich library instead of the
git command line porcelain.

View File

@@ -1,7 +0,0 @@
---
prelude: >
This is the first release.
features:
- Creating new notes files with unique names.
- Listing the files with notes related to each release.
- Producing a unified report of all of the notes for a release.

View File

@@ -1,8 +0,0 @@
---
fixes:
- |
Fix a problem with the way reno automatically detects the initial
version in a branch that prevented it from including all of the
notes associated with a release, especially if the branch was
created at a pre-release version number.
`Bug #1652092 <https://bugs.launchpad.net/reno/+bug/1652092>`__

View File

@@ -1,6 +0,0 @@
---
fixes:
- |
Correct a problem with handling deleted release notes that
triggered a TypeError with a message like "Can't mix strings and
bytes in path components"

View File

@@ -1,6 +0,0 @@
---
fixes:
- Fixes `bug 1522153
<https://bugs.launchpad.net/reno/+bug/1522153>`__ so that notes
added in commits that are merged after tags are associated with
the correct version.

View File

@@ -1,3 +0,0 @@
---
fixes:
- Fixed the section used in the report to include the prelude in the output.

View File

@@ -1,5 +0,0 @@
---
fixes:
- Fix `bug 1517175 <https://bugs.launchpad.net/reno/+bug/1517175>`__ to
ensure that all tagged versions are detected and that notes are associated
with the correct version numbers.

View File

@@ -1,8 +0,0 @@
---
fixes:
- |
Fixes a problem with the sphinx extension that caused it to
produce an error if it had a list of versions to include that were
not within the set that seemed to be on the branch because of the
branch-base detection logic. Now if a list of versions is
included, the scan always includes the full history.

View File

@@ -1,5 +0,0 @@
---
features:
Release notes entries may now be made up of single strings. This
simplifies formatting for smaller notes, and eliminates a class of
errors associated with escaping reStructuredText inside YAML lists.

View File

@@ -1,8 +0,0 @@
---
features:
- |
Add a new configuration option ``ignore_notes``. Setting the value
to a list of filenames or UIDs for notes causes the reno scanner
to ignore them. It is most useful to set this when a note is
edited on the wrong branch, making it appear to be part of a
release that it is not.

View File

@@ -1,18 +0,0 @@
---
features:
- |
By default, reno now ignores "null" merge commits that bring in
tags from other threads. The new configuration option
``ignore_null_merges`` controls this behavior. Setting the flag to
False restores the previous behavior in which the null-merge
commits were traversed like any other merge commit.
upgrade:
- |
The new configuration option ``ignore_null_merges`` causes the
scanner to ignore merge commits with no changes when one of the
parents being merged in has a release tag on it.
fixes:
- |
This release fixes a problem with the scanner that may have caused
it to stop scanning a branch prematurely when the tag from another
branch had been merged into the history.

View File

@@ -1,7 +0,0 @@
---
features:
- |
Include the local working copy when scanning the history of the
current branch. Notes files must at least be staged to indicate
that they will eventually be part of the history, but subsequent
changes to the file do not need to also be staged to be seen.

View File

@@ -1,8 +0,0 @@
---
fixes:
- |
Sphinx 1.6.1 now interprets error and warning log messages as
reasons to abort the build when strict mode is enabled. This
release changes the log level for some calls that weren't really
errors to begin with to avoid having Sphinx abort the build
unnecessarily.

View File

@@ -1,8 +0,0 @@
---
features:
- |
Add a ``--no-show-source`` option to the report command to skip
including the note reference file names and SHA information
in comments in the output. This restores the previous format of
the output for cases where it is meant to be read by people directly,
not just converted to HTML.

View File

@@ -1,5 +0,0 @@
---
fixes:
- |
Remove an infinite loop in the traversal algorithm caused by some
null-merge skip situations.

View File

@@ -1,5 +0,0 @@
---
other:
- The oslosphinx dependency for building documentation
is now optional. This breaks a build cycle between
oslosphinx and reno.

View File

@@ -1,7 +0,0 @@
---
features:
- |
reno will now scan for a ``reno.yaml`` file in the root repo directory if a
``config.yaml`` file does not exist in the releasenotes directory. This
allows users to do away with the unnecessary ``notes`` subdirectory in the
releasenotes directory.

View File

@@ -1,4 +0,0 @@
---
features:
- |
Add a ``--title`` option to the report command.

View File

@@ -1,9 +0,0 @@
---
upgrade:
- |
Change the order of the slug and UUID value in the note filename so
the slug comes before the UUID to make tab completion easier to
use.
Older files are still supported, and can be renamed to use
the new style.

View File

@@ -1,8 +0,0 @@
---
features:
- |
The scanner for the "current" branch (usually ``master``) now stops
when it encounters the base of an earlier branch matching the
``branch_name_re`` config option. This results in less history
appearing on the unreleased pages, while still actually showing
the current series and any unreleased notes.

View File

@@ -1,7 +0,0 @@
---
features:
- |
The report output now includes debugging details with the filename
and sha for the version of the content used to indicate where the
content is from to assist with debugging formatting or content
issues.

View File

@@ -1,3 +0,0 @@
---
features:
- Add the sphinx extension for integration with documentation builds.

View File

@@ -1,7 +0,0 @@
---
features:
- Automatically stop scanning branches at the point where they
diverge from master. This avoids having release notes from older
versions, that appear on master before the branch, from showing up
in the versions from the branch. This logic is only applied to
branches created from master.

View File

@@ -1,9 +0,0 @@
---
features:
- Add a new configuration option, stop_at_branch_base, to control
whether or not the scanner stops looking for changes at the point
where a branch diverges from master. The default is True, meaning
that the scanner does stop. A false value means that versions that
appear on master from a point earlier than when the branch was
created will be included when scanning the branch for release
notes.

View File

@@ -1,6 +0,0 @@
---
features:
- |
Reno now supports to set through ``template`` attribute in
``config.yaml`` a custom template which will be used by reno new
to create notes.

View File

@@ -1,5 +0,0 @@
---
features:
- |
Reno now enables with reno new ``--edit`` to create a note and edit it with
your editor (defined with EDITOR environment variable).

View File

@@ -1,13 +0,0 @@
---
features:
- |
Support note entries that span multiple lines using
preformatted syntax in YAML by prefixing the
list entry with ``|``.
For example::
- |
This entry has two paragraphs.
This is the second.

View File

@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import pbr.version
__version__ = pbr.version.VersionInfo(
'reno').version_string()
# Configure a null logger so that if reno is used as a library by an
# application that does not configure logging there are no warnings.
logging.getLogger(__name__).addHandler(logging.NullHandler())

View File

@@ -1,107 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import sys
import yaml
from reno import loader
from reno import scanner
def build_cache_db(conf, versions_to_include):
s = scanner.Scanner(conf)
notes = s.get_notes_by_version()
# Default to including all versions returned by the scanner.
if not versions_to_include:
versions_to_include = list(notes.keys())
# Build a cache data structure including the file contents as well
# as the basic data returned by the scanner.
file_contents = {}
for version in versions_to_include:
for filename, sha in notes[version]:
body = s.get_file_at_commit(filename, sha)
# We want to save the contents of the file, which is YAML,
# inside another YAML file. That looks terribly ugly with
# all of the escapes needed to format it properly as
# embedded YAML, so parse the input and convert it to a
# data structure that can be serialized cleanly.
y = yaml.safe_load(body)
file_contents[filename] = y
cache = {
'notes': [
{'version': k, 'files': v}
for k, v in notes.items()
],
'file-contents': file_contents,
}
return cache
def write_cache_db(conf, versions_to_include,
outfilename=None):
"""Create a cache database file for the release notes data.
Build the cache database from scanning the project history and
write it to a file within the project.
By default, the data is written to the same file the scanner will
try to read when it cannot look at the git history. If outfilename
is given and is '-' the data is written to stdout
instead. Otherwise, if outfilename is given, the data overwrites
the named file.
Return the name of the file created, if any.
"""
if outfilename == '-':
stream = sys.stdout
close_stream = False
elif outfilename:
stream = open(outfilename, 'w')
close_stream = True
else:
outfilename = loader.get_cache_filename(conf)
if not os.path.exists(os.path.dirname(outfilename)):
os.makedirs(os.path.dirname(outfilename))
stream = open(outfilename, 'w')
close_stream = True
try:
cache = build_cache_db(
conf,
versions_to_include=versions_to_include,
)
yaml.safe_dump(
cache,
stream,
allow_unicode=True,
explicit_start=True,
encoding='utf-8',
)
finally:
if close_stream:
stream.close()
return outfilename
def cache_cmd(args, conf):
"Generates a release notes cache"
write_cache_db(
conf=conf,
versions_to_include=args.version,
outfilename=args.output,
)
return

View File

@@ -1,251 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os.path
import yaml
from reno import defaults
LOG = logging.getLogger(__name__)
class Config(object):
_OPTS = {
# The notes subdirectory within the relnotesdir where the
# notes live.
'notesdir': defaults.NOTES_SUBDIR,
# Should pre-release versions be merged into the final release
# of the same number (1.0.0.0a1 notes appear under 1.0.0).
'collapse_pre_releases': True,
# Should the scanner stop at the base of a branch (True) or go
# ahead and scan the entire history (False)?
'stop_at_branch_base': True,
# The git branch to scan. Defaults to the "current" branch
# checked out.
'branch': None,
# The earliest version to be included. This is usually the
# lowest version number, and is meant to be the oldest
# version.
'earliest_version': None,
# The template used by reno new to create a note.
'template': defaults.TEMPLATE.format(defaults.PRELUDE_SECTION_NAME),
# The RE pattern used to match the repo tags representing a valid
# release version. The pattern is compiled with the verbose and unicode
# flags enabled.
'release_tag_re': '''
((?:[\d.ab]|rc)+) # digits, a, b, and rc cover regular and
# pre-releases
''',
# The RE pattern used to check if a valid release version tag is also a
# valid pre-release version. The pattern is compiled with the verbose
# and unicode flags enabled. The pattern must define a group called
# 'pre_release' that matches the pre-release part of the tag and any
# separator, e.g for pre-release version '12.0.0.0rc1' the default RE
# pattern will identify '.0rc1' as the value of the group
# 'pre_release'.
'pre_release_tag_re': '''
(?P<pre_release>\.\d+(?:[ab]|rc)+\d*)$
''',
# The pattern for names for branches that are relevant when
# scanning history to determine where to stop, to find the
# "base" of a branch. Other branches are ignored.
'branch_name_re': 'stable/.+',
# The identifiers and names of permitted sections in the
# release notes, in the order in which the final report will
# be generated. A prelude section will always be automatically
# inserted before the first element of this list.
'sections': [
['features', 'New Features'],
['issues', 'Known Issues'],
['upgrade', 'Upgrade Notes'],
['deprecations', 'Deprecation Notes'],
['critical', 'Critical Issues'],
['security', 'Security Issues'],
['fixes', 'Bug Fixes'],
['other', 'Other Notes'],
],
# The name of the prelude section in the note template. This
# allows users to rename the section to, for example,
# 'release_summary' or 'project_wide_general_announcements',
# which is displayed in titlecase in the report after
# replacing underscores with spaces.
'prelude_section_name': defaults.PRELUDE_SECTION_NAME,
# When this option is set to True, any merge commits with no
# changes and in which the second or later parent is tagged
# are considered "null-merges" that bring the tag information
# into the current branch but nothing else.
#
# OpenStack used to use null-merges to bring final release
# tags from stable branches back into the master branch. This
# confuses the regular traversal because it makes that stable
# branch appear to be part of master and/or the later stable
# branch. This option allows us to ignore those.
'ignore_null_merges': True,
# Note files to be ignored. It's useful to be able to ignore a
# file if it is edited on the wrong branch. Notes should be
# specified by their filename or UID. Setting the value in the
# configuration file makes it apply to all branches.
'ignore_notes': [],
}
@classmethod
def get_default(cls, opt):
"Return the default for an option."
try:
return cls._OPTS[opt]
except KeyError:
raise ValueError('unknown option name %r' % (opt,))
def __init__(self, reporoot, relnotesdir=None):
"""Instantiate a Config object
:param str reporoot:
The root directory of the repository.
:param str relnotesdir:
The directory containing release notes. Defaults to
'releasenotes'.
"""
self.reporoot = reporoot
if relnotesdir is None:
relnotesdir = defaults.RELEASE_NOTES_SUBDIR
self.relnotesdir = relnotesdir
# Initialize attributes from the defaults.
self.override(**self._OPTS)
self._contents = {}
self._load_file()
def _load_file(self):
filenames = [
os.path.join(self.reporoot, self.relnotesdir, 'config.yaml'),
os.path.join(self.reporoot, 'reno.yaml')]
for filename in filenames:
if os.path.isfile(filename):
break
else:
LOG.info('no configuration file in: %s', ', '.join(filenames))
return
try:
with open(filename, 'r') as fd:
self._contents = yaml.safe_load(fd)
except IOError as err:
LOG.warning('did not load config file %s: %s', filename, err)
else:
self.override(**self._contents)
def _rename_prelude_section(self, **kwargs):
key = 'prelude_section_name'
if key in kwargs and kwargs[key] != self._OPTS[key]:
new_prelude_name = kwargs[key]
self.template = defaults.TEMPLATE.format(new_prelude_name)
def override(self, **kwds):
"""Set the values of the named configuration options.
Take the values of the keyword arguments as the current value
of the same option, regardless of whether a value is already
present.
"""
# Replace prelude section name if it has been changed.
self._rename_prelude_section(**kwds)
for n, v in kwds.items():
if n not in self._OPTS:
LOG.warning('ignoring unknown configuration value %r = %r',
n, v)
else:
setattr(self, n, v)
def override_from_parsed_args(self, parsed_args):
"""Set the values of the configuration options from parsed CLI args.
This method assumes that the DEST values for the command line
arguments are named the same as the configuration options.
"""
arg_values = {
o: getattr(parsed_args, o)
for o in self._OPTS.keys()
if hasattr(parsed_args, o)
}
self.override(**arg_values)
@property
def reporoot(self):
return self._reporoot
# Ensure that the 'reporoot' value always only ends in one '/'.
@reporoot.setter
def reporoot(self, value):
self._reporoot = value.rstrip('/') + '/'
@property
def notespath(self):
"""The path in the repo where notes are kept.
.. important::
This does not take ``reporoot`` into account. You need to add this
manually if required.
"""
return os.path.join(self.relnotesdir, self.notesdir)
@property
def options(self):
"""Get all configuration options as a dict.
Returns the actual configuration options after overrides.
"""
options = {o: getattr(self, o) for o in self._OPTS}
return options
# def parse_config_into(parsed_arguments):
# """Parse the user config onto the namespace arguments.
# :param parsed_arguments:
# The result of calling :meth:`argparse.ArgumentParser.parse_args`.
# :type parsed_arguments:
# argparse.Namespace
# """
# config_path = get_config_path(parsed_arguments.relnotesdir)
# config_values = read_config(config_path)
# for key in config_values.keys():
# try:
# getattr(parsed_arguments, key)
# except AttributeError:
# LOG.info('Option "%s" does not apply to this particular command.'
# '. Ignoring...', key)
# continue
# setattr(parsed_arguments, key, config_values[key])
# parsed_arguments._config = config_values

View File

@@ -1,65 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import os
import subprocess
from reno import utils
def _pick_note_file_name(notesdir, slug):
"Pick a unique name in notesdir."
for i in range(50):
newid = utils.get_random_string()
notefilename = os.path.join(notesdir, '%s-%s.yaml' % (slug, newid))
if not os.path.exists(notefilename):
return notefilename
else:
raise ValueError(
'Unable to generate unique random filename '
'in %s after 50 tries' % notesdir,
)
def _make_note_file(filename, template):
notesdir = os.path.dirname(filename)
if not os.path.exists(notesdir):
os.makedirs(notesdir)
with open(filename, 'w') as f:
f.write(template)
def _edit_file(filename):
if 'EDITOR' not in os.environ:
return False
subprocess.call([os.environ['EDITOR'], filename])
return True
def create_cmd(args, conf):
"Create a new release note file from the template."
# NOTE(dhellmann): There is a short race window where we might try
# to pick a name that does not exist, then overwrite the file if
# it is created before we try to write it. This isn't a problem
# because this command is expected to be run by one developer in
# their local git tree, and so there should not be any concurrency
# concern.
slug = args.slug.replace(' ', '-')
filename = _pick_note_file_name(conf.notespath, slug)
_make_note_file(filename, conf.template)
if args.edit and not _edit_file(filename):
print('Was unable to edit the new note. EDITOR environment variable '
'is missing!')
print('Created new notes file in %s' % filename)
return

View File

@@ -1,83 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
RELEASE_NOTES_SUBDIR = 'releasenotes'
NOTES_SUBDIR = 'notes'
PRELUDE_SECTION_NAME = 'prelude'
# This is a format string, so it needs to be formatted wherever it is used.
TEMPLATE = """\
---
{0}: >
Replace this text with content to appear at the top of the section for this
release. All of the prelude content is merged together and then rendered
separately from the items listed in other parts of the file, so the text
needs to be worded so that both the prelude and the other items make sense
when read independently. This may mean repeating some details. Not every
release note requires a prelude. Usually only notes describing major
features or adding release theme details should have a prelude.
features:
- |
List new features here, or remove this section. All of the list items in
this section are combined when the release notes are rendered, so the text
needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
issues:
- |
List known issues here, or remove this section. All of the list items in
this section are combined when the release notes are rendered, so the text
needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
upgrade:
- |
List upgrade notes here, or remove this section. All of the list items in
this section are combined when the release notes are rendered, so the text
needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
deprecations:
- |
List deprecations notes here, or remove this section. All of the list
items in this section are combined when the release notes are rendered, so
the text needs to be worded so that it does not depend on any information
only available in another section, such as the prelude. This may mean
repeating some details.
critical:
- |
Add critical notes here, or remove this section. All of the list items in
this section are combined when the release notes are rendered, so the text
needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
security:
- |
Add security notes here, or remove this section. All of the list items in
this section are combined when the release notes are rendered, so the text
needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
fixes:
- |
Add normal bug fixes here, or remove this section. All of the list items
in this section are combined when the release notes are rendered, so the
text needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
other:
- |
Add other notes here, or remove this section. All of the list items in
this section are combined when the release notes are rendered, so the text
needs to be worded so that it does not depend on any information only
available in another section, such as the prelude. This may mean repeating
some details.
"""

View File

@@ -1,83 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
def _indent_for_list(text, prefix=' '):
"""Indent some text to make it work as a list entry.
Indent all lines except the first with the prefix.
"""
lines = text.splitlines()
return '\n'.join([lines[0]] + [
prefix + l
for l in lines[1:]
]) + '\n'
def format_report(loader, config, versions_to_include, title=None,
show_source=True):
report = []
if title:
report.append('=' * len(title))
report.append(title)
report.append('=' * len(title))
report.append('')
# Read all of the notes files.
file_contents = {}
for version in versions_to_include:
for filename, sha in loader[version]:
body = loader.parse_note_file(filename, sha)
file_contents[filename] = body
for version in versions_to_include:
report.append(version)
report.append('=' * len(version))
report.append('')
# Add the preludes.
notefiles = loader[version]
prelude_name = config.prelude_section_name
notefiles_with_prelude = [(n, sha) for n, sha in notefiles
if prelude_name in file_contents[n]]
if notefiles_with_prelude:
report.append(prelude_name.replace('_', ' ').title())
report.append('-' * len(prelude_name))
report.append('')
for n, sha in notefiles_with_prelude:
if show_source:
report.append('.. %s @ %s\n' % (n, sha))
report.append(file_contents[n][prelude_name])
report.append('')
# Add other sections.
for section_name, section_title in config.sections:
notes = [
(n, fn, sha)
for fn, sha in notefiles
if file_contents[fn].get(section_name)
for n in file_contents[fn].get(section_name, [])
]
if notes:
report.append(section_title)
report.append('-' * len(section_title))
report.append('')
for n, fn, sha in notes:
if show_source:
report.append('.. %s @ %s\n' % (fn, sha))
report.append('- %s' % _indent_for_list(n))
report.append('')
return '\n'.join(report)

View File

@@ -1,54 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import glob
import logging
import os.path
from reno import loader
from reno import scanner
LOG = logging.getLogger(__name__)
def lint_cmd(args, conf):
"Check some common mistakes"
LOG.debug('starting lint')
notesdir = os.path.join(conf.reporoot, conf.notespath)
notes = glob.glob(os.path.join(notesdir, '*.yaml'))
error = 0
load = loader.Loader(conf, ignore_cache=True)
allowed_section_names = [conf.prelude_section_name] + \
[s[0] for s in conf.sections]
uids = {}
for f in notes:
LOG.debug('examining %s', f)
uid = scanner._get_unique_id(f)
uids.setdefault(uid, []).append(f)
content = load.parse_note_file(f, None)
for section_name in content.keys():
if section_name not in allowed_section_names:
LOG.warning('unrecognized section name %s in %s',
section_name, f)
error = 1
for uid, names in sorted(uids.items()):
if len(names) > 1:
LOG.warning('UID collision: %s', names)
error = 1
return error

View File

@@ -1,38 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
import logging
from reno import loader
LOG = logging.getLogger(__name__)
def list_cmd(args, conf):
"List notes files based on query arguments"
LOG.debug('starting list')
reporoot = conf.reporoot
ldr = loader.Loader(conf)
if args.version:
versions = args.version
else:
versions = ldr.versions
for version in versions:
notefiles = ldr[version]
print(version)
for n, sha in notefiles:
if n.startswith(reporoot):
n = n[len(reporoot):]
print('\t%s (%s)' % (n, sha))
return

View File

@@ -1,140 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os.path
import six
import yaml
from reno import scanner
LOG = logging.getLogger(__name__)
def get_cache_filename(conf):
return os.path.normpath(os.path.join(
conf.reporoot, conf.notespath, 'reno.cache'))
class Loader(object):
"Load the release notes for a given repository."
def __init__(self, conf,
ignore_cache=False):
"""Initialize a Loader.
The versions are presented in reverse chronological order.
Notes files are associated with the earliest version for which
they were available, regardless of whether they changed later.
:param conf: Parsed configuration from file
:type conf: reno.config.Config
:param ignore_cache: Do not load a cache file if it is present.
:type ignore_cache: bool
"""
self._config = conf
self._ignore_cache = ignore_cache
self._reporoot = conf.reporoot
self._notespath = conf.notespath
self._branch = conf.branch
self._collapse_pre_releases = conf.collapse_pre_releases
self._earliest_version = conf.earliest_version
self._cache = None
self._scanner = None
self._scanner_output = None
self._cache_filename = get_cache_filename(conf)
self._load_data()
def _load_data(self):
cache_file_exists = os.path.exists(self._cache_filename)
if self._ignore_cache and cache_file_exists:
LOG.debug('ignoring cache file %s', self._cache_filename)
if (not self._ignore_cache) and cache_file_exists:
with open(self._cache_filename, 'r') as f:
self._cache = yaml.safe_load(f.read())
# Save the cached scanner output to the same attribute
# it would be in if we had loaded it "live". This
# simplifies some of the logic in the other methods.
self._scanner_output = {
n['version']: n['files']
for n in self._cache['notes']
}
else:
self._scanner = scanner.Scanner(self._config)
self._scanner_output = self._scanner.get_notes_by_version()
@property
def versions(self):
"A list of all of the versions found."
return list(self._scanner_output.keys())
def __getitem__(self, version):
"Return data about the files that should go into a given version."
return self._scanner_output[version]
def parse_note_file(self, filename, sha):
"""Return the data structure encoded in the note file.
Emit warnings for content that does not look valid in some
way, but return it anyway for backwards-compatibility.
"""
if self._cache:
content = self._cache['file-contents'][filename]
else:
body = self._scanner.get_file_at_commit(filename, sha)
content = yaml.safe_load(body)
cleaned_content = {}
for section_name, section_content in content.items():
if section_name == self._config.prelude_section_name:
if not isinstance(section_content, six.string_types):
LOG.warning(
('The %s section of %s '
'does not parse as a single string. '
'Is the YAML input escaped properly?') %
(self._config.prelude_section_name, filename),
)
else:
if isinstance(section_content, six.string_types):
# A single string is OK, but wrap it with a list
# so the rest of the code can treat the data model
# consistently.
section_content = [section_content]
elif not isinstance(section_content, list):
LOG.warning(
('The %s section of %s '
'does not parse as a string or list of strings. '
'Is the YAML input escaped properly?') % (
section_name, filename),
)
else:
for item in section_content:
if not isinstance(item, six.string_types):
LOG.warning(
('The item %r in the %s section of %s '
'parses as a %s instead of a string. '
'Is the YAML input escaped properly?'
) % (item, section_name,
filename, type(item)),
)
cleaned_content[section_name] = section_content
return cleaned_content

View File

@@ -1,198 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import logging
import sys
from reno import cache
from reno import config
from reno import create
from reno import defaults
from reno import linter
from reno import lister
from reno import report
_query_args = [
(('--version',),
dict(default=[],
action='append',
help='the version(s) to include, defaults to all')),
(('--branch',),
dict(default=config.Config.get_default('branch'),
help='the branch to scan, defaults to the current')),
(('--collapse-pre-releases',),
dict(action='store_true',
default=config.Config.get_default('collapse_pre_releases'),
help='combine pre-releases with their final release')),
(('--no-collapse-pre-releases',),
dict(action='store_false',
dest='collapse_pre_releases',
help='show pre-releases separately')),
(('--earliest-version',),
dict(default=None,
help='stop when this version is reached in the history')),
(('--ignore-cache',),
dict(default=False,
action='store_true',
help='if there is a cache file present, do not use it')),
(('--stop-at-branch-base',),
dict(action='store_true',
default=True,
dest='stop_at_branch_base',
help='stop scanning when the branch meets master')),
(('--no-stop-at-branch-base',),
dict(action='store_false',
dest='stop_at_branch_base',
help='do not stop scanning when the branch meets master')),
]
def _build_query_arg_group(parser):
group = parser.add_argument_group('query')
for args, kwds in _query_args:
group.add_argument(*args, **kwds)
def main(argv=sys.argv[1:]):
parser = argparse.ArgumentParser()
parser.add_argument(
'-v', '--verbose',
dest='verbosity',
default=logging.INFO,
help='produce more output',
action='store_const',
const=logging.DEBUG,
)
parser.add_argument(
'-q', '--quiet',
dest='verbosity',
action='store_const',
const=logging.WARN,
help='produce less output',
)
parser.add_argument(
'--rel-notes-dir', '-d',
dest='relnotesdir',
default=defaults.RELEASE_NOTES_SUBDIR,
help='location of release notes YAML files',
)
subparsers = parser.add_subparsers(
title='commands',
)
do_new = subparsers.add_parser(
'new',
help='create a new note',
)
do_new.add_argument(
'--edit',
action='store_true',
help='Edit note after its creation (require EDITOR env variable)',
)
do_new.add_argument(
'slug',
help='descriptive title of note (keep it short)',
)
do_new.add_argument(
'reporoot',
default='.',
nargs='?',
help='root of the git repository',
)
do_new.set_defaults(func=create.create_cmd)
do_list = subparsers.add_parser(
'list',
help='list notes files based on query arguments',
)
_build_query_arg_group(do_list)
do_list.add_argument(
'reporoot',
default='.',
nargs='?',
help='root of the git repository',
)
do_list.set_defaults(func=lister.list_cmd)
do_report = subparsers.add_parser(
'report',
help='generate release notes report',
)
do_report.add_argument(
'reporoot',
default='.',
nargs='?',
help='root of the git repository',
)
do_report.add_argument(
'--output', '-o',
default=None,
help='output filename, defaults to stdout',
)
do_report.add_argument(
'--no-show-source',
dest='show_source',
default=True,
action='store_false',
help='do not show the source for notes',
)
do_report.add_argument(
'--title',
default='Release Notes',
help='set the main title of the generated report',
)
_build_query_arg_group(do_report)
do_report.set_defaults(func=report.report_cmd)
do_cache = subparsers.add_parser(
'cache',
help='generate release notes cache',
)
do_cache.add_argument(
'reporoot',
default='.',
nargs='?',
help='root of the git repository',
)
do_cache.add_argument(
'--output', '-o',
default=None,
help=('output filename, '
'defaults to the cache file within the notesdir, '
'use "-" for stdout'),
)
_build_query_arg_group(do_cache)
do_cache.set_defaults(func=cache.cache_cmd)
do_linter = subparsers.add_parser(
'lint',
help='check some common mistakes',
)
do_linter.add_argument(
'reporoot',
default='.',
nargs='?',
help='root of the git repository',
)
do_linter.set_defaults(func=linter.lint_cmd)
args = parser.parse_args(argv)
conf = config.Config(args.reporoot, args.relnotesdir)
conf.override_from_parsed_args(args)
logging.basicConfig(
level=args.verbosity,
format='%(message)s',
)
return args.func(args, conf)

View File

@@ -1,38 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import print_function
from reno import formatter
from reno import loader
def report_cmd(args, conf):
"Generates a release notes report"
ldr = loader.Loader(conf)
if args.version:
versions = args.version
else:
versions = ldr.versions
text = formatter.format_report(
ldr,
conf,
versions,
title=args.title,
show_source=args.show_source,
)
if args.output:
with open(args.output, 'w') as f:
f.write(text)
else:
print(text)
return

File diff suppressed because it is too large Load Diff

View File

@@ -1,123 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import os.path
from docutils import nodes
from docutils.parsers import rst
from docutils.parsers.rst import directives
from docutils import statemachine
from sphinx.util.nodes import nested_parse_with_titles
from dulwich import repo
from reno import config
from reno import defaults
from reno import formatter
from reno import loader
class ReleaseNotesDirective(rst.Directive):
has_content = True
option_spec = {
'branch': directives.unchanged,
'reporoot': directives.unchanged,
'relnotessubdir': directives.unchanged,
'notesdir': directives.unchanged,
'version': directives.unchanged,
'collapse-pre-releases': directives.flag,
'earliest-version': directives.unchanged,
'stop-at-branch-base': directives.flag,
'ignore-notes': directives.unchanged,
}
def run(self):
env = self.state.document.settings.env
app = env.app
def info(msg):
app.info('[reno] %s' % (msg,))
title = ' '.join(self.content)
branch = self.options.get('branch')
reporoot_opt = self.options.get('reporoot', '.')
reporoot = os.path.abspath(reporoot_opt)
# When building on RTD.org the root directory may not be
# the current directory, so look for it.
reporoot = repo.Repo.discover(reporoot).path
relnotessubdir = self.options.get('relnotessubdir',
defaults.RELEASE_NOTES_SUBDIR)
ignore_notes = [
name.strip()
for name in self.options.get('ignore-notes', '').split(',')
]
conf = config.Config(reporoot, relnotessubdir)
opt_overrides = {}
if 'notesdir' in self.options:
opt_overrides['notesdir'] = self.options.get('notesdir')
version_opt = self.options.get('version')
# FIXME(dhellmann): Force these flags True for now and figure
# out how Sphinx passes a "false" flag later.
# 'collapse-pre-releases' in self.options
opt_overrides['collapse_pre_releases'] = True
# Only stop at the branch base if we have not been told
# explicitly which versions to include.
opt_overrides['stop_at_branch_base'] = (version_opt is None)
if 'earliest-version' in self.options:
opt_overrides['earliest_version'] = self.options.get(
'earliest-version')
if branch:
opt_overrides['branch'] = branch
if ignore_notes:
opt_overrides['ignore_notes'] = ignore_notes
conf.override(**opt_overrides)
notesdir = os.path.join(relnotessubdir, conf.notesdir)
info('scanning %s for %s release notes' %
(os.path.join(conf.reporoot, notesdir),
branch or 'current branch'))
ldr = loader.Loader(conf)
if version_opt is not None:
versions = [
v.strip()
for v in version_opt.split(',')
]
else:
versions = ldr.versions
info('got versions %s' % (versions,))
text = formatter.format_report(
ldr,
conf,
versions,
title=title,
)
source_name = '<%s %s>' % (__name__, branch or 'current branch')
result = statemachine.ViewList()
for line in text.splitlines():
result.append(line, source_name)
node = nodes.section()
node.document = self.state.document
nested_parse_with_titles(self.state, result, node)
return node.children
def setup(app):
app.add_directive('release-notes', ReleaseNotesDirective)
logging.basicConfig(
level=logging.INFO,
format='[%(name)s] %(message)s',
)

View File

View File

@@ -1,33 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright 2010-2011 OpenStack Foundation
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import testtools
class TestCase(testtools.TestCase):
"""Test case base class for all unit tests."""
def setUp(self):
super(TestCase, self).setUp()
self._stdout_fixture = fixtures.StringStream('stdout')
self.stdout = self.useFixture(self._stdout_fixture).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout))
self._stderr_fixture = fixtures.StringStream('stderr')
self.stderr = self.useFixture(self._stderr_fixture).stream
self.useFixture(fixtures.MonkeyPatch('sys.stderr', self.stderr))

View File

@@ -1,86 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import textwrap
import mock
from reno import cache
from reno import config
from reno.tests import base
class TestCache(base.TestCase):
scanner_output = {
'0.0.0': [('note1', 'shaA')],
'1.0.0': [('note2', 'shaB'), ('note3', 'shaC')],
}
note_bodies = {
'note1': textwrap.dedent("""
prelude: >
This is the prelude.
"""),
'note2': textwrap.dedent("""
issues:
- This is the first issue.
- This is the second issue.
"""),
'note3': textwrap.dedent("""
features:
- We added a feature!
""")
}
def _get_note_body(self, filename, sha):
return self.note_bodies.get(filename, '')
def setUp(self):
super(TestCache, self).setUp()
self.useFixture(
fixtures.MockPatch('reno.scanner.Scanner.get_file_at_commit',
new=self._get_note_body)
)
self.c = config.Config('.')
def test_build_cache_db(self):
with mock.patch('reno.scanner.Scanner.get_notes_by_version') as gnbv:
gnbv.return_value = self.scanner_output
db = cache.build_cache_db(
self.c,
versions_to_include=[],
)
expected = {
'notes': [
{'version': k, 'files': v}
for k, v in self.scanner_output.items()
],
'file-contents': {
'note1': {
'prelude': 'This is the prelude.\n',
},
'note2': {
'issues': [
'This is the first issue.',
'This is the second issue.',
],
},
'note3': {
'features': ['We added a feature!'],
},
},
}
self.assertEqual(expected, db)

View File

@@ -1,174 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import os
import fixtures
from reno import config
from reno import defaults
from reno import main
from reno.tests import base
import mock
class TestConfig(base.TestCase):
EXAMPLE_CONFIG = """
collapse_pre_releases: false
"""
def setUp(self):
super(TestConfig, self).setUp()
# Temporary directory to store our config
self.tempdir = self.useFixture(fixtures.TempDir())
def test_defaults(self):
c = config.Config(self.tempdir.path)
actual = c.options
self.assertEqual(config.Config._OPTS, actual)
def test_override(self):
c = config.Config(self.tempdir.path)
c.override(
collapse_pre_releases=False,
)
actual = c.options
expected = {}
expected.update(config.Config._OPTS)
expected['collapse_pre_releases'] = False
self.assertEqual(expected, actual)
def test_override_multiple(self):
c = config.Config(self.tempdir.path)
c.override(
notesdir='value1',
)
c.override(
notesdir='value2',
)
actual = c.options
expected = {}
expected.update(config.Config._OPTS)
expected['notesdir'] = 'value2'
self.assertEqual(expected, actual)
def test_load_file_not_present(self):
with mock.patch.object(config.LOG, 'info') as logger:
config.Config(self.tempdir.path)
self.assertEqual(1, logger.call_count)
def _test_load_file(self, config_path):
with open(config_path, 'w') as fd:
fd.write(self.EXAMPLE_CONFIG)
self.addCleanup(os.unlink, config_path)
c = config.Config(self.tempdir.path)
self.assertEqual(False, c.collapse_pre_releases)
def test_load_file_in_releasenotesdir(self):
rn_path = self.tempdir.join('releasenotes')
os.mkdir(rn_path)
config_path = self.tempdir.join('releasenotes/config.yaml')
self._test_load_file(config_path)
def test_load_file_in_repodir(self):
config_path = self.tempdir.join('reno.yaml')
self._test_load_file(config_path)
def test_get_default(self):
d = config.Config.get_default('notesdir')
self.assertEqual('notes', d)
def test_get_default_unknown(self):
self.assertRaises(
ValueError,
config.Config.get_default,
'unknownopt',
)
def _run_override_from_parsed_args(self, argv):
parser = argparse.ArgumentParser()
main._build_query_arg_group(parser)
args = parser.parse_args(argv)
c = config.Config(self.tempdir.path)
c.override_from_parsed_args(args)
return c
def test_override_from_parsed_args_empty(self):
c = self._run_override_from_parsed_args([])
actual = {
o: getattr(c, o)
for o in config.Config._OPTS.keys()
}
self.assertEqual(config.Config._OPTS, actual)
def test_override_from_parsed_args(self):
c = self._run_override_from_parsed_args([
'--no-collapse-pre-releases',
])
actual = c.options
expected = {}
expected.update(config.Config._OPTS)
expected['collapse_pre_releases'] = False
self.assertEqual(expected, actual)
def test_override_from_parsed_args_ignore_non_options(self):
parser = argparse.ArgumentParser()
main._build_query_arg_group(parser)
parser.add_argument('not_a_config_option')
args = parser.parse_args(['value'])
c = config.Config(self.tempdir.path)
c.override_from_parsed_args(args)
self.assertFalse(hasattr(c, 'not_a_config_option'))
class TestConfigProperties(base.TestCase):
def setUp(self):
super(TestConfigProperties, self).setUp()
# Temporary directory to store our config
self.tempdir = self.useFixture(fixtures.TempDir())
self.c = config.Config('releasenotes')
def test_reporoot(self):
self.c.reporoot = 'blah//'
self.assertEqual('blah/', self.c.reporoot)
self.c.reporoot = 'blah'
self.assertEqual('blah/', self.c.reporoot)
def test_notespath(self):
self.assertEqual('releasenotes/notes', self.c.notespath)
self.c.override(notesdir='thenotes')
self.assertEqual('releasenotes/thenotes', self.c.notespath)
def test_template(self):
template = defaults.TEMPLATE.format(defaults.PRELUDE_SECTION_NAME)
self.assertEqual(template, self.c.template)
self.c.override(template='i-am-a-template')
self.assertEqual('i-am-a-template', self.c.template)
def test_prelude_override(self):
template = defaults.TEMPLATE.format(defaults.PRELUDE_SECTION_NAME)
self.assertEqual(template, self.c.template)
self.c.override(prelude_section_name='fake_prelude_name')
expected_template = defaults.TEMPLATE.format('fake_prelude_name')
self.assertEqual(expected_template, self.c.template)
def test_prelude_and_template_override(self):
template = defaults.TEMPLATE.format(defaults.PRELUDE_SECTION_NAME)
self.assertEqual(template, self.c.template)
self.c.override(prelude_section_name='fake_prelude_name',
template='i-am-a-template')
self.assertEqual('fake_prelude_name', self.c.prelude_section_name)
self.assertEqual('i-am-a-template', self.c.template)

View File

@@ -1,65 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import mock
from reno import create
from reno.tests import base
class TestPickFileName(base.TestCase):
@mock.patch('os.path.exists')
def test_not_random_enough(self, exists):
exists.return_value = True
self.assertRaises(
ValueError,
create._pick_note_file_name,
'somepath',
'someslug',
)
@mock.patch('os.path.exists')
def test_random_enough(self, exists):
exists.return_value = False
result = create._pick_note_file_name('somepath', 'someslug')
self.assertIn('somepath', result)
self.assertIn('someslug', result)
class TestCreate(base.TestCase):
def setUp(self):
super(TestCreate, self).setUp()
self.tmpdir = self.useFixture(fixtures.TempDir()).path
def test_create_from_template(self):
filename = create._pick_note_file_name(self.tmpdir, 'theslug')
create._make_note_file(filename, 'i-am-a-template')
with open(filename, 'r') as f:
body = f.read()
self.assertEqual('i-am-a-template', body)
def test_edit(self):
self.useFixture(fixtures.EnvironmentVariable('EDITOR', 'myeditor'))
with mock.patch('subprocess.call') as call_mock:
self.assertTrue(create._edit_file('somepath'))
call_mock.assert_called_once_with(['myeditor', 'somepath'])
def test_edit_without_editor_env_var(self):
self.useFixture(fixtures.EnvironmentVariable('EDITOR'))
with mock.patch('subprocess.call') as call_mock:
self.assertFalse(create._edit_file('somepath'))
call_mock.assert_not_called()

View File

@@ -1,158 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from reno import config
from reno import formatter
from reno import loader
from reno.tests import base
class TestFormatterBase(base.TestCase):
scanner_output = {
'0.0.0': [('note1', 'shaA')],
'1.0.0': [('note2', 'shaB'), ('note3', 'shaC')],
}
versions = ['0.0.0', '1.0.0']
def _get_note_body(self, reporoot, filename, sha):
return self.note_bodies.get(filename, '')
def setUp(self):
super(TestFormatterBase, self).setUp()
def _load(ldr):
ldr._scanner_output = self.scanner_output
ldr._cache = {
'file-contents': self.note_bodies
}
self.c = config.Config('reporoot')
with mock.patch('reno.loader.Loader._load_data', _load):
self.ldr = loader.Loader(
self.c,
ignore_cache=False,
)
class TestFormatter(TestFormatterBase):
note_bodies = {
'note1': {
'prelude': 'This is the prelude.',
},
'note2': {
'issues': [
'This is the first issue.',
'This is the second issue.',
],
},
'note3': {
'features': [
'We added a feature!',
],
'upgrade': None,
},
}
def test_with_title(self):
result = formatter.format_report(
loader=self.ldr,
config=self.c,
versions_to_include=self.versions,
title='This is the title',
)
self.assertIn('This is the title', result)
def test_versions(self):
result = formatter.format_report(
loader=self.ldr,
config=self.c,
versions_to_include=self.versions,
title='This is the title',
)
self.assertIn('0.0.0\n=====', result)
self.assertIn('1.0.0\n=====', result)
def test_without_title(self):
result = formatter.format_report(
loader=self.ldr,
config=self.c,
versions_to_include=self.versions,
title=None,
)
self.assertNotIn('This is the title', result)
def test_default_section_order(self):
result = formatter.format_report(
loader=self.ldr,
config=self.c,
versions_to_include=self.versions,
title=None,
)
prelude_pos = result.index('This is the prelude.')
issues_pos = result.index('This is the first issue.')
features_pos = result.index('We added a feature!')
expected = [prelude_pos, features_pos, issues_pos]
actual = list(sorted([prelude_pos, features_pos, issues_pos]))
self.assertEqual(expected, actual)
class TestFormatterCustomSections(TestFormatterBase):
note_bodies = {
'note1': {
'prelude': 'This is the prelude.',
},
'note2': {
'features': [
'This is the first feature.',
],
'api': [
'This is the API change for the first feature.',
],
},
'note3': {
'api': [
'This is the API change for the second feature.',
],
'features': [
'This is the second feature.',
],
},
}
def setUp(self):
super(TestFormatterCustomSections, self).setUp()
self.c.override(sections=[
['api', 'API Changes'],
['features', 'New Features'],
])
def test_custom_section_order(self):
result = formatter.format_report(
loader=self.ldr,
config=self.c,
versions_to_include=self.versions,
title=None,
)
prelude_pos = result.index('This is the prelude.')
api_pos = result.index('API Changes')
features_pos = result.index('New Features')
expected = [prelude_pos, api_pos, features_pos]
actual = list(sorted([prelude_pos, features_pos, api_pos]))
self.assertEqual(expected, actual)

View File

@@ -1,90 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging
import textwrap
import fixtures
import mock
import six
import yaml
from reno import config
from reno import loader
from reno.tests import base
class TestValidate(base.TestCase):
scanner_output = {
'0.0.0': [('note', 'shaA')],
}
versions = ['0.0.0']
def setUp(self):
super(TestValidate, self).setUp()
self.logger = self.useFixture(
fixtures.FakeLogger(
format='%(message)s',
level=logging.WARNING,
)
)
self.c = config.Config('reporoot')
def _make_loader(self, note_bodies):
def _load(ldr):
ldr._scanner_output = self.scanner_output
ldr._cache = {
'file-contents': {'note1': note_bodies},
}
with mock.patch('reno.loader.Loader._load_data', _load):
return loader.Loader(
self.c,
ignore_cache=False,
)
def test_prelude_list(self):
note_bodies = yaml.safe_load(textwrap.dedent('''
prelude:
- This is the first comment.
- This is a second.
'''))
self.assertIsInstance(note_bodies['prelude'], list)
ldr = self._make_loader(note_bodies)
ldr.parse_note_file('note1', None)
self.assertIn('prelude', self.logger.output)
def test_non_prelude_single_string_converted_to_list(self):
note_bodies = yaml.safe_load(textwrap.dedent('''
issues: |
This is a single string.
'''))
print(type(note_bodies['issues']))
self.assertIsInstance(note_bodies['issues'], six.string_types)
ldr = self._make_loader(note_bodies)
parse_results = ldr.parse_note_file('note1', None)
self.assertIsInstance(parse_results['issues'], list)
def test_note_with_colon_as_dict(self):
note_bodies = yaml.safe_load(textwrap.dedent('''
issues:
- This is the first issue.
- dict: This is parsed as a dictionary.
'''))
self.assertIsInstance(note_bodies['issues'][-1], dict)
ldr = self._make_loader(note_bodies)
ldr.parse_note_file('note1', None)
self.assertIn('dict', self.logger.output)

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import six
from reno.tests import base
from reno import utils
class TestGetRandomString(base.TestCase):
@mock.patch('random.randrange')
@mock.patch('os.urandom')
def test_no_urandom(self, urandom, randrange):
urandom.side_effect = Exception('cannot use this')
randrange.return_value = ord('a')
actual = utils.get_random_string()
expected = '61' * 8 # hex for ord('a')
self.assertIsInstance(actual, six.text_type)
self.assertEqual(expected, actual)
@mock.patch('random.randrange')
@mock.patch('os.urandom')
def test_with_urandom(self, urandom, randrange):
urandom.return_value = b'\x62' * 8
randrange.return_value = ord('a')
actual = utils.get_random_string()
expected = '62' * 8 # hex for ord('b')
self.assertIsInstance(actual, six.text_type)
self.assertEqual(expected, actual)

View File

@@ -1,54 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import binascii
import logging
import os
import os.path
import random
import subprocess
LOG = logging.getLogger(__name__)
def get_random_string(nbytes=8):
"""Return a fixed-length random string
:rtype: six.text_type
"""
try:
# NOTE(dhellmann): Not all systems support urandom().
# hexlify returns six.binary_type, decode to convert to six.text_type.
val = binascii.hexlify(os.urandom(nbytes)).decode('utf-8')
except Exception as e:
print('ERROR, perhaps urandom is not supported: %s' % e)
val = u''.join(u'%02x' % random.randrange(256)
for i in range(nbytes))
return val
def check_output(*args, **kwds):
"""Unicode-aware wrapper for subprocess.check_output"""
process = subprocess.Popen(stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
*args, **kwds)
output, errors = process.communicate()
retcode = process.poll()
if errors:
LOG.debug('ran: %s', ' '.join(*args))
LOG.debug('returned: %s', retcode)
LOG.debug('error output: %s', errors.rstrip())
LOG.debug('regular output: %s', output.rstrip())
if retcode:
LOG.debug('raising error')
raise subprocess.CalledProcessError(retcode, args, output=output)
return output.decode('utf-8')

View File

@@ -1,8 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.4
PyYAML>=3.1.0
six>=1.9.0
dulwich>=0.15.0 # Apache-2.0

View File

@@ -1,50 +0,0 @@
[metadata]
name = reno
summary = RElease NOtes manager
description-file =
README.rst
author = OpenStack
author-email = openstack-dev@lists.openstack.org
home-page = https://docs.openstack.org/reno/latest/
classifier =
Environment :: OpenStack
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.5
[files]
packages =
reno
[entry_points]
console_scripts =
reno = reno.main:main
[extras]
sphinx =
sphinx>=1.5.1,!=1.6.1 # BSD
docutils>=0.11 # OSI-Approved Open Source, Public Domain
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
warning-is-error = 1
[upload_sphinx]
upload-dir = doc/build/html
[compile_catalog]
directory = reno/locale
domain = reno
[update_catalog]
domain = reno
output_dir = reno/locale
input_file = reno/locale/reno.pot

View File

@@ -1,29 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
import multiprocessing # noqa
except ImportError:
pass
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

View File

@@ -1,14 +0,0 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
hacking>=0.12.0,!=0.13.0,<0.14 # Apache-2.0
mock>=1.2
coverage>=3.6
python-subunit>=0.0.18
openstackdocstheme>=1.11.0 # Apache-2.0
testrepository>=0.0.18
testscenarios>=0.4
testtools>=1.4.0

41
tox.ini
View File

@@ -1,41 +0,0 @@
[tox]
minversion = 1.6
envlist = py35,py27,pep8
skipsdist = True
[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv =
VIRTUAL_ENV={envdir}
deps =
-r{toxinidir}/test-requirements.txt
.[sphinx]
commands =
python setup.py test --slowest --coverage --coverage-package-name=reno --testr-args='{posargs}'
coverage report --show-missing
[testenv:pep8]
commands =
flake8
reno -q lint
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py test --coverage --testr-args='{posargs}'
[testenv:docs]
commands = python setup.py build_sphinx
[testenv:debug]
commands = oslo_debug_helper {posargs}
[flake8]
# E123, E125 skipped as they are invalid PEP-8.
show-source = True
ignore = E123,E125
builtins = _
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build