예제 #1
0
파일: lister.py 프로젝트: fbo-test/project2
def list_cmd(args):
    "List notes files based on query arguments"
    LOG.debug('starting list')
    reporoot = args.reporoot.rstrip('/') + '/'
    notesdir = utils.get_notes_dir(args)
    collapse = args.collapse_pre_releases
    ldr = loader.Loader(
        reporoot=reporoot,
        notesdir=notesdir,
        branch=args.branch,
        collapse_pre_releases=collapse,
        earliest_version=args.earliest_version,
    )
    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
예제 #2
0
def report_cmd(args):
    "Generates a release notes report"
    reporoot = args.reporoot.rstrip('/') + '/'
    notesdir = utils.get_notes_dir(args)
    collapse = args.collapse_pre_releases
    ldr = loader.Loader(
        reporoot=reporoot,
        notesdir=notesdir,
        branch=args.branch,
        collapse_pre_releases=collapse,
        earliest_version=args.earliest_version,
    )
    if args.version:
        versions = args.version
    else:
        versions = ldr.versions
    text = formatter.format_report(
        ldr,
        versions,
        title='Release Notes',
    )
    if args.output:
        with open(args.output, 'w') as f:
            f.write(text)
    else:
        print(text)
    return
예제 #3
0
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
예제 #4
0
    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)
        relnotessubdir = self.options.get('relnotessubdir',
                                          defaults.RELEASE_NOTES_SUBDIR)
        notessubdir = self.options.get('notesdir', defaults.NOTES_SUBDIR)
        version_opt = self.options.get('version')
        # FIXME(dhellmann): Force this flag True for now and figure
        # out how Sphinx passes a "false" flag later.
        collapse = True  # 'collapse-pre-releases' in self.options
        earliest_version = self.options.get('earliest-version')

        notesdir = os.path.join(relnotessubdir, notessubdir)
        info('scanning %s for %s release notes' %
             (os.path.join(reporoot, notesdir), branch or 'current branch'))

        ldr = loader.Loader(
            reporoot=reporoot,
            notesdir=notesdir,
            branch=branch,
            collapse_pre_releases=collapse,
            earliest_version=earliest_version,
        )
        if version_opt is not None:
            versions = [
                v.strip()
                for v in version_opt.split(',')
            ]
        else:
            versions = ldr.versions
        text = formatter.format_report(
            ldr,
            versions,
            title=title,
        )
        source_name = '<' + __name__ + '>'
        result = 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
예제 #5
0
    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,
            )
예제 #6
0
    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,
            )
예제 #7
0
    def setUp(self):
        super(TestFormatter, self).setUp()

        def _load(ldr):
            ldr._scanner_output = self.scanner_output
            ldr._cache = {'file-contents': self.note_bodies}

        with mock.patch('reno.loader.Loader._load_data', _load):
            self.ldr = loader.Loader(
                reporoot='reporoot',
                notesdir='notesdir',
                branch=None,
                collapse_pre_releases=None,
                earliest_version=None,
                ignore_cache=False,
            )
예제 #8
0
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
예제 #9
0
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
예제 #10
0
    def run(self):
        conf = config.Config(self.repo_root, self.rel_notes_dir)

        # Generate the cache using the configuration options found
        # in the release notes directory and the default output
        # filename.
        cache_filename = cache.write_cache_db(
            conf=conf,
            versions_to_include=[],  # include all versions
            outfilename=None,  # generate the default name
        )
        log.info('wrote cache file to %s', cache_filename)

        ldr = loader.Loader(conf)
        text = formatter.format_report(
            ldr,
            conf,
            ldr.versions,
            title=self.distribution.metadata.name,
        )
        with open(self.output_file, 'w') as f:
            f.write(text)
        log.info('wrote release notes to %s', self.output_file)
