3 # rediff-patches.py name.spec
16 RPMBUILD_ISPATCH = (1<<1)
18 def prepare_spec(r, patch_nr, before=False):
19 tempspec = tempfile.NamedTemporaryFile()
20 re_patch = re.compile(r'^%patch(?P<patch_number>\d+)\w*')
21 for line in r.parsed.split('\n'):
22 m = re_patch.match(line)
24 patch_number = int(m.group('patch_number'))
25 if patch_nr == patch_number:
27 tempspec.write(b"exit 0\n# here was patch%d\n" % patch_nr)
29 line = re.sub(r'#.*', "", line)
30 tempspec.write(b"%s\nexit 0\n" % line.encode('utf-8'))
32 tempspec.write(b"%s\n" % line.encode('utf-8'))
36 def unpack(spec, appsourcedir, builddir):
37 cmd = [ 'rpmbuild', '-bp',
38 '--define', '_builddir %s' % builddir,
39 '--define', '_specdir %s' % appsourcedir,
40 '--define', '_sourcedir %s' % appsourcedir,
41 '--define', '_enable_debug_packages 0',
42 '--define', '_default_patch_fuzz 2',
44 logging.debug("running %s" % repr(cmd))
46 res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True,
47 env={'LC_ALL': 'C.UTF-8'}, timeout=600)
48 except subprocess.CalledProcessError as err:
49 logging.error("unpacking exited with status code %d." % err.returncode)
50 logging.error("STDOUT:")
52 for line in err.stdout.decode('utf-8').split("\n"):
54 logging.error("STDERR:")
56 for line in err.stderr.decode('utf-8').split("\n"):
60 logging.debug("unpacking exited with status code %d." % res.returncode)
61 logging.debug("STDOUT/STDERR:")
63 for line in res.stdout.decode('utf-8').split("\n"):
67 def patch_comment_get(patch):
70 with open(patch, 'rt') as f:
72 if line.startswith('diff ') or line.startswith('--- '):
76 return patch_comment if patch_got else ""
78 def diff(diffdir_org, diffdir, builddir, patch_comment, output):
79 diffdir_org = os.path.basename(diffdir_org)
80 diffdir = os.path.basename(diffdir)
82 with open(output, 'wt') as f:
84 f.write(patch_comment)
86 cmd = [ 'diff', '-urNp', '-x', '*.orig', diffdir_org, diffdir ]
87 logging.debug("running %s" % repr(cmd))
89 subprocess.check_call(cmd, cwd=builddir, stdout=f, stderr=sys.stderr,
90 env={'LC_ALL': 'C.UTF-8'}, timeout=600)
91 except subprocess.CalledProcessError as err:
92 if err.returncode != 1:
94 logging.info("rediff generated as %s" % output)
97 cmd = [ 'diffstat', patch ]
98 logging.info("running diffstat for: %s" % patch)
100 subprocess.check_call(cmd, stdout=sys.stdout, stderr=sys.stderr,
101 env={'LC_ALL': 'C.UTF-8'}, timeout=60)
102 except subprocess.CalledProcessError as err:
103 logging.error("running diffstat failed: %s" % err)
104 except FileNotFoundError as err:
105 logging.error("running diffstat failed: %s, install diffstat package?" % err)
108 parser = parser = argparse.ArgumentParser(description='rediff patches to avoid fuzzy hunks')
109 parser.add_argument('spec', type=str, help='spec file name')
110 parser.add_argument('-p', '--patches', type=str, help='comma separated list of patch numbers to rediff')
111 parser.add_argument('-s', '--skip-patches', type=str, help='comma separated list of patch numbers to skip rediff')
112 parser.add_argument('-v', '--verbose', help='increase output verbosity', action='store_true')
113 args = parser.parse_args()
115 logging.basicConfig(level=logging.INFO)
116 rpm.setVerbosity(rpm.RPMLOG_ERR)
119 logging.basicConfig(level=logging.DEBUG)
120 rpm.setVerbosity(rpm.RPMLOG_DEBUG)
123 args.patches = [int(x) for x in args.patches.split(',')]
125 if args.skip_patches:
126 args.skip_patches = [int(x) for x in args.skip_patches.split(',')]
129 appsourcedir = os.path.dirname(os.path.abspath(specfile))
132 tempdir = tempfile.TemporaryDirectory(dir="/dev/shm")
133 except FileNotFoundError as e:
134 tempdir = tempfile.TemporaryDirectory(dir="/tmp")
135 topdir = tempdir.name
136 builddir = os.path.join(topdir, 'BUILD')
138 rpm.addMacro("_builddir", builddir)
140 r = rpm.spec(specfile)
144 for (name, nr, flags) in r.sources:
145 if flags & RPMBUILD_ISPATCH:
148 applied_patches = collections.OrderedDict()
149 re_patch = re.compile(r'^%patch(?P<patch_number>\d+)\w*(?P<patch_args>.*)')
150 for line in r.parsed.split('\n'):
151 m = re_patch.match(line)
154 patch_nr = int(m.group('patch_number'))
155 patch_args = m.group('patch_args')
156 applied_patches[patch_nr] = patch_args
158 appbuilddir = rpm.expandMacro("%{_builddir}/%{?buildsubdir}")
160 for patch_nr in applied_patches.keys():
161 if args.patches and patch_nr not in args.patches:
163 if args.skip_patches and patch_nr in args.skip_patches:
165 patch_name = patches[patch_nr]
166 logging.info("*** patch %d: %s" % (patch_nr, patch_name))
168 tempspec = prepare_spec(r, patch_nr, before=True)
169 unpack(tempspec.name, appsourcedir, builddir)
171 os.rename(appbuilddir, appbuilddir + ".org")
173 tempspec = prepare_spec(r, patch_nr, before=False)
174 unpack(tempspec.name, appsourcedir, builddir)
177 patch_comment = patch_comment_get(patch_name)
178 diff(appbuilddir + ".org",
182 os.path.join(topdir, os.path.join(appsourcedir, patch_name + ".rediff")))
184 diffstat(os.path.join(topdir, os.path.join(appsourcedir, patch_name)))
185 diffstat(os.path.join(topdir, os.path.join(appsourcedir, patch_name + ".rediff")))
187 shutil.rmtree(builddir)
190 if __name__ == '__main__':