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>