예제 #11
0
파일: semver.py 프로젝트: dhellmann/reno
def compute_next_version(conf):
    "Compute the next semantic version based on the available release notes."
    LOG.debug('starting semver-next')
    ldr = loader.Loader(conf, ignore_cache=True)
    LOG.debug('known versions: %s', ldr.versions)

    # We want to include any notes in the local working directory or
    # in any commits that came after the last tag. We should never end
    # up with more than 2 entries in to_include.
    to_include = []
    for to_consider in ldr.versions:
        if to_consider == '*working-copy*':
            to_include.append(to_consider)
            continue
        # This check relies on PEP 440 versioning
        parsed = version.Version(to_consider)
        if parsed.post:
            to_include.append(to_consider)
            continue
        break

    # If we found no commits then we're sitting on a real tag and
    # there is nothing to do to update the version.
    if not to_include:
        LOG.debug('found no staged notes and no post-release commits')
        return ldr.versions[0]

    LOG.debug('including notes from %s', to_include)

    candidate_bases = to_include[:]
    if candidate_bases[0] == '*working-copy*':
        candidate_bases = candidate_bases[1:]

    if not candidate_bases:
        # We have a real tag and some locally modified files. Use the
        # real tag as the basis of the next version.
        base_version = version.Version(ldr.versions[1])
    else:
        base_version = version.Version(candidate_bases[0])

    LOG.debug('base version %s', base_version)

    inc_minor = False
    inc_patch = False
    for ver in to_include:
        for filename, sha in ldr[ver]:
            notes = ldr.parse_note_file(filename, sha)
            for section in conf.semver_major:
                if notes.get(section, []):
                    LOG.debug('found breaking change in %r section of %s',
                              section, filename)
                    return '{}.0.0'.format(base_version.major + 1)
            for section in conf.semver_minor:
                if notes.get(section, []):
                    LOG.debug('found feature in %r section of %s', section,
                              filename)
                    inc_minor = True
                    break
            for section in conf.semver_patch:
                if notes.get(section, []):
                    LOG.debug('found bugfix in %r section of %s', section,
                              filename)
                    inc_patch = True
                    break

    major = base_version.major
    minor = base_version.minor
    patch = base_version.micro
    if inc_patch:
        patch += 1
    if inc_minor:
        minor += 1
        patch = 0
    return '{}.{}.{}'.format(major, minor, patch)
예제 #12
0
    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
