Annotation of embedaddon/rsync/packaging/patch-update, revision 1.1.1.4
1.1.1.4 ! misho 1: #!/usr/bin/env -S python3 -B
! 2:
1.1 misho 3: # This script is used to turn one or more of the "patch/BASE/*" branches
4: # into one or more diffs in the "patches" directory. Pass the option
5: # --gen if you want generated files in the diffs. Pass the name of
6: # one or more diffs if you want to just update a subset of all the
7: # diffs.
8:
1.1.1.4 ! misho 9: import os, sys, re, argparse, time, shutil
! 10:
! 11: sys.path = ['packaging'] + sys.path
! 12:
! 13: from pkglib import *
! 14:
! 15: MAKE_GEN_CMDS = [
! 16: './prepare-source'.split(),
! 17: 'cd build && if test -f config.status ; then ./config.status ; else ../configure ; fi',
! 18: 'make -C build gen'.split(),
! 19: ]
! 20: TMP_DIR = "patches.gen"
! 21:
! 22: os.environ['GIT_MERGE_AUTOEDIT'] = 'no'
! 23:
! 24: def main():
! 25: global master_commit, parent_patch, description, completed, last_touch
! 26:
! 27: if not os.path.isdir(args.patches_dir):
! 28: die(f'No "{args.patches_dir}" directory was found.')
! 29: if not os.path.isdir('.git'):
! 30: die('No ".git" directory present in the current dir.')
! 31:
! 32: starting_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
! 33:
! 34: master_commit = latest_git_hash(args.base_branch)
! 35:
! 36: if cmd_txt_chk(['packaging/prep-auto-dir']) == '':
! 37: die('You must setup an auto-build-save dir to use this script.')
! 38:
! 39: if args.gen:
! 40: if os.path.lexists(TMP_DIR):
! 41: die(f'"{TMP_DIR}" must not exist in the current directory.')
! 42: gen_files = get_gen_files()
! 43: os.mkdir(TMP_DIR, 0o700)
! 44: for cmd in MAKE_GEN_CMDS:
! 45: cmd_chk(cmd)
! 46: cmd_chk(['rsync', '-a', *gen_files, f'{TMP_DIR}/master/'])
! 47:
! 48: last_touch = int(time.time())
! 49:
! 50: # Start by finding all patches so that we can load all possible parents.
! 51: patches = sorted(list(get_patch_branches(args.base_branch)))
! 52:
! 53: parent_patch = { }
! 54: description = { }
! 55:
! 56: for patch in patches:
! 57: branch = f"patch/{args.base_branch}/{patch}"
! 58: desc = ''
! 59: proc = cmd_pipe(['git', 'diff', '-U1000', f"{args.base_branch}...{branch}", '--', f"PATCH.{patch}"])
! 60: in_diff = False
! 61: for line in proc.stdout:
! 62: if in_diff:
! 63: if not re.match(r'^[ +]', line):
! 64: continue
! 65: line = line[1:]
! 66: m = re.search(r'patch -p1 <patches/(\S+)\.diff', line)
! 67: if m and m[1] != patch:
! 68: parpat = parent_patch[patch] = m[1]
! 69: if not parpat in patches:
! 70: die(f"Parent of {patch} is not a local branch: {parpat}")
! 71: desc += line
! 72: elif re.match(r'^@@ ', line):
! 73: in_diff = True
! 74: description[patch] = desc
! 75: proc.communicate()
! 76:
! 77: if args.patch_files: # Limit the list of patches to actually process
! 78: valid_patches = patches
! 79: patches = [ ]
! 80: for fn in args.patch_files:
! 81: name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
! 82: if name not in valid_patches:
! 83: die(f"Local branch not available for patch: {name}")
! 84: patches.append(name)
! 85:
! 86: completed = set()
! 87:
! 88: for patch in patches:
! 89: if patch in completed:
! 90: continue
! 91: if not update_patch(patch):
! 92: break
! 93:
! 94: if args.gen:
! 95: shutil.rmtree(TMP_DIR)
! 96:
! 97: while last_touch >= int(time.time()):
! 98: time.sleep(1)
! 99: cmd_chk(['git', 'checkout', starting_branch])
! 100: cmd_chk(['packaging/prep-auto-dir'], discard='output')
! 101:
! 102:
! 103: def update_patch(patch):
! 104: global last_touch
! 105:
! 106: completed.add(patch) # Mark it as completed early to short-circuit any (bogus) dependency loops.
! 107:
! 108: parent = parent_patch.get(patch, None)
! 109: if parent:
! 110: if parent not in completed:
! 111: if not update_patch(parent):
! 112: return 0
! 113: based_on = parent = f"patch/{args.base_branch}/{parent}"
! 114: else:
! 115: parent = args.base_branch
! 116: based_on = master_commit
! 117:
! 118: print(f"======== {patch} ========")
! 119:
! 120: while args.gen and last_touch >= int(time.time()):
! 121: time.sleep(1)
! 122:
! 123: branch = f"patch/{args.base_branch}/{patch}"
! 124: s = cmd_run(['git', 'checkout', branch])
! 125: if s.returncode != 0:
! 126: return 0
! 127:
! 128: s = cmd_run(['git', 'merge', based_on])
! 129: ok = s.returncode == 0
! 130: skip_shell = False
! 131: if not ok or args.cmd or args.make or args.shell:
! 132: cmd_chk(['packaging/prep-auto-dir'], discard='output')
! 133: if not ok:
! 134: print(f'"git merge {based_on}" incomplete -- please fix.')
! 135: if not run_a_shell(parent, patch):
! 136: return 0
! 137: if not args.make and not args.cmd:
! 138: skip_shell = True
! 139: if args.make:
! 140: if cmd_run(['packaging/smart-make']).returncode != 0:
! 141: if not run_a_shell(parent, patch):
! 142: return 0
! 143: if not args.cmd:
! 144: skip_shell = True
! 145: if args.cmd:
! 146: if cmd_run(args.cmd).returncode != 0:
! 147: if not run_a_shell(parent, patch):
! 148: return 0
! 149: skip_shell = True
! 150: if args.shell and not skip_shell:
! 151: if not run_a_shell(parent, patch):
! 152: return 0
! 153:
! 154: with open(f"{args.patches_dir}/{patch}.diff", 'w', encoding='utf-8') as fh:
! 155: fh.write(description[patch])
! 156: fh.write(f"\nbased-on: {based_on}\n")
! 157:
! 158: if args.gen:
! 159: gen_files = get_gen_files()
! 160: for cmd in MAKE_GEN_CMDS:
! 161: cmd_chk(cmd)
! 162: cmd_chk(['rsync', '-a', *gen_files, f"{TMP_DIR}/{patch}/"])
! 163: else:
! 164: gen_files = [ ]
! 165: last_touch = int(time.time())
! 166:
! 167: proc = cmd_pipe(['git', 'diff', based_on])
! 168: skipping = False
! 169: for line in proc.stdout:
! 170: if skipping:
! 171: if not re.match(r'^diff --git a/', line):
! 172: continue
! 173: skipping = False
! 174: elif re.match(r'^diff --git a/PATCH', line):
! 175: skipping = True
! 176: continue
! 177: if not re.match(r'^index ', line):
! 178: fh.write(line)
! 179: proc.communicate()
! 180:
! 181: if args.gen:
! 182: e_tmp_dir = re.escape(TMP_DIR)
! 183: diff_re = re.compile(r'^(diff -Nurp) %s/[^/]+/(.*?) %s/[^/]+/(.*)' % (e_tmp_dir, e_tmp_dir))
! 184: minus_re = re.compile(r'^\-\-\- %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
! 185: plus_re = re.compile(r'^\+\+\+ %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
! 186:
! 187: if parent == args.base_branch:
! 188: parent_dir = 'master'
! 189: else:
! 190: m = re.search(r'([^/]+)$', parent)
! 191: parent_dir = m[1]
! 192:
! 193: proc = cmd_pipe(['diff', '-Nurp', f"{TMP_DIR}/{parent_dir}", f"{TMP_DIR}/{patch}"])
! 194: for line in proc.stdout:
! 195: line = diff_re.sub(r'\1 a/\2 b/\3', line)
! 196: line = minus_re.sub(r'--- a/\1', line)
! 197: line = plus_re.sub(r'+++ b/\1', line)
! 198: fh.write(line)
! 199: proc.communicate()
! 200:
! 201: return 1
! 202:
! 203:
! 204: def run_a_shell(parent, patch):
! 205: m = re.search(r'([^/]+)$', parent)
! 206: parent_dir = m[1]
! 207: os.environ['PS1'] = f"[{parent_dir}] {patch}: "
! 208:
! 209: while True:
! 210: s = cmd_run([os.environ.get('SHELL', '/bin/sh')])
! 211: if s.returncode != 0:
! 212: ans = input("Abort? [n/y] ")
! 213: if re.match(r'^y', ans, flags=re.I):
! 214: return False
! 215: continue
! 216: cur_branch, is_clean, status_txt = check_git_status(0)
! 217: if is_clean:
! 218: break
! 219: print(status_txt, end='')
! 220:
! 221: cmd_run('rm -f build/*.o build/*/*.o')
! 222:
! 223: return True
! 224:
! 225:
! 226: if __name__ == '__main__':
! 227: parser = argparse.ArgumentParser(description="Turn a git branch back into a diff files in the patches dir.", add_help=False)
! 228: parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
! 229: parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
! 230: parser.add_argument('--make', '-m', action='store_true', help="Run the smart-make script in every patch branch.")
! 231: parser.add_argument('--cmd', '-c', help="Run a command in every patch branch.")
! 232: parser.add_argument('--shell', '-s', action='store_true', help="Launch a shell for every patch/BASE/* branch updated, not just when a conflict occurs.")
! 233: parser.add_argument('--gen', metavar='DIR', nargs='?', const='', help='Include generated files. Optional DIR value overrides the default of using the "patches" dir.')
! 234: parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
! 235: parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
! 236: parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
! 237: args = parser.parse_args()
! 238: if args.gen == '':
! 239: args.gen = args.patches_dir
! 240: elif args.gen is not None:
! 241: args.patches_dir = args.gen
! 242: main()
! 243:
! 244: # vim: sw=4 et ft=python
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>