diff options
author | Markus Lehtonen <markus.lehtonen@linux.intel.com> | 2012-01-12 15:38:29 +0200 |
---|---|---|
committer | Markus Lehtonen <markus.lehtonen@linux.intel.com> | 2014-06-05 14:20:04 +0300 |
commit | fa1041859e7d382a0c7a26d09236202a3663524b (patch) | |
tree | c29196a261ae5cee2ecfe39896dfc1cca3f2fd99 | |
parent | 103d54aa93faaa47f6dbef28e9a794b0c1255e1b (diff) | |
download | git-buildpackage-fa1041859e7d382a0c7a26d09236202a3663524b.tar.gz git-buildpackage-fa1041859e7d382a0c7a26d09236202a3663524b.tar.bz2 git-buildpackage-fa1041859e7d382a0c7a26d09236202a3663524b.zip |
Introduce gbp-pq-rpm tool
Initial version of gbp-pq-rpm: a tool for managing patch queues for rpm
packages.
Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
Signed-off-by: Olev Kartau <olev.kartau@intel.com>
-rwxr-xr-x | bin/gbp-pq-rpm | 5 | ||||
-rw-r--r-- | gbp/config.py | 3 | ||||
-rwxr-xr-x | gbp/scripts/pq_rpm.py | 386 | ||||
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | tests/01_test_help.py | 3 |
5 files changed, 398 insertions, 2 deletions
diff --git a/bin/gbp-pq-rpm b/bin/gbp-pq-rpm new file mode 100755 index 00000000..528020df --- /dev/null +++ b/bin/gbp-pq-rpm @@ -0,0 +1,5 @@ +#! /usr/bin/python -u +import sys +from gbp.scripts.pq_rpm import main + +sys.exit(main(sys.argv)) diff --git a/gbp/config.py b/gbp/config.py index 27fe6288..50568a38 100644 --- a/gbp/config.py +++ b/gbp/config.py @@ -581,6 +581,7 @@ class GbpOptionParserRpm(GbpOptionParser): 'cleaner' : '', 'packaging-dir' : '', 'packaging-tag' : 'packaging/%(version)s', + 'spec-file' : 'auto', } ) help = dict(GbpOptionParser.help) @@ -589,6 +590,8 @@ class GbpOptionParserRpm(GbpOptionParser): "subdir where packaging files are stored, default is '%(packaging-dir)s'", 'packaging-tag': "format string for packaging tags, rpm counterpart of the 'debian-tag' option, default is '%(packaging-tag)s'", + 'spec-file': + "Spec file to use, 'auto' makes gbp to guess, other values make the packaging-dir option to be ignored, default is '%(spec-file)s'", } ) # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·: diff --git a/gbp/scripts/pq_rpm.py b/gbp/scripts/pq_rpm.py new file mode 100755 index 00000000..4287cdc0 --- /dev/null +++ b/gbp/scripts/pq_rpm.py @@ -0,0 +1,386 @@ +# vim: set fileencoding=utf-8 : +# +# (C) 2011 Guido Günther <agx@sigxcpu.org> +# (C) 2012 Intel Corporation <markus.lehtonen@linux.intel.com> +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +"""manage patches in a patch queue""" + +import ConfigParser +import errno +import os +import shutil +import sys +import tempfile +import re +from gbp.config import (GbpOptionParserRpm, GbpOptionGroup) +from gbp.rpm.git import (GitRepositoryError, RpmGitRepository) +from gbp.git import GitModifier +from gbp.command_wrappers import (Command, GitCommand, RunAtCommand, + CommandExecFailed) +from gbp.errors import GbpError +import gbp.log +from gbp.patch_series import (PatchSeries, Patch) +from gbp.rpm import (SpecFile, guess_spec) +from gbp.scripts.common.pq import (is_pq_branch, pq_branch_name, pq_branch_base, + parse_gbp_commands, format_patch, + switch_to_pq_branch, apply_single_patch, + apply_and_commit_patch, drop_pq) + + +def generate_patches(repo, start, end, outdir, options): + """ + Generate patch files from git + """ + gbp.log.info("Generating patches from git (%s..%s)" % (start, end)) + patches = [] + commands = {} + for treeish in [start, end]: + if not repo.has_treeish(treeish): + raise GbpError('%s not a valid tree-ish' % treeish) + + # Generate patches + for commit in reversed(repo.get_commits(start, end)): + info = repo.get_commit_info(commit) + cmds = parse_gbp_commands(info, 'gbp-rpm', ('ignore'), None)[0] + if not 'ignore' in cmds: + patch_fn = format_patch(outdir, repo, info, patches, + options.patch_numbers) + if patch_fn: + commands[os.path.basename(patch_fn)] = cmds + else: + gbp.log.info('Ignoring commit %s' % info['id']) + + return patches, commands + + +def rm_patch_files(spec): + """ + Delete the patch files listed in the spec files. Doesn't delete patches + marked as not maintained by gbp. + """ + # Remove all old patches from the spec dir + for n, p in spec.patches.iteritems(): + if p['autoupdate']: + f = os.path.join(spec.specdir, p['filename']) + gbp.log.debug("Removing '%s'" % f) + try: + os.unlink(f) + except OSError, (e, msg): + if e != errno.ENOENT: + raise GbpError, "Failed to remove patch: %s" % msg + else: + gbp.log.debug("%s does not exist." % f) + + +def update_patch_series(repo, spec, start, end, options): + """ + Export patches to packaging directory and update spec file accordingly. + """ + # Unlink old patch files and generate new patches + rm_patch_files(spec) + + patches, _commands = generate_patches(repo, start, end, + spec.specdir, options) + spec.update_patches(patches) + spec.write_spec_file() + + +def export_patches(repo, branch, options): + """Export patches from the pq branch into a packaging branch""" + if is_pq_branch(branch, options): + base = pq_branch_base(branch, options) + gbp.log.info("On '%s', switching to '%s'" % (branch, base)) + branch = base + repo.set_branch(branch) + + pq_branch = pq_branch_name(branch, options) + + # Find and parse .spec file + try: + if options.spec_file != 'auto': + specfilename = options.spec_file + options.packaging_dir = os.path.dirname(specfilename) + else: + specfilename = guess_spec(options.packaging_dir, + True, + os.path.basename(repo.path) + '.spec') + spec = SpecFile(specfilename) + except KeyError: + raise GbpError, "Can't parse spec" + + # Find upstream version + upstream_commit = repo.find_version(options.upstream_tag, spec.version) + if not upstream_commit: + raise GbpError, ("Couldn't find upstream version %s. Don't know on what base to import." % spec.version) + + update_patch_series(repo, spec, upstream_commit, pq_branch, options) + + GitCommand('status')(['--', spec.specdir]) + + +def safe_patches(queue, tmpdir_base): + """ + Safe the current patches in a temporary directory + below 'tmpdir_base' + + @param queue: an existing patch queue + @param tmpdir_base: base under which to create tmpdir + @return: tmpdir and a safed queue (with patches in tmpdir) + @rtype: tuple + """ + + tmpdir = tempfile.mkdtemp(dir=tmpdir_base, prefix='gbp-pq') + safequeue=PatchSeries() + + if len(queue) > 0: + gbp.log.debug("Safeing patches '%s' in '%s'" % (os.path.dirname(queue[0].path), tmpdir)) + for p in queue: + dst = os.path.join(tmpdir, os.path.basename(p.path)) + shutil.copy(p.path, dst) + safequeue.append(p) + safequeue[-1].path = dst; + + return (tmpdir, safequeue) + + +def get_packager(spec): + """Get packager information from spec""" + if spec.packager: + match = re.match(r'(?P<name>.*[^ ])\s*<(?P<email>\S*)>', + spec.packager.strip()) + if match: + return GitModifier(match.group('name'), match.group('email')) + return GitModifier() + + +def import_spec_patches(repo, branch, options): + """ + apply a series of patches in a spec/packaging dir to branch + the patch-queue branch for 'branch' + + @param repo: git repository to work on + @param branch: branch to base pqtch queue on + @param options: command options + """ + tmpdir = None + + if is_pq_branch(branch, options): + if options.force: + branch = pq_branch_base(branch, options) + pq_branch = pq_branch_name(branch, options) + repo.checkout(branch) + else: + gbp.log.err("Already on a patch-queue branch '%s' - doing nothing." % branch) + raise GbpError + else: + pq_branch = pq_branch_name(branch, options) + + if repo.has_branch(pq_branch): + if options.force: + drop_pq(repo, branch, options) + else: + raise GbpError, ("Patch queue branch '%s'. already exists. Try 'rebase' instead." + % pq_branch) + + # Find and parse .spec file + try: + if options.spec_file != 'auto': + specfilename = options.spec_file + options.packaging_dir = os.path.dirname(specfilename) + else: + specfilename = guess_spec(options.packaging_dir, + True, + os.path.basename(repo.path) + '.spec') + spec = SpecFile(specfilename) + except KeyError: + raise GbpError, "Can't parse spec" + + # Find upstream version + commit = repo.find_version(options.upstream_tag, spec.version) + if commit: + commits=[commit] + else: + raise GbpError, ("Couldn't find upstream version %s. Don't know on what base to import." % spec.version) + + queue = spec.patchseries() + packager = get_packager(spec) + # Put patches in a safe place + tmpdir, queue = safe_patches(queue, repo.path) + for commit in commits: + try: + gbp.log.info("Trying to apply patches at '%s'" % commit) + repo.create_branch(pq_branch, commit) + except GitRepositoryError: + raise GbpError, ("Cannot create patch-queue branch '%s'." % pq_branch) + + repo.set_branch(pq_branch) + for patch in queue: + gbp.log.debug("Applying %s" % patch.path) + try: + apply_and_commit_patch(repo, patch, packager) + except (GbpError, GitRepositoryError): + repo.set_branch(branch) + repo.delete_branch(pq_branch) + break + else: + # All patches applied successfully + break + else: + raise GbpError, "Couldn't apply patches" + + if tmpdir: + gbp.log.debug("Remove temporary patch safe '%s'" % tmpdir) + shutil.rmtree(tmpdir) + + repo.set_branch(branch) + + return os.path.basename(spec.specfile) + + +def rebase_pq(repo, branch, options): + if is_pq_branch(branch, options): + base = pq_branch_base(branch, options) + gbp.log.info("On '%s', switching to '%s'" % (branch, base)) + branch = base + repo.set_branch(branch) + + # Find and parse .spec file + try: + if options.spec_file != 'auto': + specfilename = options.spec_file + options.packaging_dir = os.path.dirname(specfilename) + else: + specfilename = guess_spec(options.packaging_dir, + True, + os.path.basename(repo.path) + '.spec') + spec = SpecFile(specfilename) + except KeyError: + raise GbpError, "Can't parse spec" + + # Find upstream version + upstream_commit = repo.find_version(options.upstream_tag, spec.version) + if not upstream_commit: + raise GbpError, ("Couldn't find upstream version %s. Don't know on what base to import." % spec.version) + + switch_to_pq_branch(repo, branch) + GitCommand("rebase")([upstream_commit]) + + +def switch_pq(repo, current, options): + """Switch to patch-queue branch if on base branch and vice versa""" + if is_pq_branch(current, options): + base = pq_branch_base(current, options) + gbp.log.info("Switching to branch '%s'" % base) + repo.checkout(base) + else: + switch_to_pq_branch(repo, current, options) + + +def main(argv): + retval = 0 + + try: + parser = GbpOptionParserRpm(command=os.path.basename(argv[0]), prefix='', + usage="%prog [options] action - maintain patches on a patch queue branch\n" + "Actions:\n" + " export Export the patch queue / devel branch associated to the\n" + " current branch into a patch series in and update the spec file\n" + " import Create a patch queue / devel branch from spec file\n" + " and patches in current dir.\n" + " rebase Switch to patch queue / devel branch associated to the current\n" + " branch and rebase against upstream.\n" + " drop Drop (delete) the patch queue /devel branch associated to\n" + " the current branch.\n" + " apply Apply a patch\n" + " switch Switch to patch-queue branch and vice versa") + except ConfigParser.ParsingError as err: + gbp.log.err('Invalid config file: %s' % err) + return 1 + + parser.add_boolean_config_file_option(option_name="patch-numbers", dest="patch_numbers") + parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, + help="Verbose command execution") + parser.add_option("--force", dest="force", action="store_true", default=False, + help="In case of import even import if the branch already exists") + parser.add_config_file_option(option_name="color", dest="color", type='tristate') + parser.add_config_file_option(option_name="upstream-tag", dest="upstream_tag") + parser.add_config_file_option(option_name="spec-file", dest="spec_file") + parser.add_config_file_option(option_name="packaging-dir", dest="packaging_dir") + + (options, args) = parser.parse_args(argv) + gbp.log.setup(options.color, options.verbose) + + if len(args) < 2: + gbp.log.err("No action given.") + return 1 + else: + action = args[1] + + if args[1] in ["export", "import", "rebase", "drop", "switch"]: + pass + elif args[1] in ["apply"]: + if len(args) != 3: + gbp.log.err("No patch name given.") + return 1 + else: + patchfile = args[2] + else: + gbp.log.err("Unknown action '%s'." % args[1]) + return 1 + + try: + repo = RpmGitRepository(os.path.curdir) + except GitRepositoryError: + gbp.log.err("%s is not a git repository" % (os.path.abspath('.'))) + return 1 + + if os.path.abspath('.') != repo.path: + gbp.log.warn("Switching to topdir before running commands") + os.chdir(repo.path) + + try: + current = repo.get_branch() + if action == "export": + export_patches(repo, current, options) + elif action == "import": + specfile=import_spec_patches(repo, current, options) + current = repo.get_branch() + gbp.log.info("Patches listed in '%s' imported on '%s'" % + (specfile, current)) + elif action == "drop": + drop_pq(repo, current, options) + elif action == "rebase": + rebase_pq(repo, current, options) + elif action == "apply": + patch = Patch(patchfile) + apply_single_patch(repo, current, patch, None, options) + elif action == "switch": + switch_pq(repo, current, options) + except CommandExecFailed: + retval = 1 + except GitRepositoryError as err: + gbp.log.err("Git command failed: %s" % err) + retval = 1 + except GbpError, err: + if len(err.__str__()): + gbp.log.err(err) + retval = 1 + + return retval + +if __name__ == '__main__': + sys.exit(main(sys.argv)) + @@ -56,7 +56,8 @@ setup(name = "gbp", 'bin/gbp-clone', 'bin/gbp-create-remote-repo', 'bin/git-pbuilder', - 'bin/git-import-srpm'], + 'bin/git-import-srpm', + 'bin/gbp-pq-rpm'], packages = find_packages(exclude=['tests', 'tests.*']), data_files = [("/etc/git-buildpackage/", ["gbp.conf"]),], setup_requires=['nose>=0.11.1', 'coverage>=2.85', 'nosexcover>=1.0.7'] if \ diff --git a/tests/01_test_help.py b/tests/01_test_help.py index 331d7cc6..d0266a66 100644 --- a/tests/01_test_help.py +++ b/tests/01_test_help.py @@ -27,7 +27,8 @@ class TestHelp(unittest.TestCase): """Test help output of RPM-specific commands""" def testHelpRpm(self): - for script in ['import_srpm']: + for script in ['import_srpm', + 'pq_rpm']: module = 'gbp.scripts.%s' % script m = __import__(module, globals(), locals(), ['main'], -1) self.assertRaises(SystemExit, |