예제 #13
0
def generate_release_notes(
    repo,
    repo_path,
    start_revision,
    end_revision,
    show_dates,
    skip_requirement_merges,
    is_stable,
    series,
    email,
    email_from,
    email_reply_to,
    email_tags,
    include_pypi_link,
    changes_only,
    first_release,
    deliverable_file,
    description,
    publishing_dir_name,
):
    """Return the text of the release notes.

    :param repo: The name of the repo.
    :param repo_path: Path to the repo repository on disk.
    :param start_revision: First reference for finding change log.
    :param end_revision: Final reference for finding change log.
    :param show_dates: Boolean indicating whether or not to show dates
        in the output.
    :param skip_requirement_merges: Boolean indicating whether to
        skip merge commits for requirements changes.
    :param is_stable: Boolean indicating whether this is a stable
        series or not.
    :param series: String holding the name of the series.
    :param email: Boolean indicating whether the output format should
        be an email message.
    :param email_from: String containing the sender email address.
    :param email_reply_to: String containing the email reply-to address.
    :param email_tags: String containing the email header topic tags to add.
    :param include_pypi_link: Boolean indicating whether or not to
        include an automatically generated link to the PyPI package
        page.
    :param changes_only: Boolean indicating whether to limit output to
        the list of changes, without any extra data.
    :param first_release: Boolean indicating whether this is the first
        release of the project
    :param deliverable_file: The deliverable file path from the repo root.
    :param description: Description of the repo
    :param publishing_dir_name: The directory on publishings.openstack.org
        containing the package.
    """
    repo_name = repo.split('/')[-1]
    # Determine if this is a release candidate or not.
    is_release_candidate = 'rc' in end_revision

    # Do not mention the series in independent model since there is none
    if series == 'independent':
        series = ''

    if not email_from:
        raise RuntimeError('No email-from specified')

    # Get the commits that are in the desired range...
    git_range = "%s..%s" % (start_revision, end_revision)
    if show_dates:
        format = "--format=%h %ci %s"
    else:
        format = "--oneline"
    cmd = ["git", "log", "--no-color", format, "--no-merges", git_range]
    stdout = run_cmd(cmd, cwd=repo_path)
    changes = []
    for commit_line in stdout.splitlines():
        commit_line = commit_line.strip()
        if not commit_line or is_skippable_commit(skip_requirement_merges,
                                                  commit_line):
            continue
        else:
            changes.append(commit_line)

    # Filter out any requirement file changes...
    requirement_changes = []
    requirement_files = list(
        glob.glob(os.path.join(repo_path, '*requirements*.txt')))
    if requirement_files:
        cmd = ['git', 'diff', '-U0', '--no-color', git_range]
        cmd.extend(requirement_files)
        stdout = run_cmd(cmd, cwd=repo_path)
        requirement_changes = [
            line.strip() for line in stdout.splitlines() if line.strip()
        ]

    # Get statistics about the range given...
    cmd = ['git', 'diff', '--stat', '--no-color', git_range]
    stdout = run_cmd(cmd, cwd=repo_path)
    diff_stats = []
    for line in stdout.splitlines():
        line = line.strip()
        if not line or line.find("tests") != -1 or line.startswith("doc"):
            continue
        diff_stats.append(line)

    # Extract + valdiate needed sections...
    sections = parse_deliverable(series,
                                 repo_name,
                                 deliverable_file=deliverable_file)
    change_header = ["Changes in %s %s" % (repo, git_range)]
    change_header.append("-" * len(change_header[0]))

    # Look for reno notes for this version.
    if not changes_only:
        logging.getLogger('reno').setLevel(logging.WARNING)
        cfg = reno_config.Config(reporoot=repo_path, )
        branch = None
        if is_stable and series:
            branch = 'origin/stable/%s' % series
        cfg.override(branch=branch)
        ldr = loader.Loader(conf=cfg, ignore_cache=True)
        if end_revision in ldr.versions:
            rst_notes = formatter.format_report(
                loader=ldr,
                config=cfg,
                versions_to_include=[end_revision],
            )
            reno_notes = rst2txt.convert(rst_notes).decode('utf-8')
        else:
            LOG.warning(
                ('Did not find revision %r in list of versions '
                 'with release notes %r, skipping reno'),
                end_revision,
                ldr.versions,
            )
            reno_notes = ''
    else:
        reno_notes = ''

    # The recipient for announcements should always be the
    # [email protected] ML (except for
    # release-test)
    email_to = '*****@*****.**'
    if repo_name == 'openstack-release-test':
        email_to = '*****@*****.**'

    params = dict(sections)
    params.update({
        'project': repo,
        'description': description,
        'end_rev': end_revision,
        'range': git_range,
        'lib': repo_path,
        'skip_requirement_merges': skip_requirement_merges,
        'changes': changes,
        'requirement_changes': requirement_changes,
        'diff_stats': diff_stats,
        'change_header': "\n".join(change_header),
        'emotion': random.choice(EMOTIONS),
        'stable_series': is_stable,
        'series': series,
        'email': email,
        'email_from': email_from,
        'email_to': email_to,
        'email_reply_to': email_reply_to,
        'email_tags': email_tags,
        'reno_notes': reno_notes,
        'first_release': first_release,
        'publishing_dir_name': publishing_dir_name,
    })
    if include_pypi_link:
        params['pypi_url'] = PYPI_URL_TPL % repo_name
    else:
        params['pypi_url'] = None

    response = []
    if changes_only:
        response.append(expand_template(CHANGES_ONLY_TPL, params))
    else:
        if email:
            email_header = expand_template(EMAIL_HEADER_TPL.strip(), params)
            response.append(email_header.lstrip())
        if is_release_candidate:
            response.append(expand_template(RELEASE_CANDIDATE_TPL, params))
        else:
            header = expand_template(HEADER_RELEASE_TPL.strip(), params)
            response.append(parawrap.fill(header))
            response.append(expand_template(CHANGE_RELEASE_TPL, params))
    return '\n'.join(response)
예제 #14
0
파일: sphinxext.py 프로젝트: kastenhq/reno
    def run(self):
        title = ' '.join(self.content)
        branch = self.options.get('branch')
        relnotessubdir = self.options.get(
            'relnotessubdir',
            defaults.RELEASE_NOTES_SUBDIR,
        )
        reporoot = self._find_reporoot(
            self.options.get('reporoot', '.'),
            relnotessubdir,
        )
        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 'unreleased-version-title' in self.options:
            opt_overrides['unreleased_version_title'] = self.options.get(
                'unreleased-version-title')

        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)
        LOG.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
        LOG.info('got versions %s' % (versions, ))
        text = formatter.format_report(
            ldr,
            conf,
            versions,
            title=title,
            branch=branch,
        )
        source_name = '<%s %s>' % (__name__, branch or 'current branch')
        result = statemachine.ViewList()
        for line_num, line in enumerate(text.splitlines(), 1):
            LOG.debug('%4d: %s', line_num, line)
            result.append(line, source_name, line_num)

        node = nodes.section()
        node.document = self.state.document
        nested_parse_with_titles(self.state, result, node)
        return node.children
