#!/usr/bin/env python3 import argparse from os import path, getcwd, makedirs, listdir, remove, rename from subprocess import run, DEVNULL def folder(default_search, user_input): if path.sep in user_input: return user_input else: the_path = path.dirname(path.dirname(path.abspath(__file__))) the_path = path.join(the_path, default_search, user_input) return the_path parser = argparse.ArgumentParser(description=''' Shrink a git changelog into a patchset (based on git format-patch) ''') parser.add_argument('worktree', metavar='WORKTREE', nargs='?', action='store', default=getcwd(), type=lambda x: folder('worktree', x), help='Worktree (default is cwd, or assumed in ../worktree/)') parser.add_argument('patchset', metavar='PATCHSET', action='store', type=lambda x: folder('patchset', x), help='Destination patchset (assumed in ../patchset/)') parser.add_argument('--raw', action='store_true', help='Do not neaten up commits') args = parser.parse_args() args.patchset = path.abspath(args.patchset) assert not path.exists(path.join(args.patchset, '.git')) # protect against argument swap assert path.exists(path.join(args.worktree, '.git')) makedirs(args.patchset, exist_ok=True) for do_delete in listdir(args.patchset): do_delete = path.join(args.patchset, do_delete) if path.splitext(do_delete)[1].lower() == '.patch': remove(do_delete) run(['git', 'format-patch', '-o', args.patchset, '--anchored=;', 'master-patchset-base'], stdout=DEVNULL, cwd=args.worktree, check=True) if args.raw: exit() # Edit the patches to look a bit better (and be more reproducible) patchnames = [x for x in sorted(listdir(args.patchset)) if path.splitext(x)[1].lower() == '.patch'] strip_chars = min(len(x) - len(x.lstrip('0')) for x in patchnames) for do_edit in patchnames: real_file = path.join(args.patchset, do_edit) fake_file = path.join(args.patchset, do_edit + '~') new_file = path.join(args.patchset, do_edit[strip_chars:].lower()) print(path.basename(new_file)) rename(real_file, fake_file) with open(new_file, 'w') as o, open(fake_file, 'r') as i: for hdr_line in i: if hdr_line == '\n': o.write(hdr_line) break elif hdr_line.startswith('Subject:'): if '[' in hdr_line: o.write('Subject:' + hdr_line.rpartition(']')[2]) else: o.write(hdr_line) elif hdr_line.startswith('From:'): o.write('From: Horst Beepmanh <>\n') for msg_line in i: o.write(msg_line) if msg_line == '---\n': break for junk_line in i: if junk_line == junk_line.lstrip(): o.write(junk_line) break for diff_line in i: if diff_line.startswith('index'): # strip this hash line if possible next_line = next(i) if next_line.startswith('GIT binary patch'): o.write(diff_line) o.write(next_line) else: o.write(next_line) else: o.write(diff_line) remove(fake_file)