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>