Annotation of embedaddon/rsync/packaging/branch-from-patch, revision 1.1.1.2

1.1.1.2 ! misho       1: #!/usr/bin/env -S python3 -B
1.1       misho       2: 
1.1.1.2 ! misho       3: # This script turns one or more diff files in the patches dir (which is
        !             4: # expected to be a checkout of the rsync-patches git repo) into a branch
        !             5: # in the main rsync git checkout. This allows the applied patch to be
        !             6: # merged with the latest rsync changes and tested.  To update the diff
        !             7: # with the resulting changes, see the patch-update script.
        !             8: 
        !             9: import os, sys, re, argparse, glob
        !            10: 
        !            11: sys.path = ['packaging'] + sys.path
        !            12: 
        !            13: from pkglib import *
        !            14: 
        !            15: def main():
        !            16:     global created, info, local_branch
        !            17: 
        !            18:     cur_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
        !            19: 
        !            20:     local_branch = get_patch_branches(args.base_branch)
        !            21: 
        !            22:     if args.delete_local_branches:
        !            23:         for name in sorted(local_branch):
        !            24:             branch = f"patch/{args.base_branch}/{name}"
        !            25:             cmd_chk(['git', 'branch', '-D', branch])
        !            26:         local_branch = set()
        !            27: 
        !            28:     if args.add_missing:
        !            29:         for fn in sorted(glob.glob(f"{args.patches_dir}/*.diff")):
        !            30:             name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
        !            31:             if name not in local_branch and fn not in args.patch_files:
        !            32:                 args.patch_files.append(fn)
        !            33: 
        !            34:     if not args.patch_files:
        !            35:         return
        !            36: 
        !            37:     for fn in args.patch_files:
        !            38:         if not fn.endswith('.diff'):
        !            39:             die(f"Filename is not a .diff file: {fn}")
        !            40:         if not os.path.isfile(fn):
        !            41:             die(f"File not found: {fn}")
        !            42: 
        !            43:     scanned = set()
        !            44:     info = { }
        !            45: 
        !            46:     patch_list = [ ]
        !            47:     for fn in args.patch_files:
        !            48:         m = re.match(r'^(?P<dir>.*?)(?P<name>[^/]+)\.diff$', fn)
        !            49:         patch = argparse.Namespace(**m.groupdict())
        !            50:         if patch.name in scanned:
        !            51:             continue
        !            52:         patch.fn = fn
        !            53: 
        !            54:         lines = [ ]
        !            55:         commit_hash = None
        !            56:         with open(patch.fn, 'r', encoding='utf-8') as fh:
        !            57:             for line in fh:
        !            58:                 m = re.match(r'^based-on: (\S+)', line)
        !            59:                 if m:
        !            60:                     commit_hash = m[1]
        !            61:                     break
        !            62:                 if (re.match(r'^index .*\.\..* \d', line)
        !            63:                  or re.match(r'^diff --git ', line)
        !            64:                  or re.match(r'^--- (old|a)/', line)):
        !            65:                     break
        !            66:                 lines.append(re.sub(r'\s*\Z', "\n", line, 1))
        !            67:         info_txt = ''.join(lines).strip() + "\n"
        !            68:         lines = None
        !            69: 
        !            70:         parent = args.base_branch
        !            71:         patches = re.findall(r'patch -p1 <%s/(\S+)\.diff' % args.patches_dir, info_txt)
        !            72:         if patches:
        !            73:             last = patches.pop()
        !            74:             if last != patch.name:
        !            75:                 warn(f"No identity patch line in {patch.fn}")
        !            76:                 patches.append(last)
        !            77:             if patches:
        !            78:                 parent = patches.pop()
        !            79:                 if parent not in scanned:
        !            80:                     diff_fn = patch.dir + parent + '.diff'
        !            81:                     if not os.path.isfile(diff_fn):
        !            82:                         die(f"Failed to find parent of {patch.fn}: {parent}")
        !            83:                     # Add parent to args.patch_files so that we will look for the
        !            84:                     # parent's parent.  Any duplicates will be ignored.
        !            85:                     args.patch_files.append(diff_fn)
        !            86:         else:
        !            87:             warn(f"No patch lines found in {patch.fn}")
        !            88: 
        !            89:         info[patch.name] = [ parent, info_txt, commit_hash ]
        !            90: 
        !            91:         patch_list.append(patch)
        !            92: 
        !            93:     created = set()
        !            94:     for patch in patch_list:
        !            95:         create_branch(patch)
        !            96: 
        !            97:     cmd_chk(['git', 'checkout', args.base_branch])
        !            98: 
        !            99: 
        !           100: def create_branch(patch):
        !           101:     if patch.name in created:
        !           102:         return
        !           103:     created.add(patch.name)
        !           104: 
        !           105:     parent, info_txt, commit_hash = info[patch.name]
        !           106:     parent = argparse.Namespace(dir=patch.dir, name=parent, fn=patch.dir + parent + '.diff')
        !           107: 
        !           108:     if parent.name == args.base_branch:
        !           109:         parent_branch = commit_hash if commit_hash else args.base_branch
        !           110:     else:
        !           111:         create_branch(parent)
        !           112:         parent_branch = '/'.join(['patch', args.base_branch, parent.name])
        !           113: 
        !           114:     branch = '/'.join(['patch', args.base_branch, patch.name])
        !           115:     print("\n" + '=' * 64)
        !           116:     print(f"Processing {branch} ({parent_branch})")
        !           117: 
        !           118:     if patch.name in local_branch:
        !           119:         cmd_chk(['git', 'branch', '-D', branch])
        !           120: 
        !           121:     cmd_chk(['git', 'checkout', '-b', branch, parent_branch])
        !           122: 
        !           123:     info_fn = 'PATCH.' + patch.name
        !           124:     with open(info_fn, 'w', encoding='utf-8') as fh:
        !           125:         fh.write(info_txt)
        !           126:     cmd_chk(['git', 'add', info_fn])
        !           127: 
        !           128:     with open(patch.fn, 'r', encoding='utf-8') as fh:
        !           129:         patch_txt = fh.read()
        !           130: 
        !           131:     cmd_run('patch -p1'.split(), input=patch_txt)
        !           132: 
        !           133:     for fn in glob.glob('*.orig') + glob.glob('*/*.orig'):
        !           134:         os.unlink(fn)
        !           135: 
        !           136:     pos = 0
        !           137:     new_file_re = re.compile(r'\nnew file mode (?P<mode>\d+)\s+--- /dev/null\s+\+\+\+ b/(?P<fn>.+)')
        !           138:     while True:
        !           139:         m = new_file_re.search(patch_txt, pos)
        !           140:         if not m:
        !           141:             break
        !           142:         os.chmod(m['fn'], int(m['mode'], 8))
        !           143:         cmd_chk(['git', 'add', m['fn']])
        !           144:         pos = m.end()
        !           145: 
        !           146:     while True:
        !           147:         cmd_chk('git status'.split())
        !           148:         ans = input('Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: ')
        !           149:         if ans == '':
        !           150:             break
        !           151:         cmd_chk("git add " + ans, shell=True)
        !           152: 
        !           153:     while True:
        !           154:         s = cmd_run(['git', 'commit', '-a', '-m', f"Creating branch from {patch.name}.diff."])
        !           155:         if not s.returncode:
        !           156:             break
        !           157:         s = cmd_run(['/bin/zsh'])
        !           158:         if s.returncode:
        !           159:             die('Aborting due to shell error code')
        !           160: 
        !           161: 
        !           162: if __name__ == '__main__':
        !           163:     parser = argparse.ArgumentParser(description="Create a git patch branch from an rsync patch file.", add_help=False)
        !           164:     parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
        !           165:     parser.add_argument('--add-missing', '-a', action='store_true', help="Add a branch for every patches/*.diff that doesn't have a branch.")
        !           166:     parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
        !           167:     parser.add_argument('--delete', dest='delete_local_branches', action='store_true', help="Delete all the local patch/BASE/* branches, not just the ones that are being recreated.")
        !           168:     parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
        !           169:     parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
        !           170:     parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
        !           171:     args = parser.parse_args()
        !           172:     main()
        !           173: 
        !           174: # vim: sw=4 et ft=python

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>