def test_rename_legacy_file_to_new(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file('slug1', legacy=True) self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') # Rename the file with the new convention of placing the UUID # after the slug instead of before. f2 = f1.replace('0000000000000001-slug1', 'slug1-0000000000000001') self._run_git('mv', f1, f2) self._git_commit('rename note file') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'2.0.0': [f2], '2.0.0-1': [], }, results, )
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 notes = scanner.get_notes_by_version( reporoot, notesdir, args.branch, collapse_pre_releases=collapse, earliest_version=args.earliest_version, ) if args.version: versions = args.version else: versions = notes.keys() for version in versions: notefiles = notes[version] print(version) for n, sha in notefiles: if n.startswith(reporoot): n = n[len(reporoot):] print('\t%s (%s)' % (n, sha)) return
def test_2(self): # Create changes on the branch before the tag into which it is # actually merged. self._add_other_file('ignore-0.txt') self._run_git('checkout', '-b', 'test_merge_commit') n1 = self._add_notes_file() self._run_git('checkout', 'master') n2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') self._add_other_file('ignore-1.txt') self._run_git('merge', '--no-ff', 'test_merge_commit') self._add_other_file('ignore-2.txt') self._run_git('tag', '-s', '-m', 'second tag', '2.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'1.0.0': [n2], '2.0.0': [n1]}, results, ) self.assertEqual( ['2.0.0', '1.0.0'], list(raw_results.keys()), )
def test_2(self): # Create changes on the branch before the tag into which it is # actually merged. self._add_other_file('ignore-0.txt') self._run_git('checkout', '-b', 'test_merge_commit') n1 = self._add_notes_file() self._run_git('checkout', 'master') n2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') self._add_other_file('ignore-1.txt') self._run_git('merge', '--no-ff', 'test_merge_commit') self._add_other_file('ignore-2.txt') self._run_git('tag', '-s', '-m', 'second tag', '2.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '1.0.0': [n2], '2.0.0': [n1] }, results, ) self.assertEqual( ['2.0.0', '1.0.0'], list(raw_results.keys()), )
def test_4(self): # Create changes on the branch before the tag into which it is # actually merged, with another tag in between the time of the # commit and the time of the merge. This should reflect the # order of events described in bug #1522153. self._add_other_file('ignore-0.txt') self._run_git('checkout', '-b', 'test_merge_commit') n1 = self._add_notes_file() self._run_git('checkout', 'master') n2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') self._add_other_file('ignore-1.txt') n3 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'second tag', '1.1.0') self._run_git('merge', '--no-ff', 'test_merge_commit') self._add_other_file('ignore-2.txt') self._run_git('tag', '-s', '-m', 'third tag', '2.0.0') self._add_other_file('ignore-3.txt') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '1.0.0': [n2], '1.1.0': [n3], '2.0.0': [n1] }, results, ) self.assertEqual( ['2.0.0', '1.1.0', '1.0.0'], list(raw_results.keys()), )
def report_cmd(args): "Generates a release notes report" reporoot = args.reporoot.rstrip('/') + '/' notesdir = utils.get_notes_dir(args) collapse = args.collapse_pre_releases notes = scanner.get_notes_by_version( reporoot, notesdir, args.branch, collapse_pre_releases=collapse, earliest_version=args.earliest_version, ) if args.version: versions = args.version else: versions = notes.keys() text = formatter.format_report( reporoot, notes, versions, title='Release Notes', ) if args.output: with open(args.output, 'w') as f: f.write(text) else: print(text) return
def test_files_stable_from_master(self): self._run_git('checkout', '2.0.0') self._run_git('checkout', '-b', 'stable/2') f21 = self._add_notes_file('slug21') self._run_git('checkout', 'master') log_text = self._run_git('log', '--pretty=%x00%H %d', '--name-only', 'stable/2') self.addDetail('git log', text_content(log_text)) raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', 'stable/2', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( { '1.0.0': [self.f1], '2.0.0': [self.f2], '2.0.0-1': [f21], }, results, )
def test_non_python_no_tags(self): filename = self._add_notes_file() raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( {'0.0.0': [filename]}, results, )
def test_note_commit_tagged(self): filename = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( {'1.0.0': [filename]}, results, )
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')) notes = scanner.get_notes_by_version( reporoot, notesdir, 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 = notes.keys() text = formatter.format_report( reporoot, notes, 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
def test_non_python_no_tags(self): filename = self._add_notes_file() raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'0.0.0': [filename]}, results, )
def test_multiple_notes_within_tag(self): self._make_python_package() f1 = self._add_notes_file(commit=False) f2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( {'1.0.0': [f1, f2]}, results, )
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')) notes = scanner.get_notes_by_version( reporoot, notesdir, 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 = notes.keys() text = formatter.format_report( reporoot, notes, 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
def test_note_commit_tagged(self): filename = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'1.0.0': [filename]}, results, )
def test_release_candidate(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0.0rc1') f1 = self._add_notes_file('slug1') self._run_git('tag', '-s', '-m', 'first tag', '1.0.0.0rc2') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '1.0.0.0rc2': [f1], }, results, )
def test_multiple_notes_within_tag(self): self._make_python_package() f1 = self._add_notes_file(commit=False) f2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'1.0.0': [f1, f2]}, results, )
def list_cmd(args): "List notes files based on query arguments" reporoot = args.reporoot.rstrip("/") + "/" notesdir = utils.get_notes_dir(args) notes = scanner.get_notes_by_version(reporoot, notesdir, args.branch) if args.version: versions = args.version else: versions = notes.keys() for version in versions: notefiles = notes[version] print(version) for n, sha in notefiles: if n.startswith(reporoot): n = n[len(reporoot) :] print("\t%s (%s)" % (n, sha)) return
def test_files_current_branch(self): self._run_git('checkout', '2.0.0') self._run_git('checkout', '-b', 'stable/2') f21 = self._add_notes_file('slug21') log_text = self._run_git('log') self.addDetail('git log', text_content(log_text)) raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '1.0.0': [self.f1], '2.0.0': [self.f2], '2.0.0-1': [f21], }, results, )
def test_delete_file(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file('slug1') f2 = self._add_notes_file('slug2') self._run_git('rm', f1) self._git_commit('remove note file') self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '2.0.0': [f2], }, results, )
def test_rename_file_sort_earlier(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file('slug1') self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') f2 = f1.replace('slug1', 'slug0') self._run_git('mv', f1, f2) self._git_commit('rename note file') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '2.0.0': [f2], }, results, )
def test_edit_file(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') with open(os.path.join(self.reporoot, f1), 'w') as f: f.write('---\npreamble: new contents for file') self._git_commit('edit note file') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '2.0.0': [f1], }, results, )
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') notesdir = os.path.join(relnotessubdir, notessubdir) info('scanning %s for %s release notes' % (os.path.join(reporoot, notesdir), branch or 'current branch')) notes = scanner.get_notes_by_version(reporoot, notesdir, branch) if version_opt is not None: versions = [ v.strip() for v in version_opt.split(',') ] else: versions = notes.keys() text = formatter.format_report( reporoot, notes, 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
def test_edit_file(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') with open(os.path.join(self.reporoot, f1), 'w') as f: f.write('---\npreamble: new contents for file') self._git_commit('edit note file') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'2.0.0': [f1], }, results, )
def test_legacy_file(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file('slug1', legacy=True) self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') f2 = f1.replace('slug1', 'slug2') self._run_git('mv', f1, f2) self._git_commit('rename note file') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } self.assertEqual( {'2.0.0': [f2], }, results, )
def test_limit_by_earliest_version(self): self._make_python_package() self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'middle tag', '2.0.0') f3 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'last tag', '3.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', earliest_version='2.0.0', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '2.0.0': [f2], '3.0.0': [f3], }, results, )
def test_rename_legacy_file_to_new(self): self._make_python_package() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') f1 = self._add_notes_file('slug1', legacy=True) self._run_git('tag', '-s', '-m', 'first tag', '2.0.0') # Rename the file with the new convention of placing the UUID # after the slug instead of before. f2 = f1.replace('0000000000000001-slug1', 'slug1-0000000000000001') self._run_git('mv', f1, f2) self._git_commit('rename note file') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '2.0.0': [f2], }, results, )
def build_cache_db(reporoot, notesdir, branch, collapse_pre_releases, versions_to_include, earliest_version): notes = scanner.get_notes_by_version( reporoot, notesdir, branch, collapse_pre_releases=collapse_pre_releases, earliest_version=earliest_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 = scanner.get_file_at_commit( reporoot, 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 test_collapse_without_full_release(self): self._make_python_package() f1 = self._add_notes_file('slug1') self._run_git('tag', '-s', '-m', 'alpha tag', '1.0.0.0a1') f2 = self._add_notes_file('slug2') self._run_git('tag', '-s', '-m', 'beta tag', '1.0.0.0b1') f3 = self._add_notes_file('slug3') self._run_git('tag', '-s', '-m', 'release candidate tag', '1.0.0.0rc1') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', collapse_pre_releases=True, ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '1.0.0.0a1': [f1], '1.0.0.0b1': [f2], '1.0.0.0rc1': [f3], }, results, )
def build_cache_db(reporoot, notesdir, branch, collapse_pre_releases, versions_to_include, earliest_version): notes = scanner.get_notes_by_version( reporoot, notesdir, branch, collapse_pre_releases=collapse_pre_releases, earliest_version=earliest_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 = scanner.get_file_at_commit( reporoot, 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 test_collapse(self): files = [] self._make_python_package() files.append(self._add_notes_file('slug1')) self._run_git('tag', '-s', '-m', 'alpha tag', '1.0.0.0a1') files.append(self._add_notes_file('slug2')) self._run_git('tag', '-s', '-m', 'beta tag', '1.0.0.0b1') files.append(self._add_notes_file('slug3')) self._run_git('tag', '-s', '-m', 'release candidate tag', '1.0.0.0rc1') files.append(self._add_notes_file('slug4')) self._run_git('tag', '-s', '-m', 'full release tag', '1.0.0') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', collapse_pre_releases=True, ) results = {k: [f for (f, n) in v] for (k, v) in raw_results.items()} self.assertEqual( { '1.0.0': files, }, results, )
def test_3(self): # Create changes on the branch before the tag into which it is # actually merged, with another tag in between the time of the # commit and the time of the merge. This should reflect the # order of events described in bug #1522153. self._add_other_file('ignore-0.txt') self._run_git('checkout', '-b', 'test_merge_commit') n1 = self._add_notes_file() self._run_git('checkout', 'master') n2 = self._add_notes_file() self._run_git('tag', '-s', '-m', 'first tag', '1.0.0') self._add_other_file('ignore-1.txt') self._run_git('tag', '-s', '-m', 'second tag', '1.1.0') self._run_git('merge', '--no-ff', 'test_merge_commit') self._add_other_file('ignore-2.txt') self._run_git('tag', '-s', '-m', 'third tag', '2.0.0') self._add_other_file('ignore-3.txt') raw_results = scanner.get_notes_by_version( self.reporoot, 'releasenotes/notes', ) results = { k: [f for (f, n) in v] for (k, v) in raw_results.items() } # Since the 1.1.0 tag has no notes files, it does not appear # in the output. It's only there to trigger the bug as it was # originally reported. self.assertEqual( {'1.0.0': [n2], '2.0.0': [n1]}, results, ) self.assertEqual( ['2.0.0', '1.0.0'], list(raw_results.keys()), )
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_output = scanner.get_notes_by_version( reporoot=self._reporoot, notesdir=self._notesdir, branch=self._branch, collapse_pre_releases=self._collapse_pre_releases, earliest_version=self._earliest_version, )
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 scanner_output = scanner.get_notes_by_version( reporoot=library_path, notesdir='%s/%s' % (reno_defaults.RELEASE_NOTES_SUBDIR, reno_defaults.NOTES_SUBDIR), branch=branch, ) if end_revision in scanner_output: rst_notes = formatter.format_report( reporoot=library_path, scanner_output=scanner_output, 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)