Skip to content

Instantly share code, notes, and snippets.

@tchollingsworth
Created December 14, 2013 01:14
Show Gist options
  • Save tchollingsworth/7954397 to your computer and use it in GitHub Desktop.
Save tchollingsworth/7954397 to your computer and use it in GitHub Desktop.
git pre-commit hook for checking for problems with patches in RPM spec files
#!/usr/bin/python2
"""
git pre-commit hook for checking for problems with patches in RPM spec files
It verifies that:
- all patches are committed to git
- all patches are applied in %prep
- no unexpanded %patch macros exist in %prep
If any of the above checks fail, the commit is aborted.
"""
# Copyright 2013 T.C. Hollingsworth <[email protected]>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
# OPTIONS
# Turn this off if you do something funky that breaks this check, but still
# want the commit check. Patches that fail this check will still be printed
# to stderr, but the commit will not be aborted.
abort_not_applied = True
# Turn this off if you have patches that are not checked into git, but still
# want to check if they're applied. Patches that fail this check will still be
# printed to stderr, but the commit will not be aborted.
abort_not_committed = True
import os
import re
import rpm
import subprocess
import sys
co = subprocess.check_output
# lets check everything first and abort only at the end so packagers can fix
# everything at once
not_applied = False
not_committed = False
not_expanded = False
# stash any unstaged changes
subprocess.call(['git', 'stash', '-q', '--keep-index'])
# try and read the spec and abort if RPM can't parse it
specfile = os.path.join(co(['git', 'rev-parse', '--show-toplevel']).strip(),
co(['fedpkg', 'gimmespec']).strip())
try:
specobj = rpm.TransactionSet().parseSpec(specfile)
except ValueError:
sys.stderr.write('RPM is unable to parse the spec file, aborting commit!\n')
sys.exit(1)
spectxt = open(specfile).read()
patches = { src[1]: src[0] for src in specobj.sources if src[2] == 2 }
# make sure all patches are applied
patchcalls = [ int(m) for m in re.findall(r'%patch([0-9]+)', spectxt) ]
if re.search(r'%\{?patches\}?', spectxt) is None:
for p in patches.iterkeys():
if p not in patchcalls:
sys.stderr.write('Patch #{0} not applied in %prep\n'.format(p))
not_applied = True
# make sure there are no unexpanded patch macros in %prep
for p in patchcalls:
if p not in patches:
sys.stderr.write('Unexpanded patch #{0} macro in %prep\n'.format(p))
not_expanded = True
# make sure all patches are committed
committed_files = [ line.split('\t')[1] for line in co(
['git', 'ls-tree', '--full-tree', 'HEAD']).strip().split('\n') ]
staged_files = co(['git', 'diff', '--cached', '--name-only']).strip().split('\n')
all_files = committed_files + staged_files # could have dupes but we don't care
for num, url in patches.iteritems():
if os.path.basename(url) not in all_files:
sys.stderr.write('Patch #{0} not committed to git\n'.format(num))
not_committed = True
# restore unstaged changes
subprocess.call(['git', 'stash', 'pop', '-q'])
# finally, abort the commit if that's what we want
if abort_not_applied and not_applied:
sys.stderr.write('Some patches not applied, aborting commit\n')
sys.exit(2)
elif abort_not_committed and not_committed:
sys.stderr.write('Some patches not committed, aborting commit\n')
sys.exit(3)
elif not_expanded:
sys.stderr.write('Unexpanded patch macros detected in %prep, aborting commit\n')
sys.exit(4)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment