From 40f4709668425b03b9f59c28176547c313957715 Mon Sep 17 00:00:00 2001 From: Guido Günther Date: Fri, 11 Mar 2011 18:08:58 +0100 Subject: gbp-pq: Allow to specify subdirs for patches via the "Gbp-Pq-Tag: " directive in the patch header. This also gets rid sed callouts. Add "apply" action to apply single patches --- debian/changelog | 17 +++ debian/git-buildpackage.bash-completion | 3 +- docs/manpages/gbp-pq.sgml | 21 +++ gbp-pq | 222 ++++++++++++++++++++++++++------ gbp/git.py | 11 ++ 5 files changed, 230 insertions(+), 44 deletions(-) diff --git a/debian/changelog b/debian/changelog index 0064582..9a4cee1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,20 @@ +git-buildpackage (0.5.20~1.gbp194f43) UNRELEASED; urgency=low + + ** SNAPSHOT build @194f43948c2d5789d28dd5ba9167014199220182 ** + + * [a618bdc] Make the desktop notification transient + so they timeout and don't clutter the notification area. + * [3b0f296] Add script to ignore .pc and tell dpkg-source unpatch the + source. See #591858. + * [258743a] is_fast_forward: make sure git interprets arguments as revisions + so git prints a clearer error message on configuration errors in + .git/config + * [194f439] gbp-pq: Allow to specify subdirs for patches via the + "Gbp-Pq-Tag: " directive in the patch header. This also gets rid + sed callouts. Add "apply" action to apply single patches + + -- Guido Günther Sat, 12 Mar 2011 21:40:58 +0100 + git-buildpackage (0.5.19) unstable; urgency=low [ Jonathan Nieder ] diff --git a/debian/git-buildpackage.bash-completion b/debian/git-buildpackage.bash-completion index fda633e..0f8c9d5 100644 --- a/debian/git-buildpackage.bash-completion +++ b/debian/git-buildpackage.bash-completion @@ -124,10 +124,9 @@ have gbp-pq && _gbp_pq () { local options=$(_gbp_options gbp-pq) - options="$options export import rebase drop" + options="$options export import rebase drop apply" local tristate_opts="--color\=" - _gbp_comp "$options" _gbp_comp "$options" "" "" "$tristate_opts" } [ "${have:-}" ] && complete -F _gbp_pq -o default gbp-pq diff --git a/docs/manpages/gbp-pq.sgml b/docs/manpages/gbp-pq.sgml index cf55dd0..83b0500 100644 --- a/docs/manpages/gbp-pq.sgml +++ b/docs/manpages/gbp-pq.sgml @@ -23,11 +23,13 @@ [auto|on|off] + topic + @@ -91,6 +93,18 @@ + + + + + + Add a single patch to the patch-queue similar to using + git-am. Use if you want + the patch to appear in a separate subdir when exporting the patch queue + using . This can be used to separate upstream + pathes from debian specific patches. + + @@ -117,6 +131,13 @@ Whether the patch files should start with a number or not. + + topic + + + Topic to use when importing a single patch + + diff --git a/gbp-pq b/gbp-pq index 2498b69..7c83a88 100755 --- a/gbp-pq +++ b/gbp-pq @@ -1,7 +1,7 @@ #!/usr/bin/python -u # vim: set fileencoding=utf-8 : # -# (C) 2011 Guido Guenther +# (C) 2011 Guido Günther # 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 @@ -33,7 +33,7 @@ import gbp.log PQ_BRANCH_PREFIX = "patch-queue/" PATCH_DIR = "debian/patches/" -SERIES_FILE = PATCH_DIR + "series" +SERIES_FILE = os.path.join(PATCH_DIR,"series") def is_pq_branch(branch): return [False, True][branch.startswith(PQ_BRANCH_PREFIX)] @@ -43,32 +43,68 @@ def pq_branch_name(branch): if not is_pq_branch(branch): return PQ_BRANCH_PREFIX + branch + def pq_branch_base(pq_branch): """get the branch corresponding to the given patch queue branch""" if is_pq_branch(pq_branch): return pq_branch[len(PQ_BRANCH_PREFIX):] +def write_patch(patch, options): + """Write the patch exported by 'git-format-patch' to it's final location + (as specified in the commit)""" + oldname = patch[len(PATCH_DIR):] + newname = oldname + tmpname = patch + ".gbp" + old = file(patch, 'r') + tmp = file(tmpname, 'w') + in_patch = False + topic = None + + # Skip first line (from ) + old.readline() + for line in old: + if in_patch: + if line.startswith('-- '): + # Found final signature, we're done: + tmp.write(line) + break + else: + if line.lower().startswith("gbp-pq-topic: "): + topic = line.split(" ",1)[1].strip() + gbp.log.debug("Topic %s found for %s" % (topic, patch)) + continue + elif (line.startswith("diff --git a/") or + line.startswith("---")): + in_patch = True + tmp.write(line) + + tmp.close() + old.close() -def write_patch(patch, options) - # delete the first line (from sha1) and last two lines (git version - # info) of the patch file - Command("sed -i -e '1d' -e 'N;$!P;$!D;$d' %s" % patch, shell=True)() - Command("sed -i -e 's/^-- \\n[0-9\.]+$//' %s" % patch, shell=True)() - - name = patch[len(PATCH_DIR):] if not options.patch_numbers: - m = patch_re.match(name) + patch_re = re.compile("[0-9]+-(?P.+)") + m = patch_re.match(oldname) if m: - filename = m.group('name') - shutil.move(patch, os.path.join(PATCH_DIR, filename)) - else: - filename = name - return filename + newname = m.group('name') + + if topic: + topicdir = os.path.join(PATCH_DIR, topic) + else: + topicdir = PATCH_DIR + if not os.path.isdir(topicdir): + os.makedirs(topicdir, 0755) -def export_patches(repo, branch, options): - patch_re = re.compile("[0-9]+-(?P.+)") + os.unlink(patch) + dstname = os.path.join(topicdir, newname) + gbp.log.debug("Moving %s to %s" % (tmpname, dstname)) + shutil.move(tmpname, dstname) + + return dstname + +def export_patches(repo, branch, options): + """Export patches from the pq branch into a patch series""" if is_pq_branch(branch): base = pq_branch_base(branch) gbp.log.info("On '%s', switching to '%s'" % (branch, base)) @@ -90,21 +126,29 @@ def export_patches(repo, branch, options): gbp.log.info("Regenerating patch queue in '%s'." % PATCH_DIR) for patch in patches: filename = write_patch(patch, options) - f.write(filename + '\n') + f.write(filename[len(PATCH_DIR):] + '\n') + f.close() GitCommand('status')(['--', PATCH_DIR]) else: gbp.log.info("No patches on '%s' - nothing to do." % pq_branch) -def get_maintainer(): +def get_maintainer_from_control(): + """Get the maintainer from the control file""" cmd = 'sed -n -e \"s/Maintainer: \\+\\(.*\\)/\\1/p\" debian/control' maintainer = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE).stdout.readlines()[0].strip() - gbp.log.debug("Maintainer: %s" % maintainer) - return maintainer + + m = re.match('(?P.*[^ ]) *<(?P.*)>', maintainer) + if m: + return m.group('name'), m.group('email') + else: + return None, None -def import_patches(repo, branch): +def import_quilt_patches(repo, branch, series): + """apply a series of quilt patches in the series file 'series' to branch + the patch-queue branch for 'branch'""" if is_pq_branch(branch): gbp.log.err("Already on a patch-queue branch '%s' - doing nothing." % branch) raise GbpError @@ -118,14 +162,98 @@ def import_patches(repo, branch): % pq_branch) repo.set_branch(pq_branch) - if not os.path.exists(SERIES_FILE): + if not os.path.exists(series): gbp.log.info("Found no series file at '%s'. No patches to add to patch-queue branch." - % SERIES_FILE) + % series) + return + else: + patch_dir = os.path.dirname(series) + + try: + s = file(series) + except Exception, err: + raise GbpError("Cannot open series file: %s" % err) + + for patch in s: + patch = patch[:-1] + topic = os.path.dirname(patch) + if topic == '' or topic == '/': + topic = None + apply_and_commit_patch(repo, os.path.join(patch_dir, patch), topic) + s.close() + + +def get_mailinfo(patch): + """Read patch information into a structured form""" + + info = {} + body = os.path.join('.git', 'gbp_patchinfo') + pipe = subprocess.Popen("git mailinfo % s /dev/null < %s" % (body, patch), + shell=True, stdout=subprocess.PIPE).stdout + for line in pipe: + if ':' in line: + rfc_header, value = line.split(" ",1) + header = rfc_header[:-1].lower() + info[header] = value.strip() + + f = file(body) + commit_msg = "".join([ line for line in f ]) + f.close() + os.unlink(body) + + return info, commit_msg + + +def switch_to_pq_branch(repo, branch): + """Switch to patch-queue branch if not already there, create it if it + doesn't exist yet""" + if is_pq_branch (branch): return - maintainer = get_maintainer() - RunAtCommand('git', ['quiltimport', '--author', maintainer], - extra_env={'QUILT_PATCHES': PATCH_DIR})() + pq_branch = pq_branch_name(branch) + if not repo.has_branch(pq_branch): + try: + GitBranch()(pq_branch) + except CommandExecFailed: + raise GbpError, ("Cannot create patch-queue branch '%s'. Try 'rebase' instead." + % pq_branch) + + gbp.log.info("Switching to '%s'" % pq_branch) + repo.set_branch(pq_branch) + + +def apply_single_patch(repo, branch, patch, topic=None): + switch_to_pq_branch(repo, branch) + apply_and_commit_patch(repo, patch, topic) + + +def apply_and_commit_patch(repo, patch, topic=None): + """apply a single patch 'patch', add topic 'topic' and commit it""" + header, body = get_mailinfo(patch) + + if not header.has_key('subject'): + header['subject'] = os.path.basename(patch) + + if header.has_key('author') and header.has_key('email'): + header['name'] = header['author'] + else: + name, email = get_maintainer_from_control() + if name: + gbp.log.warn("Patch '%s' has no authorship information, using '%s <%s>'" + % (patch, name, email)) + header['name'] = name + header['email'] = email + else: + gbp.log.warn("Patch %s has no authorship information") + + head = repo.rev_parse('HEAD') + repo.apply_patch(patch) + tree = repo.write_tree() + msg = "%s\n\n%s" % (header['subject'], body) + if topic: + msg += "\nGbp-Pq-Topic: %s" % topic + commit = repo.commit_tree(tree, msg, [head], author=header) + repo.update_ref('HEAD', commit, msg="gbp-pq import %s" % patch) def drop_pq(repo, branch): @@ -137,19 +265,13 @@ def drop_pq(repo, branch): if repo.has_branch(pq_branch): repo.delete_branch(pq_branch) - gbp.log.info("Dropped branch %s." % pq_branch) + gbp.log.info("Dropped branch '%s'." % pq_branch) else: gbp.log.info("No patch queue branch found - doing nothing.") def rebase_pq(repo, branch): - if not is_pq_branch (branch): - pq_branch = pq_branch_name(branch) - gbp.log.info("Switching to '%s'" % pq_branch) - repo.set_branch(pq_branch) - else: - gbp.log.info("Already on '%s'" % branch) - + switch_to_pq_branch(repo, branch) GitCommand("rebase")([branch]) @@ -165,25 +287,35 @@ def main(argv): " import create a patch queue branch from quilt patches in debian/patches.\n" " rebase switch to patch queue branch associated to the current\n" " branch and rebase against current branch.\n" - " drop drop (delete) the patch queue associated to the current branch.") + " drop drop (delete) the patch queue associated to the current branch.\n" + " apply apply a patch\n") 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("--topic", dest="topic", help="in case of 'apply' topic (subdir) to put patch into") parser.add_config_file_option(option_name="color", dest="color", type='tristate') - (options, args) = parser.parse_args(argv) gbp.log.setup(options.color, options.verbose) - if len(args) != 2: + if len(args) < 2: gbp.log.err("No action given.") return 1 - elif args[1] not in ["export", "import", "rebase", "drop"]: - gbp.log.err("Unknown action '%s'." % args[1]) - return 1 else: action = args[1] + if args[1] in ["export", "import", "rebase", "drop"]: + pass + elif args[1] in ["apply"]: + if len(args) != 3: + gbp.log.err("No patch name given.") + return 1 + else: + patch = args[2] + else: + gbp.log.err("Unknown action '%s'." % args[1]) + return 1 + try: repo = GitRepository(os.path.curdir) except GitRepositoryError: @@ -195,11 +327,17 @@ def main(argv): if action == "export": export_patches(repo, current, options) elif action == "import": - import_patches(repo, current) + series = SERIES_FILE + import_quilt_patches(repo, current, series) + current = repo.get_branch() + gbp.log.info("Patches listed in '%s' imported on '%s'" % + (series, current)) elif action == "drop": drop_pq(repo, current) elif action == "rebase": rebase_pq(repo, current) + elif action == "apply": + apply_single_patch(repo, current, patch, options.topic) except CommandExecFailed: retval = 1 except GbpError, err: diff --git a/gbp/git.py b/gbp/git.py index dc648a1..e43d58f 100644 --- a/gbp/git.py +++ b/gbp/git.py @@ -486,6 +486,17 @@ class GitRepository(object): output, ret = self.__git_getoutput('format-patch', options) return [ line.strip() for line in output ] + def apply_patch(self, patch, index=True, context=None): + """Apply a patch using git apply""" + + args = [] + if context: + args += [ '-C', context ] + if index: + args.append("--index") + args.append(patch) + GitCommand("apply", args)() + class FastImport(object): """Invoke git-fast-import""" -- cgit v1.2.3