예제 #15
0
def generate_release_notes(
    library,
    library_path,
    start_revision,
    end_revision,
    show_dates,
    skip_requirement_merges,
    is_stable,
    series,
    email,
    email_from,
    email_to,
    email_reply_to,
    email_tags,
    include_pypi_link,
    changes_only,
    first_release,
):
    """Return the text of the release notes.

    :param library: The name of the library.
    :param library_path: Path to the library repository on disk.
    :param start_revision: First reference for finding change log.
    :param end_revision: Final reference for finding change log.
    :param show_dates: Boolean indicating whether or not to show dates
        in the output.
    :param skip_requirement_merges: Boolean indicating whether to
        skip merge commits for requirements changes.
    :param is_stable: Boolean indicating whether this is a stable
        series or not.
    :param series: String holding the name of the series.
    :param email: Boolean indicating whether the output format should
        be an email message.
    :param email_from: String containing the sender email address.
    :param email_to: String containing the email recipient.
    :param email_reply_to: String containing the email reply-to address.
    :param email_tags: String containing the email header topic tags to add.
    :param include_pypi_link: Boolean indicating whether or not to
        include an automatically generated link to the PyPI package
        page.
    :param changes_only: Boolean indicating whether to limit output to
        the list of changes, without any extra data.
    :param first_release: Boolean indicating whether this is the first
        release of the project

    """

    # Do not mention the series in independent model since there is none
    if series == 'independent':
        series = ''

    if not os.path.isfile(os.path.join(library_path, "setup.py")):
        raise RuntimeError("No 'setup.py' file found in %s\n" % library_path)

    if not email_from:
        raise RuntimeError('No email-from specified')

    # Get the python library/program description...
    cmd = [sys.executable, 'setup.py', '--description']
    stdout, stderr = run_cmd(cmd, cwd=library_path)
    description = stdout.strip()

    # Get the python library/program name
    cmd = [sys.executable, 'setup.py', '--name']
    stdout, stderr = run_cmd(cmd, cwd=library_path)
    library_name = stdout.strip()

    # Get the commits that are in the desired range...
    git_range = "%s..%s" % (start_revision, end_revision)
    if show_dates:
        format = "--format=%h %ci %s"
    else:
        format = "--oneline"
    cmd = ["git", "log", "--no-color", format, "--no-merges", git_range]
    stdout, stderr = run_cmd(cmd, cwd=library_path)
    changes = []
    for commit_line in stdout.splitlines():
        commit_line = commit_line.strip()
        if not commit_line or is_skippable_commit(skip_requirement_merges,
                                                  commit_line):
            continue
        else:
            changes.append(commit_line)

    # Filter out any requirement file changes...
    requirement_changes = []
    requirement_files = list(
        glob.glob(os.path.join(library_path, '*requirements*.txt')))
    if requirement_files:
        cmd = ['git', 'diff', '-U0', '--no-color', git_range]
        cmd.extend(requirement_files)
        stdout, stderr = run_cmd(cmd, cwd=library_path)
        requirement_changes = [
            line.strip() for line in stdout.splitlines() if line.strip()
        ]

    # Get statistics about the range given...
    cmd = ['git', 'diff', '--stat', '--no-color', git_range]
    stdout, stderr = run_cmd(cmd, cwd=library_path)
    diff_stats = []
    for line in stdout.splitlines():
        line = line.strip()
        if not line or line.find("tests") != -1 or line.startswith("doc"):
            continue
        diff_stats.append(line)

    # Extract + valdiate needed sections from readme...
    readme_sections = parse_readme(library_path)
    change_header = ["Changes in %s %s" % (library_name, git_range)]
    change_header.append("-" * len(change_header[0]))

    # Look for reno notes for this version.
    branch = None
    if is_stable and series:
        branch = 'origin/stable/%s' % series
    ldr = loader.Loader(
        reporoot=library_path,
        notesdir='%s/%s' %
        (reno_defaults.RELEASE_NOTES_SUBDIR, reno_defaults.NOTES_SUBDIR),
        branch=branch,
    )
    if end_revision in ldr.versions:
        rst_notes = formatter.format_report(
            ldr,
            versions_to_include=[end_revision],
        )
        reno_notes = rst2txt.convert(rst_notes)
    else:
        reno_notes = ''

    params = dict(readme_sections)
    params.update({
        'project': library_name,
        'description': description,
        'end_rev': end_revision,
        'range': git_range,
        'lib': library_path,
        'skip_requirement_merges': skip_requirement_merges,
        'changes': changes,
        'requirement_changes': requirement_changes,
        'diff_stats': diff_stats,
        'change_header': "\n".join(change_header),
        'emotion': random.choice(EMOTIONS),
        'stable_series': is_stable,
        'series': series,
        'email': email,
        'email_from': email_from,
        'email_to': email_to,
        'email_reply_to': email_reply_to,
        'email_tags': email_tags,
        'reno_notes': reno_notes,
        'first_release': first_release,
    })
    if include_pypi_link:
        params['pypi_url'] = PYPI_URL_TPL % library_name
    else:
        params['pypi_url'] = None

    response = []
    if changes_only:
        response.append(expand_template(CHANGES_ONLY_TPL, params))
    else:
        if email:
            email_header = expand_template(EMAIL_HEADER_TPL.strip(), params)
            response.append(email_header.lstrip())
        header = expand_template(HEADER_RELEASE_TPL.strip(), params)
        response.append(parawrap.fill(header))
        response.append(expand_template(CHANGE_RELEASE_TPL, params))
    return '\n'.join(response)
