summaryrefslogtreecommitdiffstats
path: root/packaging/patch-update
diff options
context:
space:
mode:
Diffstat (limited to 'packaging/patch-update')
-rwxr-xr-xpackaging/patch-update244
1 files changed, 244 insertions, 0 deletions
diff --git a/packaging/patch-update b/packaging/patch-update
new file mode 100755
index 0000000..fd56a9d
--- /dev/null
+++ b/packaging/patch-update
@@ -0,0 +1,244 @@
+#!/usr/bin/env -S python3 -B
+
+# This script is used to turn one or more of the "patch/BASE/*" branches
+# into one or more diffs in the "patches" directory. Pass the option
+# --gen if you want generated files in the diffs. Pass the name of
+# one or more diffs if you want to just update a subset of all the
+# diffs.
+
+import os, sys, re, argparse, time, shutil
+
+sys.path = ['packaging'] + sys.path
+
+from pkglib import *
+
+MAKE_GEN_CMDS = [
+ './prepare-source'.split(),
+ 'cd build && if test -f config.status ; then ./config.status ; else ../configure ; fi',
+ 'make -C build gen'.split(),
+ ]
+TMP_DIR = "patches.gen"
+
+os.environ['GIT_MERGE_AUTOEDIT'] = 'no'
+
+def main():
+ global master_commit, parent_patch, description, completed, last_touch
+
+ if not os.path.isdir(args.patches_dir):
+ die(f'No "{args.patches_dir}" directory was found.')
+ if not os.path.isdir('.git'):
+ die('No ".git" directory present in the current dir.')
+
+ starting_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
+
+ master_commit = latest_git_hash(args.base_branch)
+
+ if cmd_txt_chk(['packaging/prep-auto-dir']).out == '':
+ die('You must setup an auto-build-save dir to use this script.')
+
+ if args.gen:
+ if os.path.lexists(TMP_DIR):
+ die(f'"{TMP_DIR}" must not exist in the current directory.')
+ gen_files = get_gen_files()
+ os.mkdir(TMP_DIR, 0o700)
+ for cmd in MAKE_GEN_CMDS:
+ cmd_chk(cmd)
+ cmd_chk(['rsync', '-a', *gen_files, f'{TMP_DIR}/master/'])
+
+ last_touch = int(time.time())
+
+ # Start by finding all patches so that we can load all possible parents.
+ patches = sorted(list(get_patch_branches(args.base_branch)))
+
+ parent_patch = { }
+ description = { }
+
+ for patch in patches:
+ branch = f"patch/{args.base_branch}/{patch}"
+ desc = ''
+ proc = cmd_pipe(['git', 'diff', '-U1000', f"{args.base_branch}...{branch}", '--', f"PATCH.{patch}"])
+ in_diff = False
+ for line in proc.stdout:
+ if in_diff:
+ if not re.match(r'^[ +]', line):
+ continue
+ line = line[1:]
+ m = re.search(r'patch -p1 <patches/(\S+)\.diff', line)
+ if m and m[1] != patch:
+ parpat = parent_patch[patch] = m[1]
+ if not parpat in patches:
+ die(f"Parent of {patch} is not a local branch: {parpat}")
+ desc += line
+ elif re.match(r'^@@ ', line):
+ in_diff = True
+ description[patch] = desc
+ proc.communicate()
+
+ if args.patch_files: # Limit the list of patches to actually process
+ valid_patches = patches
+ patches = [ ]
+ for fn in args.patch_files:
+ name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
+ if name not in valid_patches:
+ die(f"Local branch not available for patch: {name}")
+ patches.append(name)
+
+ completed = set()
+
+ for patch in patches:
+ if patch in completed:
+ continue
+ if not update_patch(patch):
+ break
+
+ if args.gen:
+ shutil.rmtree(TMP_DIR)
+
+ while last_touch >= int(time.time()):
+ time.sleep(1)
+ cmd_chk(['git', 'checkout', starting_branch])
+ cmd_chk(['packaging/prep-auto-dir'], discard='output')
+
+
+def update_patch(patch):
+ global last_touch
+
+ completed.add(patch) # Mark it as completed early to short-circuit any (bogus) dependency loops.
+
+ parent = parent_patch.get(patch, None)
+ if parent:
+ if parent not in completed:
+ if not update_patch(parent):
+ return 0
+ based_on = parent = f"patch/{args.base_branch}/{parent}"
+ else:
+ parent = args.base_branch
+ based_on = master_commit
+
+ print(f"======== {patch} ========")
+
+ while args.gen and last_touch >= int(time.time()):
+ time.sleep(1)
+
+ branch = f"patch/{args.base_branch}/{patch}"
+ s = cmd_run(['git', 'checkout', branch])
+ if s.returncode != 0:
+ return 0
+
+ s = cmd_run(['git', 'merge', based_on])
+ ok = s.returncode == 0
+ skip_shell = False
+ if not ok or args.cmd or args.make or args.shell:
+ cmd_chk(['packaging/prep-auto-dir'], discard='output')
+ if not ok:
+ print(f'"git merge {based_on}" incomplete -- please fix.')
+ if not run_a_shell(parent, patch):
+ return 0
+ if not args.make and not args.cmd:
+ skip_shell = True
+ if args.make:
+ if cmd_run(['packaging/smart-make']).returncode != 0:
+ if not run_a_shell(parent, patch):
+ return 0
+ if not args.cmd:
+ skip_shell = True
+ if args.cmd:
+ if cmd_run(args.cmd).returncode != 0:
+ if not run_a_shell(parent, patch):
+ return 0
+ skip_shell = True
+ if args.shell and not skip_shell:
+ if not run_a_shell(parent, patch):
+ return 0
+
+ with open(f"{args.patches_dir}/{patch}.diff", 'w', encoding='utf-8') as fh:
+ fh.write(description[patch])
+ fh.write(f"\nbased-on: {based_on}\n")
+
+ if args.gen:
+ gen_files = get_gen_files()
+ for cmd in MAKE_GEN_CMDS:
+ cmd_chk(cmd)
+ cmd_chk(['rsync', '-a', *gen_files, f"{TMP_DIR}/{patch}/"])
+ else:
+ gen_files = [ ]
+ last_touch = int(time.time())
+
+ proc = cmd_pipe(['git', 'diff', based_on])
+ skipping = False
+ for line in proc.stdout:
+ if skipping:
+ if not re.match(r'^diff --git a/', line):
+ continue
+ skipping = False
+ elif re.match(r'^diff --git a/PATCH', line):
+ skipping = True
+ continue
+ if not re.match(r'^index ', line):
+ fh.write(line)
+ proc.communicate()
+
+ if args.gen:
+ e_tmp_dir = re.escape(TMP_DIR)
+ diff_re = re.compile(r'^(diff -Nurp) %s/[^/]+/(.*?) %s/[^/]+/(.*)' % (e_tmp_dir, e_tmp_dir))
+ minus_re = re.compile(r'^\-\-\- %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
+ plus_re = re.compile(r'^\+\+\+ %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
+
+ if parent == args.base_branch:
+ parent_dir = 'master'
+ else:
+ m = re.search(r'([^/]+)$', parent)
+ parent_dir = m[1]
+
+ proc = cmd_pipe(['diff', '-Nurp', f"{TMP_DIR}/{parent_dir}", f"{TMP_DIR}/{patch}"])
+ for line in proc.stdout:
+ line = diff_re.sub(r'\1 a/\2 b/\3', line)
+ line = minus_re.sub(r'--- a/\1', line)
+ line = plus_re.sub(r'+++ b/\1', line)
+ fh.write(line)
+ proc.communicate()
+
+ return 1
+
+
+def run_a_shell(parent, patch):
+ m = re.search(r'([^/]+)$', parent)
+ parent_dir = m[1]
+ os.environ['PS1'] = f"[{parent_dir}] {patch}: "
+
+ while True:
+ s = cmd_run([os.environ.get('SHELL', '/bin/sh')])
+ if s.returncode != 0:
+ ans = input("Abort? [n/y] ")
+ if re.match(r'^y', ans, flags=re.I):
+ return False
+ continue
+ cur_branch, is_clean, status_txt = check_git_status(0)
+ if is_clean:
+ break
+ print(status_txt, end='')
+
+ cmd_run('rm -f build/*.o build/*/*.o')
+
+ return True
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description="Turn a git branch back into a diff files in the patches dir.", add_help=False)
+ parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
+ parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
+ parser.add_argument('--make', '-m', action='store_true', help="Run the smart-make script in every patch branch.")
+ parser.add_argument('--cmd', '-c', help="Run a command in every patch branch.")
+ parser.add_argument('--shell', '-s', action='store_true', help="Launch a shell for every patch/BASE/* branch updated, not just when a conflict occurs.")
+ parser.add_argument('--gen', metavar='DIR', nargs='?', const='', help='Include generated files. Optional DIR value overrides the default of using the "patches" dir.')
+ parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
+ parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
+ parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
+ args = parser.parse_args()
+ if args.gen == '':
+ args.gen = args.patches_dir
+ elif args.gen is not None:
+ args.patches_dir = args.gen
+ main()
+
+# vim: sw=4 et ft=python