summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2011-03-11 18:08:58 +0100
committerGuido Günther <agx@sigxcpu.org>2011-03-12 23:12:22 +0100
commit40f4709668425b03b9f59c28176547c313957715 (patch)
tree166980187712abd948005dce6b348899956a0a8b
parent9bb73b46d2f215dc70482e55311a64debeca4d6e (diff)
gbp-pq: Allow to specify subdirs for patches
via the "Gbp-Pq-Tag: <subdir>" directive in the patch header. This also gets rid sed callouts. Add "apply" action to apply single patches
-rw-r--r--debian/changelog17
-rw-r--r--debian/git-buildpackage.bash-completion3
-rw-r--r--docs/manpages/gbp-pq.sgml21
-rwxr-xr-xgbp-pq222
-rw-r--r--gbp/git.py11
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: <subdir>" directive in the patch header. This also gets rid
+ sed callouts. Add "apply" action to apply single patches
+
+ -- Guido Günther <agx@sigxcpu.org> 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 @@
<arg><option>--verbose</option></arg>
<arg><option>--color=</option><replaceable>[auto|on|off]</replaceable></arg>
<arg><option>--[no-]patch-numbers</option></arg>
+ <arg><option>--topic=</option><replaceable>topic</replaceable></arg>
<group choice="plain">
<arg><option>export</option></arg>
<arg><option>import</option></arg>
<arg><option>rebase</option></arg>
<arg><option>drop</option></arg>
+ <arg><option>apply</option></arg>
</group>
</cmdsynopsis>
</refsynopsisdiv>
@@ -91,6 +93,18 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>apply</option>
+ </term>
+ <listitem>
+ <para>
+ Add a single patch to the patch-queue similar to using
+ <command>git-am</command>. Use <option>--topic</option> if you want
+ the patch to appear in a separate subdir when exporting the patch queue
+ using <option>export</option>. This can be used to separate upstream
+ pathes from debian specific patches.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
<refsect1>
@@ -117,6 +131,13 @@
<para>Whether the patch files should start with a number or not.</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--topic=</option><replaceable>topic</replaceable>
+ </term>
+ <listitem>
+ <para>Topic to use when importing a single patch</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
<refsect1>
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 <agx@sigxcpu.org>
+# (C) 2011 Guido Günther <agx@sigxcpu.org>
# 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 <sha1>)
+ 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<name>.+)")
+ 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<name>.+)")
+ 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<name>.*[^ ]) *<(?P<email>.*)>', 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"""