From cfa1743d12f28935f845a4bb350094b7d34504ed Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 6 Feb 2014 09:17:56 +0200 Subject: [PATCH 01/10] GitRepository: allow interactive commands Make it possible for git commands to output directly to stdout without redirection (i.e. capturing their stdout stream). Signed-off-by: Markus Lehtonen --- gbp/git/repository.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/gbp/git/repository.py b/gbp/git/repository.py index 6cc3cd9f..49dc2243 100644 --- a/gbp/git/repository.py +++ b/gbp/git/repository.py @@ -171,7 +171,7 @@ def _git_getoutput(self, command, args=[], extra_env=None, cwd=None): return output, popen.returncode def _git_inout(self, command, args, input=None, extra_env=None, cwd=None, - capture_stderr=False, config_args=None): + capture_stderr=False, capture_stdout=True, config_args=None): """ Run a git command with input and return output @@ -192,10 +192,12 @@ def _git_inout(self, command, args, input=None, extra_env=None, cwd=None, """ if not cwd: cwd = self.path - return self.__git_inout(command, args, input, extra_env, cwd, capture_stderr, config_args) + return self.__git_inout(command, args, input, extra_env, cwd, capture_stderr, + capture_stdout, config_args) @classmethod - def __git_inout(cls, command, args, input, extra_env, cwd, capture_stderr, config_args=None): + def __git_inout(cls, command, args, input, extra_env, cwd, capture_stderr, + capture_stdout, config_args=None): """ As _git_inout but can be used without an instance """ @@ -206,13 +208,14 @@ def __git_inout(cls, command, args, input, extra_env, cwd, capture_stderr, confi cmd = ['git'] + config_opts + [command] + args env = cls.__build_env(extra_env) + stdout_arg = subprocess.PIPE if capture_stdout else None stderr_arg = subprocess.PIPE if capture_stderr else None stdin_arg = subprocess.PIPE if input is not None else None log.debug(cmd) popen = subprocess.Popen(cmd, stdin=stdin_arg, - stdout=subprocess.PIPE, + stdout=stdout_arg, stderr=stderr_arg, env=env, close_fds=True, @@ -220,7 +223,7 @@ def __git_inout(cls, command, args, input, extra_env, cwd, capture_stderr, confi (stdout, stderr) = popen.communicate(input) return stdout, stderr, popen.returncode - def _git_command(self, command, args=[], extra_env=None): + def _git_command(self, command, args=[], extra_env=None, interactive=False): """ Execute git command with arguments args and environment env at path. @@ -232,12 +235,14 @@ def _git_command(self, command, args=[], extra_env=None): @param extra_env: extra environment variables to set when running command @type extra_env: C{dict} """ + capture_stdout = not interactive try: stdout, stderr, ret = self._git_inout(command=command, args=args, input=None, extra_env=extra_env, - capture_stderr=True) + capture_stderr=True, + capture_stdout=capture_stdout) except Exception as excobj: raise GitRepositoryError("Error running git %s: %s" % (command, excobj)) if ret: @@ -2007,7 +2012,8 @@ def create(cls, path, description=None, bare=False): input=None, extra_env=None, cwd=abspath, - capture_stderr=True) + capture_stderr=True, + capture_stdout=True) except Exception as excobj: raise GitRepositoryError("Error running git init: %s" % excobj) if ret: @@ -2073,7 +2079,8 @@ def clone(cls, path, remote, depth=0, recursive=False, mirror=False, input=None, extra_env=None, cwd=abspath, - capture_stderr=True) + capture_stderr=True, + capture_stdout=True) except Exception as excobj: raise GitRepositoryError("Error running git clone: %s" % excobj) if ret: From 8899a9747d298d90cfd5bb197486e3b36e053921 Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 6 Feb 2014 09:22:59 +0200 Subject: [PATCH 02/10] GitRepository: fix the 'edit' mode in commit methods Previously the editor would not show in the shell because stdout was captured. Signed-off-by: Markus Lehtonen --- gbp/git/repository.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/gbp/git/repository.py b/gbp/git/repository.py index 49dc2243..021ac3ea 100644 --- a/gbp/git/repository.py +++ b/gbp/git/repository.py @@ -1455,9 +1455,11 @@ def rename_file(self, old, new): #{ Comitting - def _commit(self, msg, args=[], author_info=None): + def _commit(self, msg, args=[], author_info=None, edit=False): extra_env = author_info.get_author_env() if author_info else None - self._git_command("commit", ['-q', '-m', msg] + args, extra_env=extra_env) + default_args = ['-q', '-m', msg] + (['--edit'] if edit else []) + self._git_command("commit", default_args + args, extra_env=extra_env, + interactive=edit) def commit_staged(self, msg, author_info=None, edit=False): """ @@ -1470,9 +1472,7 @@ def commit_staged(self, msg, author_info=None, edit=False): @param edit: whether to spawn an editor to edit the commit info @type edit: C{bool} """ - args = GitArgs() - args.add_true(edit, '--edit') - self._commit(msg=msg, args=args.args, author_info=author_info) + self._commit(msg=msg, author_info=author_info, edit=edit) def commit_all(self, msg, author_info=None, edit=False): """ @@ -1482,9 +1482,7 @@ def commit_all(self, msg, author_info=None, edit=False): @param author_info: authorship information @type author_info: L{GitModifier} """ - args = GitArgs('-a') - args.add_true(edit, '--edit') - self._commit(msg=msg, args=args.args, author_info=author_info) + self._commit(msg=msg, args=['-a'], author_info=author_info, edit=edit) def commit_files(self, files, msg, author_info=None): """ From 2b2064dc14d283426ff2e067f642b03dbac09e65 Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Wed, 5 Feb 2014 16:11:42 +0200 Subject: [PATCH 03/10] GitRepository.commit_files: add committer_info argument For setting the committer name/email/date - similarly to author_info. Signed-off-by: Markus Lehtonen --- gbp/git/repository.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gbp/git/repository.py b/gbp/git/repository.py index 021ac3ea..973cd1b1 100644 --- a/gbp/git/repository.py +++ b/gbp/git/repository.py @@ -1455,8 +1455,11 @@ def rename_file(self, old, new): #{ Comitting - def _commit(self, msg, args=[], author_info=None, edit=False): + def _commit(self, msg, args=[], author_info=None, + committer_info=None, edit=False): extra_env = author_info.get_author_env() if author_info else None + if committer_info: + extra_env.update(committer_info.get_committer_env()) default_args = ['-q', '-m', msg] + (['--edit'] if edit else []) self._git_command("commit", default_args + args, extra_env=extra_env, interactive=edit) @@ -1484,7 +1487,7 @@ def commit_all(self, msg, author_info=None, edit=False): """ self._commit(msg=msg, args=['-a'], author_info=author_info, edit=edit) - def commit_files(self, files, msg, author_info=None): + def commit_files(self, files, msg, author_info=None, committer_info=None): """ Commit the given files to the repository @@ -1494,10 +1497,13 @@ def commit_files(self, files, msg, author_info=None): @type msg: C{str} @param author_info: authorship information @type author_info: L{GitModifier} + @param committer_info: committer information + @type committer_info: L{GitModifier} """ if isinstance(files, str): files = [files] - self._commit(msg=msg, args=files, author_info=author_info) + self._commit(msg=msg, args=files, author_info=author_info, + committer_info=committer_info) def commit_dir(self, unpack_dir, msg, branch, other_parents=None, author={}, committer={}, create_missing_branch=False): From 66caafd7a2e141522e40181a244dfa974311706c Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Wed, 5 Feb 2014 16:11:42 +0200 Subject: [PATCH 04/10] GitRepository.commit_staged: add committer_info argument For setting the committer name/email/date - similarly to author_info. Signed-off-by: Markus Lehtonen --- gbp/git/repository.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gbp/git/repository.py b/gbp/git/repository.py index 973cd1b1..ae62b5cb 100644 --- a/gbp/git/repository.py +++ b/gbp/git/repository.py @@ -1464,7 +1464,8 @@ def _commit(self, msg, args=[], author_info=None, self._git_command("commit", default_args + args, extra_env=extra_env, interactive=edit) - def commit_staged(self, msg, author_info=None, edit=False): + def commit_staged(self, msg, author_info=None, edit=False, + committer_info=None): """ Commit currently staged files to the repository @@ -1474,8 +1475,11 @@ def commit_staged(self, msg, author_info=None, edit=False): @type author_info: L{GitModifier} @param edit: whether to spawn an editor to edit the commit info @type edit: C{bool} + @param committer_info: committer information + @type committer_info: L{GitModifier} """ - self._commit(msg=msg, author_info=author_info, edit=edit) + self._commit(msg=msg, author_info=author_info, + committer_info=committer_info, edit=edit) def commit_all(self, msg, author_info=None, edit=False): """ From c97cc034728a8d8b312a4995536db6e10732f4df Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 6 Feb 2014 11:35:42 +0200 Subject: [PATCH 05/10] rpm-ch: implement --commit option Commit the changes to Git. Using this option will commit the changelog modifications as well as all other staged changes, making it possible to easily include other changes in the commit, too. Signed-off-by: Markus Lehtonen --- docs/manpages/gbp-rpm-ch.xml | 13 +++++++++++ gbp/scripts/rpm_ch.py | 37 ++++++++++++++++++++++++++---- tests/component/rpm/test_rpm_ch.py | 22 ++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml index e0f0d9d7..71acaf87 100644 --- a/docs/manpages/gbp-rpm-ch.xml +++ b/docs/manpages/gbp-rpm-ch.xml @@ -38,6 +38,7 @@ GIT-LOG-OPTIONS EDITOR + CUSTOMIZATION-FILE [PATH1 PATH2] @@ -263,6 +264,18 @@ + + + + + + Commit changes to git after modifying changelog. Importantly, in + addition to the changelog modifications all other staged changes are + committed, too, making it possible to update other files in the same + commit. + + + CUSTOMIZATION-FILE diff --git a/gbp/scripts/rpm_ch.py b/gbp/scripts/rpm_ch.py index 9212f717..b7e49a4c 100644 --- a/gbp/scripts/rpm_ch.py +++ b/gbp/scripts/rpm_ch.py @@ -122,8 +122,9 @@ def determine_editor(options): return 'vi' -def check_branch(repo, options): - """Check the current git branch""" +def check_repo_state(repo, options): + """Check that the repository is in good state""" + # Check branch try: branch = repo.get_branch() except GitRepositoryError: @@ -133,6 +134,18 @@ def check_branch(repo, options): (options.packaging_branch, branch)) raise GbpError("Use --ignore-branch to ignore or " "--packaging-branch to set the branch name.") + # Check unstaged changes + if options.commit: + unstaged = [] + status = repo.status() + for group, files in status.items(): + if group != '??' and group[1] != ' ': + unstaged.extend([f.decode() for f in files]) + if unstaged: + gbp.log.err("Unstaged changes in:\n %s" % + '\n '.join(unstaged)) + raise GbpError("Please commit or stage your changes before using " + "the --commit option") def parse_spec_file(repo, options): @@ -307,6 +320,13 @@ def update_changelog(changelog, entries, repo, spec, options): top_section.append_entry(entry) +def commit_changelog(repo, changelog, author, committer, edit): + """Commit changelog to Git""" + repo.add_files(changelog.path) + repo.commit_staged("Update changelog", author_info=author, + committer_info=committer, edit=edit) + + def build_parser(name): """Construct command line parser""" try: @@ -322,9 +342,12 @@ def build_parser(name): "how to format the changelog entries") naming_grp = GbpOptionGroup(parser, "naming", "branch names, tag formats, directory and file naming") + commit_grp = GbpOptionGroup(parser, "commit", + "automatic committing and tagging") parser.add_option_group(range_grp) parser.add_option_group(format_grp) parser.add_option_group(naming_grp) + parser.add_option_group(commit_grp) # Non-grouped options parser.add_option("-v", "--verbose", action="store_true", dest="verbose", @@ -380,6 +403,9 @@ def build_parser(name): dest="spawn_editor") format_grp.add_config_file_option(option_name="editor-cmd", dest="editor_cmd") + # Commit group options + commit_grp.add_option("-c", "--commit", action="store_true", + help="commit changes") return parser @@ -412,7 +438,7 @@ def main(argv): editor_cmd = determine_editor(options) repo = RpmGitRepository('.') - check_branch(repo, options) + check_repo_state(repo, options) # Find and parse spec file spec = parse_spec_file(repo, options) @@ -434,13 +460,16 @@ def main(argv): entries = entries_from_commits(ch_file.changelog, repo, commits, options) update_changelog(ch_file.changelog, entries, repo, spec, options) - # Write to file ch_file.write() if editor_cmd: gbpc.Command(editor_cmd, [ch_file.path])() + if options.commit: + edit = True if editor_cmd else False + commit_changelog(repo, ch_file, None, None, edit) + except (GbpError, GitRepositoryError, ChangelogError, NoSpecError) as err: if len(err.__str__()): gbp.log.err(err) diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py index 97f5d7e6..954f0ebc 100644 --- a/tests/component/rpm/test_rpm_ch.py +++ b/tests/component/rpm/test_rpm_ch.py @@ -255,6 +255,28 @@ def test_option_changelog_revision(self): header = self.read_file('packaging/gbp-test-native.changes')[0] ok_(re.match(r'.+ foobar$', header)) + def test_option_commit(self): + """Test the --commit cmdline option""" + repo = self.init_test_repo('gbp-test') + + # Check unclean repo + with open('untracked-file', 'w') as fobj: + fobj.write('this file is not tracked\n') + with open('foo.txt', 'a') as fobj: + fobj.write('new stuff\n') + + # Unstaged file (foo.txt) -> failure + eq_(mock_ch(['--commit', '--since=HEAD^']), 1) + self._check_log(-1, 'gbp:error: Please commit or stage your changes') + + # Add file, update and commit, untracked file should be ignored + repo.add_files('foo.txt') + sha = repo.rev_parse('HEAD') + eq_(mock_ch(['--commit', '--since=HEAD^']), 0) + eq_(sha, repo.rev_parse('HEAD^')) + eq_(repo.get_commit_info('HEAD')['files'], + {'M': [b'foo.txt', b'gbp-test.spec']}) + def test_option_editor_cmd(self): """Test the --editor-cmd and --spawn-editor cmdline options""" repo = self.init_test_repo('gbp-test-native') From 526ad6ff610ef29a2bb4b7bb77a5f1e5e2c31293 Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 26 Feb 2015 16:03:31 +0200 Subject: [PATCH 06/10] rpm-ch: implement --commit-msg option Defines format string used to generate the commit message when --commit option is used. Signed-off-by: Markus Lehtonen --- docs/manpages/gbp-rpm-ch.xml | 11 +++++++++++ gbp/scripts/rpm_ch.py | 24 ++++++++++++++++++++---- tests/component/rpm/test_rpm_ch.py | 11 +++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml index 71acaf87..53c78f9f 100644 --- a/docs/manpages/gbp-rpm-ch.xml +++ b/docs/manpages/gbp-rpm-ch.xml @@ -39,6 +39,7 @@ EDITOR + MSG-FORMAT CUSTOMIZATION-FILE [PATH1 PATH2] @@ -276,6 +277,16 @@ + + MSG-FORMAT + + + + Format string for the commit message when committing changes + (when is given). + + + CUSTOMIZATION-FILE diff --git a/gbp/scripts/rpm_ch.py b/gbp/scripts/rpm_ch.py index b7e49a4c..db1f8221 100644 --- a/gbp/scripts/rpm_ch.py +++ b/gbp/scripts/rpm_ch.py @@ -320,11 +320,24 @@ def update_changelog(changelog, entries, repo, spec, options): top_section.append_entry(entry) -def commit_changelog(repo, changelog, author, committer, edit): +def create_commit_message(spec, options): + """Generate commit message""" + fields = spec.version + fields['version'] = compose_version_str(spec.version) + fields['vendor'] = options.vendor + fields['pkg'] = spec.name + try: + return options.commit_msg % fields + except KeyError as err: + raise GbpError("Unknown key %s in commit-msg string, " + "only %s are accepted" % (err, fields.keys())) + + +def commit_changelog(repo, changelog, message, author, committer, edit): """Commit changelog to Git""" repo.add_files(changelog.path) - repo.commit_staged("Update changelog", author_info=author, - committer_info=committer, edit=edit) + repo.commit_staged(message, author_info=author, committer_info=committer, + edit=edit) def build_parser(name): @@ -406,6 +419,8 @@ def build_parser(name): # Commit group options commit_grp.add_option("-c", "--commit", action="store_true", help="commit changes") + commit_grp.add_config_file_option(option_name="commit-msg", + dest="commit_msg") return parser @@ -468,7 +483,8 @@ def main(argv): if options.commit: edit = True if editor_cmd else False - commit_changelog(repo, ch_file, None, None, edit) + msg = create_commit_message(spec, options) + commit_changelog(repo, ch_file, msg, None, None, edit) except (GbpError, GitRepositoryError, ChangelogError, NoSpecError) as err: if len(err.__str__()): diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py index 954f0ebc..60b542ab 100644 --- a/tests/component/rpm/test_rpm_ch.py +++ b/tests/component/rpm/test_rpm_ch.py @@ -277,6 +277,17 @@ def test_option_commit(self): eq_(repo.get_commit_info('HEAD')['files'], {'M': [b'foo.txt', b'gbp-test.spec']}) + def test_option_commit_msg(self): + """Test the --commit-msg cmdline option""" + repo = self.init_test_repo('gbp-test2') + + eq_(mock_ch(['--commit', '--since=HEAD^', '--commit-msg=Foo']), 0) + eq_(repo.get_commit_info('HEAD')['subject'], 'Foo') + + # Unknown key in format string causes failure + eq_(mock_ch(['--commit', '--since=HEAD^', '--commit-msg=%(foo)s']), 1) + self._check_log(-1, "gbp:error: Unknown key 'foo' in commit-msg string") + def test_option_editor_cmd(self): """Test the --editor-cmd and --spawn-editor cmdline options""" repo = self.init_test_repo('gbp-test-native') From 5942338556e7dd11b90752e2c2ca1f19c58c73df Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Thu, 6 Feb 2014 11:35:42 +0200 Subject: [PATCH 07/10] rpm-ch: implement tagging options Implement '--tag' command line option (and other related options for e.g. signing) for creating and tagging a release. These correspond the tagging options in gbp-buildpackage-rpm. A slight difference is that gbp buildpackage-rpm never commits anything to git. However, in rpm-ch the '--tag' option automatically enables --commit which causes the changelog modifications (and, all other staged changes) to be committed to git before creating the tag. This makes it possible to create a release and document the packaging/release tag name in the rpm changelog. Signed-off-by: Markus Lehtonen --- docs/manpages/gbp-rpm-ch.xml | 57 ++++++++++++++++++++++++++++++ gbp/scripts/rpm_ch.py | 46 ++++++++++++++++++++---- tests/component/rpm/test_rpm_ch.py | 42 ++++++++++++++++++++++ 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml index 53c78f9f..70cc28bf 100644 --- a/docs/manpages/gbp-rpm-ch.xml +++ b/docs/manpages/gbp-rpm-ch.xml @@ -40,6 +40,11 @@ EDITOR MSG-FORMAT + + + MSG-FORMAT + + GPG-KEYID CUSTOMIZATION-FILE [PATH1 PATH2] @@ -287,6 +292,58 @@ + + + + + + Commit the changes and create a packaging (release) tag. Similarly to + , all staged changes are committed to git + before creating the tag. This option makes it possible to create a + release and correctly document the the tag name in the rpm changelog + (by using %(tagname)s in the + string). + + + + + + + + + Don't fail tag operations if a tag with the same version already + exists, but, overwrite the existing tag, instead. + + + + + MSG-FORMAT + + + + Format string for the tag message + (when is given). + + + + + + + + + GPG sign all created tags. + + + + + GPG-KEYID + + + + Use this keyid for gpg signing tags. + + + CUSTOMIZATION-FILE diff --git a/gbp/scripts/rpm_ch.py b/gbp/scripts/rpm_ch.py index db1f8221..d7da0fc9 100644 --- a/gbp/scripts/rpm_ch.py +++ b/gbp/scripts/rpm_ch.py @@ -29,11 +29,13 @@ import gbp.log from gbp.config import GbpOptionParserRpm, GbpOptionGroup from gbp.errors import GbpError +from gbp.git.modifier import GitModifier from gbp.rpm import (guess_spec, NoSpecError, SpecFile, split_version_str, compose_version_str) from gbp.rpm.changelog import Changelog, ChangelogParser, ChangelogError from gbp.rpm.git import GitRepositoryError, RpmGitRepository from gbp.rpm.policy import RpmPkgPolicy +from gbp.scripts.buildpackage_rpm import packaging_tag_data from gbp.scripts.common import ExitCodes from gbp.tmpfile import init_tmpdir, del_tmpdir @@ -145,7 +147,7 @@ def check_repo_state(repo, options): gbp.log.err("Unstaged changes in:\n %s" % '\n '.join(unstaged)) raise GbpError("Please commit or stage your changes before using " - "the --commit option") + "the --commit or --tag option") def parse_spec_file(repo, options): @@ -294,11 +296,21 @@ def update_changelog(changelog, entries, repo, spec, options): # Get info for section header now = datetime.now() name, email = get_author(repo, options.git_author) + author = None + committer = None rev_str_fields = dict(spec.version, version=compose_version_str(spec.version), - vendor=options.vendor, - tagname=repo.describe('HEAD', longfmt=True, - always=True)) + vendor=options.vendor) + if options.tag: + # Get fake information for the to-be-created git commit + author = committer = GitModifier(date=now) + tag, msg = packaging_tag_data(repo, 'HEAD', spec.name, spec.version, + options) + else: + tag = repo.describe('HEAD', longfmt=True, always=True) + msg = None + rev_str_fields['tagname'] = tag + try: revision = options.changelog_revision % rev_str_fields except KeyError as err: @@ -318,6 +330,7 @@ def update_changelog(changelog, entries, repo, spec, options): # Add new entries to the topmost section for entry in entries: top_section.append_entry(entry) + return (tag, msg, author, committer) def create_commit_message(spec, options): @@ -387,6 +400,8 @@ def build_parser(name): dest="packaging_branch") naming_grp.add_config_file_option(option_name="packaging-tag", dest="packaging_tag") + naming_grp.add_config_file_option(option_name="packaging-tag-msg", + dest="packaging_tag_msg") naming_grp.add_config_file_option(option_name="packaging-dir", dest="packaging_dir") naming_grp.add_config_file_option(option_name="changelog-file", @@ -416,11 +431,19 @@ def build_parser(name): dest="spawn_editor") format_grp.add_config_file_option(option_name="editor-cmd", dest="editor_cmd") - # Commit group options + # Commit/tag group options commit_grp.add_option("-c", "--commit", action="store_true", help="commit changes") commit_grp.add_config_file_option(option_name="commit-msg", dest="commit_msg") + commit_grp.add_option("--tag", action="store_true", + help="commit the changes and create a" + "packaging/release tag") + commit_grp.add_option("--retag", action="store_true", + help="Overwrite packaging tag if it already exists") + commit_grp.add_boolean_config_file_option(option_name="sign-tags", + dest="sign_tags") + commit_grp.add_config_file_option(option_name="keyid", dest="keyid") return parser @@ -432,6 +455,8 @@ def parse_args(argv): options, args = parser.parse_args(argv[1:]) + if options.tag: + options.commit = True if not options.changelog_revision: options.changelog_revision = RpmPkgPolicy.Changelog.header_rev_format @@ -474,7 +499,9 @@ def main(argv): # Do the actual update entries = entries_from_commits(ch_file.changelog, repo, commits, options) - update_changelog(ch_file.changelog, entries, repo, spec, options) + tag, tag_msg, author, committer = update_changelog(ch_file.changelog, + entries, repo, spec, + options) # Write to file ch_file.write() @@ -484,7 +511,12 @@ def main(argv): if options.commit: edit = True if editor_cmd else False msg = create_commit_message(spec, options) - commit_changelog(repo, ch_file, msg, None, None, edit) + commit_changelog(repo, ch_file, msg, author, committer, edit) + if options.tag: + if options.retag and repo.has_tag(tag): + repo.delete_tag(tag) + repo.create_tag(tag, tag_msg, 'HEAD', options.sign_tags, + options.keyid) except (GbpError, GitRepositoryError, ChangelogError, NoSpecError) as err: if len(err.__str__()): diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py index 60b542ab..bc57d8be 100644 --- a/tests/component/rpm/test_rpm_ch.py +++ b/tests/component/rpm/test_rpm_ch.py @@ -288,6 +288,48 @@ def test_option_commit_msg(self): eq_(mock_ch(['--commit', '--since=HEAD^', '--commit-msg=%(foo)s']), 1) self._check_log(-1, "gbp:error: Unknown key 'foo' in commit-msg string") + def test_tagging(self): + """Test commiting/tagging""" + repo = self.init_test_repo('gbp-test-native') + + # Update and commit+tag + eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 0) + ok_(repo.has_tag('new-tag')) + sha = repo.rev_parse('HEAD') + eq_(sha, repo.rev_parse('new-tag^0')) + + # Should fail if the tag already exists + eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 1) + + # Update and commit+tag + eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^', + '--retag']), 0) + ok_(repo.has_tag('new-tag')) + sha2 = repo.rev_parse('HEAD') + ok_(sha2 != sha) + eq_(sha2, repo.rev_parse('new-tag^0')) + + def test_tagging2(self): + """Test commiting/tagging spec file""" + repo = self.init_test_repo('gbp-test2') + + # Check unclean repo + with open('untracked-file', 'w') as fobj: + fobj.write('this file is not tracked\n') + with open('README', 'a') as fobj: + fobj.write('some new content\n') + + # Unstaged file (README) -> failure + eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 1) + self._check_log(-1, 'gbp:error: Please commit or stage your changes') + + # Add file, update and commit+tag, untracked file should be ignored + repo.add_files('README') + eq_(mock_ch(['--tag', '--packaging-tag=new-tag', '--since=HEAD^']), 0) + ok_(repo.has_tag('new-tag')) + sha = repo.rev_parse('HEAD') + eq_(sha, repo.rev_parse('new-tag^0')) + def test_option_editor_cmd(self): """Test the --editor-cmd and --spawn-editor cmdline options""" repo = self.init_test_repo('gbp-test-native') From c5db5daf0a4097efb4db64f3281d9179d06b157a Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Wed, 26 Mar 2014 09:01:28 +0200 Subject: [PATCH 08/10] rpm-ch: add --message cmdline option This option can be used to directly give the text for new changelog entry, skipping auto-generation from git commit history entirely. Thus, '--since' or '--all' do not have any effect when '--message' is defined. Signed-off-by: Markus Lehtonen --- docs/manpages/gbp-rpm-ch.xml | 16 +++++++++- gbp/scripts/rpm_ch.py | 49 ++++++++++++++++++++++-------- tests/component/rpm/test_rpm_ch.py | 11 +++++++ 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml index 70cc28bf..86e4c766 100644 --- a/docs/manpages/gbp-rpm-ch.xml +++ b/docs/manpages/gbp-rpm-ch.xml @@ -28,7 +28,10 @@ DIRECTORY FILEPATH FILEPATH - COMMITISH + + MESSAGE + COMMITISH + @@ -270,6 +273,17 @@ + + + + + + Text to use for new changelog entries. Git history and the commit + messages, including and + options are ignored in this case. + + + diff --git a/gbp/scripts/rpm_ch.py b/gbp/scripts/rpm_ch.py index d7da0fc9..18656ec4 100644 --- a/gbp/scripts/rpm_ch.py +++ b/gbp/scripts/rpm_ch.py @@ -291,6 +291,37 @@ def entries_from_commits(changelog, repo, commits, options): return entries +def entries_from_text(changelog, text, author): + """Generate a list of changelog entries from a string""" + entries = [] + # Use current user as the author for all entries + for line in text.splitlines(): + if line.strip(): + entry_text = "- %s" % line.strip() + entries.append(changelog.create_entry(author=author, + text=entry_text)) + return entries + + +def generate_new_entries(changelog, repo, options, args): + """Generate new entries to be appended to changelog""" + if options.message: + author = get_author(repo, options.git_author)[0] + entries = entries_from_text(changelog, options.message, author) + else: + # Get range of commits from where to generate changes + since = get_start_commit(changelog, repo, options) + if args: + gbp.log.info("Only looking for changes in '%s'" % ", ".join(args)) + commits = repo.get_commits(since=since, until='HEAD', paths=args, + options=options.git_log.split(" ")) + commits.reverse() + if not commits: + gbp.log.info("No changes detected from %s to %s." % (since, 'HEAD')) + entries = entries_from_commits(changelog, repo, commits, options) + return entries + + def update_changelog(changelog, entries, repo, spec, options): """Update the changelog with a range of commits""" # Get info for section header @@ -431,6 +462,9 @@ def build_parser(name): dest="spawn_editor") format_grp.add_config_file_option(option_name="editor-cmd", dest="editor_cmd") + format_grp.add_option("-m", '--message', + help="text to use as new changelog entries - git commit " + "messages and the --since are ignored in this case") # Commit/tag group options commit_grp.add_option("-c", "--commit", action="store_true", help="commit changes") @@ -485,27 +519,18 @@ def main(argv): # Find and parse changelog file ch_file = parse_changelog_file(repo, spec, options) - since = get_start_commit(ch_file.changelog, repo, options) - # Get range of commits from where to generate changes - if args: - gbp.log.info("Only looking for changes in '%s'" % ", ".join(args)) - commits = repo.get_commits(since=since, until='HEAD', paths=args, - options=options.git_log.split(" ")) - commits.reverse() - if not commits: - gbp.log.info("No changes detected from %s to %s." % (since, 'HEAD')) + # Get new entries + entries = generate_new_entries(ch_file.changelog, repo, options, args) # Do the actual update - entries = entries_from_commits(ch_file.changelog, repo, commits, - options) tag, tag_msg, author, committer = update_changelog(ch_file.changelog, entries, repo, spec, options) # Write to file ch_file.write() - if editor_cmd: + if editor_cmd and not options.message: gbpc.Command(editor_cmd, [ch_file.path])() if options.commit: diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py index bc57d8be..91c2358b 100644 --- a/tests/component/rpm/test_rpm_ch.py +++ b/tests/component/rpm/test_rpm_ch.py @@ -343,6 +343,17 @@ def test_option_editor_cmd(self): eq_(mock_ch(['--spawn-editor=always', '--editor-cmd=']), 0) + def test_option_message(self): + """Test the --message cmdline option""" + self.init_test_repo('gbp-test-native') + orig_content = self.read_file('packaging/gbp-test-native.changes') + + eq_(mock_ch(['--message', 'my entry\nanother entry']), 0) + content = self.read_file('packaging/gbp-test-native.changes') + # Added header, two entries and a blank line + eq_(len(content), len(orig_content) + 4) + eq_(content[2], '- another entry\n') + def test_user_customizations(self): """Test the user customizations""" repo = self.init_test_repo('gbp-test-native') From 9a7c787b565b0ac109d2ba12ebbf07d3ed6aa2a7 Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Fri, 16 May 2014 14:26:49 +0300 Subject: [PATCH 09/10] rpm-ch: implement --all option If defined, rpm-ch uses all commits from the Git history. This can be useful when creating a new changelog from scratch. Using '--all' causes '--since' to be ignored. Signed-off-by: Markus Lehtonen --- docs/manpages/gbp-rpm-ch.xml | 11 +++++++++++ gbp/scripts/rpm_ch.py | 9 +++++++-- tests/component/rpm/test_rpm_ch.py | 10 ++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml index 86e4c766..b1fb17bf 100644 --- a/docs/manpages/gbp-rpm-ch.xml +++ b/docs/manpages/gbp-rpm-ch.xml @@ -29,6 +29,7 @@ FILEPATH FILEPATH + MESSAGE COMMITISH @@ -161,6 +162,16 @@ + + + + + + Use all commits from the Git history, overrides + . + + + COMMITTISH diff --git a/gbp/scripts/rpm_ch.py b/gbp/scripts/rpm_ch.py index 18656ec4..29dc54fa 100644 --- a/gbp/scripts/rpm_ch.py +++ b/gbp/scripts/rpm_ch.py @@ -238,7 +238,9 @@ def guess_commit(section, repo, options): def get_start_commit(changelog, repo, options): """Get the start commit from which to generate new entries""" - if options.since: + if options.all: + since = None + elif options.since: since = options.since else: if changelog.sections: @@ -247,7 +249,7 @@ def get_start_commit(changelog, repo, options): since = None if not since: raise GbpError("Couldn't determine starting point from " - "changelog, please use the '--since' option") + "changelog, please use the '--since' or '--all'") gbp.log.info("Continuing from commit '%s'" % since) return since @@ -441,6 +443,9 @@ def build_parser(name): # Range group options range_grp.add_option("-s", "--since", dest="since", help="commit to start from (e.g. HEAD^^^, release/0.1.2)") + range_grp.add_option("--all", action="store_true", + help="use all commits from the Git history, overrides " + "--since") # Formatting group options format_grp.add_option("--no-release", action="store_false", default=True, dest="release", diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py index 91c2358b..e640bb01 100644 --- a/tests/component/rpm/test_rpm_ch.py +++ b/tests/component/rpm/test_rpm_ch.py @@ -109,6 +109,16 @@ def test_create_changes_file(self): # Should contain 3 lines (header, 1 entry and an empty line) eq_(len(content), 3) + def test_option_all(self): + """Test the --all cmdline option""" + repo = self.init_test_repo('gbp-test2') + + eq_(mock_ch(['--changelog-file=CHANGES', '--all']), 0) + content = self.read_file('packaging/gbp-test2.changes') + # Should contain N+2 lines (header, N commits and an empty line) + commit_cnt = len(repo.get_commits(since=None, until='master')) + eq_(len(content), commit_cnt + 2) + def test_option_changelog_file(self): """Test the --changelog-file cmdline option""" repo = self.init_test_repo('gbp-test-native') From b14ee9bfae268d0e326cf3c4a790ef3115f640e3 Mon Sep 17 00:00:00 2001 From: Markus Lehtonen Date: Fri, 6 Jun 2014 12:40:32 +0300 Subject: [PATCH 10/10] rpm-ch: implement --meta-bts option This gives the user the possibility to define what meta tags (in git commit message) gbp-rpm-ch recognizes as bug tracking system references. Alternatively, it makes it possible to disable bts meta tag parsing altogether. Signed-off-by: Markus Lehtonen --- docs/manpages/gbp-rpm-ch.xml | 16 ++++++++++++++++ gbp/config.py | 3 +++ gbp/rpm/policy.py | 17 +++++++++-------- gbp/scripts/rpm_ch.py | 4 +++- tests/component/rpm/test_rpm_ch.py | 20 ++++++++++++++++++++ 5 files changed, 51 insertions(+), 9 deletions(-) diff --git a/docs/manpages/gbp-rpm-ch.xml b/docs/manpages/gbp-rpm-ch.xml index b1fb17bf..b7cb47ce 100644 --- a/docs/manpages/gbp-rpm-ch.xml +++ b/docs/manpages/gbp-rpm-ch.xml @@ -33,6 +33,7 @@ MESSAGE COMMITISH + META_TAGS @@ -182,6 +183,19 @@ + + META_TAGS + + + + Meta tags in the commit messages that are interpreted as bug tracking + system related references. The recognized bts references are added in + the generated changelog entries. See the META TAGS section below for + more information. The bts meta tag tracking feature can be disabled + by defining an empty string. + + + @@ -416,6 +430,8 @@ Indicate in the changelog entry that bug BUGNUMBER was addressed in this commit. + The bts meta tags recognized by &gbp-rpm-ch; is actually defined by + the option. diff --git a/gbp/config.py b/gbp/config.py index 76a850de..b062248d 100644 --- a/gbp/config.py +++ b/gbp/config.py @@ -798,6 +798,7 @@ class GbpOptionParserRpm(GbpOptionParser): 'changelog-revision': '', 'spawn-editor': 'always', 'editor-cmd': 'vim', + 'meta-bts': '(Close|Closes|Fixes|Fix)', 'spec-vcs-tag': '', }) @@ -860,6 +861,8 @@ class GbpOptionParserRpm(GbpOptionParser): 'git-author': "Use name and email from git-config for the changelog header, " "default is '%(git-author)s'", + 'meta-bts': + "Meta tags for the bts commands, default is '%(meta-bts)s'", 'spec-vcs-tag': "Set/update the 'VCS:' tag in the spec file, empty value " "removes the tag entirely, default is '%(spec-vcs-tag)s'", diff --git a/gbp/rpm/policy.py b/gbp/rpm/policy.py index a027ed99..791b25b1 100644 --- a/gbp/rpm/policy.py +++ b/gbp/rpm/policy.py @@ -94,8 +94,6 @@ class ChangelogEntryFormatter(object): # Maximum length for a changelog entry line max_entry_line_length = 76 - # Bug tracking system related meta tags recognized from git commit msg - bts_meta_tags = ("Close", "Closes", "Fixes", "Fix") # Regexp for matching bug tracking system ids (e.g. "bgo#123") bug_id_re = r'[A-Za-z0-9#_\-]+' @@ -107,15 +105,18 @@ def _parse_bts_tags(cls, lines, meta_tags): @param lines: commit message @type lines: C{list} of C{str} - @param meta_tags: meta tags to look for - @type meta_tags: C{tuple} of C{str} + @param meta_tags: meta tags (regexp) to look for + @type meta_tags: C{str} @return: bts-ids per meta tag and the non-mathced lines @rtype: (C{dict}, C{list} of C{str}) """ + if not meta_tags: + return ({}, lines[:]) + tags = {} other_lines = [] - bts_re = re.compile(r'^(?P%s):\s*(?P.*)' % - ('|'.join(meta_tags)), re.I) + bts_re = re.compile(r'^(?P%s):\s*(?P.*)' % meta_tags, + re.I) bug_id_re = re.compile(cls.bug_id_re) for line in lines: match = bts_re.match(line) @@ -172,7 +173,7 @@ def compose(cls, commit_info, **kwargs): return None # Parse and filter out bts-related meta-tags - bts_tags, body = cls._parse_bts_tags(body, cls.bts_meta_tags) + bts_tags, body = cls._parse_bts_tags(body, kwargs['meta_bts']) # Additional filtering body = cls._extra_filter(body, kwargs['ignore_re']) @@ -191,7 +192,7 @@ def compose(cls, commit_info, **kwargs): text.extend([" " + line for line in body if line.strip()]) # Add bts tags and ids in the end - for tag, ids in bts_tags.items(): + for tag, ids in sorted(bts_tags.items()): bts_msg = " (%s: %s)" % (tag, ', '.join(ids)) if len(text[-1]) + len(bts_msg) >= cls.max_entry_line_length: text.append(" ") diff --git a/gbp/scripts/rpm_ch.py b/gbp/scripts/rpm_ch.py index 29dc54fa..a60ba96f 100644 --- a/gbp/scripts/rpm_ch.py +++ b/gbp/scripts/rpm_ch.py @@ -286,7 +286,8 @@ def entries_from_commits(changelog, repo, commits, options): info = repo.get_commit_info(commit) entry_text = ChangelogEntryFormatter.compose(info, full=options.full, ignore_re=options.ignore_regex, - id_len=options.idlen) + id_len=options.idlen, + meta_bts=options.meta_bts) if entry_text: entries.append(changelog.create_entry(author=info['author'].name, text=entry_text)) @@ -447,6 +448,7 @@ def build_parser(name): help="use all commits from the Git history, overrides " "--since") # Formatting group options + format_grp.add_config_file_option(option_name="meta-bts", dest="meta_bts") format_grp.add_option("--no-release", action="store_false", default=True, dest="release", help="no release, just update the last changelog section") diff --git a/tests/component/rpm/test_rpm_ch.py b/tests/component/rpm/test_rpm_ch.py index e640bb01..197f6db9 100644 --- a/tests/component/rpm/test_rpm_ch.py +++ b/tests/component/rpm/test_rpm_ch.py @@ -173,6 +173,26 @@ def test_branch_options(self): eq_(mock_ch(['--packaging-branch=foo', '--ignore-branch']), 0) + def test_option_meta_bts(self): + """Test parsing of the bts meta tags""" + repo = self.init_test_repo('gbp-test-native') + + # Create a dummy commit that references bts + with open('new-file', 'w') as fobj: + fobj.write('foobar\n') + repo.add_files('new-file') + repo.commit_all('Fix\n\nCloses: #123\nFixes: #456\n Fixes: #789') + + eq_(mock_ch(['--since=HEAD^']), 0) + content = self.read_file('packaging/gbp-test-native.changes') + # rpm-ch shouldn't have picked the ref with leading whitespace + eq_(content[1], '- Fix (Closes: #123) (Fixes: #456)\n') + + # Check the --meta-bts option + eq_(mock_ch(['--since=HEAD^', '--meta-bts=Fixes']), 0) + content = self.read_file('packaging/gbp-test-native.changes') + eq_(content[1], '- Fix (Fixes: #456)\n') + def test_option_no_release(self): """Test the --no-release cmdline option""" self.init_test_repo('gbp-test-native')