File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / rsync / packaging / patch-update
Revision 1.1.1.4 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Mar 17 00:32:36 2021 UTC (3 years, 3 months ago) by misho
Branches: rsync, MAIN
CVS tags: v3_2_3, HEAD
rsync 3.2.3

    1: #!/usr/bin/env -S python3 -B
    2: 
    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: 
    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>