예제 #16
0
    def run(self):
        # type: () -> nodes.Node
        """
        Run to generate the output from .. ddtrace-release-notes:: directive


        1. Determine the max version cutoff we need to report for

           We determine this by traversing the git log until we
           find the first dev or release branch ref.

           If we are generating for 1.x branch we will use 2.0 as the cutoff.

           If we are generating for 0.60 branch we will use 0.61 as the cutoff.

           We do this to ensure if we are generating notes for older versions
           we do no include all up to date release notes. Think releasing 0.57.2
           when there is 0.58.0, 0.59.0, 1.0.0, etc we only want notes for < 0.58.

        2. Iterate through all release branches

           A release branch is one that matches the ``^[0-9]+.[0-9]+``` pattern

           Skip any that do not meet the max version cutoff.

        3. Determine the earliest version to report for each release branch

           If the release has only RC releases then use ``.0rc1`` as the earliest
           version. If there are non-RC releases then use ``.0`` version as the
           earliest.

           We do this because we want reno to only report notes that are for that
           given release branch but it will collapse RC releases if there is a
           non-RC tag on that branch. So there isn't a consistent "earliest version"
           we can use for in-progress/dev branches as well as released branches.


        4. Generate a reno config for reporting and generate the notes for each branch
        """
        # This is where we will aggregate the generated notes
        title = " ".join(self.content)
        result = statemachine.ViewList()

        # Determine the max version we want to report for
        max_version = self._get_report_max_version()
        LOG.info("capping max report version to %r", max_version)

        # For each release branch, starting with the newest
        for version, ref in self._release_branches:
            # If this version is equal to or greater than the max version we want to report for
            if max_version is not None and version >= max_version:
                LOG.info("skipping %s >= %s", version, max_version)
                continue

            # Older versions did not have reno release notes
            # DEV: Reno will fail if we try to run on a branch with no notes
            if (version.major, version.minor) < (0, 44):
                LOG.info("skipping older version %s", version)
                continue

            # Parse the branch name from the ref, we want origin/{major}.{minor}[-dev]
            _, _, branch = ref.partition("refs/remotes/")

            # Determine the earliest release tag for this version
            earliest_version = self._get_earliest_version(version)
            if not earliest_version:
                LOG.info("no release tags found for %s", version)
                continue

            # Setup reno config
            conf = config.Config(self._repo.path, "releasenotes")
            conf.override(
                branch=branch,
                collapse_pre_releases=True,
                stop_at_branch_base=True,
                earliest_version=earliest_version,
            )
            LOG.info(
                "scanning %s for %s release notes, stopping at %s",
                os.path.join(self._repo.path, "releasenotes/notes"),
                branch,
                earliest_version,
            )

            # Generate the formatted RST
            with loader.Loader(conf) as ldr:
                versions = ldr.versions
                LOG.info("got versions %s", versions)
                text = formatter.format_report(
                    ldr,
                    conf,
                    versions,
                    title=title,
                    branch=branch,
                )

            source_name = "<%s %s>" % (__name__, branch or "current branch")
            for line_num, line in enumerate(text.splitlines(), 1):
                LOG.debug("%4d: %s", line_num, line)
                result.append(line, source_name, line_num)

        # Generate the RST nodes to return for rendering
        node = nodes.section()
        node.document = self.state.document
        nested_parse_with_titles(self.state, result, node)
        return node.children