mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-01-27 16:28:34 -05:00
The release script & the patch management script now require the use of an auto-build-save dir that makes it much easier to keep the generated files from melding together, and remembers the configure setup for each patch branch.
219 lines
8.0 KiB
Python
Executable File
219 lines
8.0 KiB
Python
Executable File
#!/usr/bin/env -S python3 -B
|
|
|
|
# This script is used to turn one or more of the "patch/BASE/*" branches
|
|
# into one or more diffs in the "patches" directory. Pass the option
|
|
# --gen if you want generated files in the diffs. Pass the name of
|
|
# one or more diffs if you want to just update a subset of all the
|
|
# diffs.
|
|
|
|
import os, sys, re, argparse, time, shutil
|
|
|
|
sys.path = ['packaging'] + sys.path
|
|
|
|
from pkglib import *
|
|
|
|
MAKE_GEN_CMDS = [
|
|
'./prepare-source'.split(),
|
|
'cd build && if test -f config.status ; then ./config.status ; else ../configure ; fi',
|
|
'make -C build gen'.split(),
|
|
]
|
|
TMP_DIR = "patches.gen"
|
|
|
|
os.environ['GIT_MERGE_AUTOEDIT'] = 'no'
|
|
|
|
def main():
|
|
global master_commit, parent_patch, description, completed, last_touch
|
|
|
|
if not os.path.isdir(args.patches_dir):
|
|
die(f'No "{args.patches_dir}" directory was found.')
|
|
if not os.path.isdir('.git'):
|
|
die('No ".git" directory present in the current dir.')
|
|
|
|
starting_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
|
|
|
|
master_commit = latest_git_hash(args.base_branch)
|
|
|
|
if cmd_txt_chk(['packaging/prep-auto-dir']) == '':
|
|
die('You must setup an auto-build-save dir to use this script.')
|
|
|
|
if args.gen:
|
|
if os.path.lexists(TMP_DIR):
|
|
die(f'"{TMP_DIR}" must not exist in the current directory.')
|
|
gen_files = get_gen_files()
|
|
os.mkdir(TMP_DIR, 0o700)
|
|
for cmd in MAKE_GEN_CMDS:
|
|
cmd_chk(cmd)
|
|
cmd_chk(['rsync', '-a', *gen_files, f'{TMP_DIR}/master/'])
|
|
|
|
last_touch = int(time.time())
|
|
|
|
# Start by finding all patches so that we can load all possible parents.
|
|
patches = sorted(list(get_patch_branches(args.base_branch)))
|
|
|
|
parent_patch = { }
|
|
description = { }
|
|
|
|
for patch in patches:
|
|
branch = f"patch/{args.base_branch}/{patch}"
|
|
desc = ''
|
|
proc = cmd_pipe(['git', 'diff', '-U1000', f"{args.base_branch}...{branch}", '--', f"PATCH.{patch}"])
|
|
in_diff = False
|
|
for line in proc.stdout:
|
|
if in_diff:
|
|
if not re.match(r'^[ +]', line):
|
|
continue
|
|
line = line[1:]
|
|
m = re.search(r'patch -p1 <patches/(\S+)\.diff', line)
|
|
if m and m[1] != patch:
|
|
parpat = parent_patch[patch] = m[1]
|
|
if not parpat in patches:
|
|
die(f"Parent of {patch} is not a local branch: {parpat}")
|
|
desc += line
|
|
elif re.match(r'^@@ ', line):
|
|
in_diff = True
|
|
description[patch] = desc
|
|
proc.communicate()
|
|
|
|
if args.patch_files: # Limit the list of patches to actually process
|
|
valid_patches = patches
|
|
patches = [ ]
|
|
for fn in args.patch_files:
|
|
name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
|
|
if name not in valid_patches:
|
|
die(f"Local branch not available for patch: {name}")
|
|
patches.append(name)
|
|
|
|
completed = set()
|
|
|
|
for patch in patches:
|
|
if patch in completed:
|
|
continue
|
|
if not update_patch(patch):
|
|
break
|
|
|
|
if args.gen:
|
|
shutil.rmtree(TMP_DIR)
|
|
|
|
while last_touch >= int(time.time()):
|
|
time.sleep(1)
|
|
cmd_chk(['git', 'checkout', starting_branch])
|
|
cmd_chk(['packaging/prep-auto-dir'], discard='output')
|
|
|
|
|
|
def update_patch(patch):
|
|
global last_touch
|
|
|
|
completed.add(patch) # Mark it as completed early to short-circuit any (bogus) dependency loops.
|
|
|
|
parent = parent_patch.get(patch, None)
|
|
if parent:
|
|
if parent not in completed:
|
|
if not update_patch(parent):
|
|
return 0
|
|
based_on = parent = f"patch/{args.base_branch}/{parent}"
|
|
else:
|
|
parent = args.base_branch
|
|
based_on = master_commit
|
|
|
|
print(f"======== {patch} ========")
|
|
|
|
while args.gen and last_touch >= int(time.time()):
|
|
time.sleep(1)
|
|
|
|
branch = f"patch/{args.base_branch}/{patch}"
|
|
s = cmd_run(['git', 'checkout', branch])
|
|
if s.returncode != 0:
|
|
return 0
|
|
|
|
s = cmd_run(['git', 'merge', based_on])
|
|
ok = s.returncode == 0
|
|
if not ok or args.shell:
|
|
cmd_chk(['packaging/prep-auto-dir'], discard='output')
|
|
m = re.search(r'([^/]+)$', parent)
|
|
parent_dir = m[1]
|
|
if not ok:
|
|
print(f'"git merge {based_on}" incomplete -- please fix.')
|
|
os.environ['PS1'] = f"[{parent_dir}] {patch}: "
|
|
while True:
|
|
s = cmd_run([os.environ.get('SHELL', '/bin/sh')])
|
|
if s.returncode != 0:
|
|
ans = input("Abort? [n/y] ")
|
|
if re.match(r'^y', ans, flags=re.I):
|
|
return 0
|
|
continue
|
|
cur_branch, is_clean, status_txt = check_git_status(0)
|
|
if is_clean:
|
|
break
|
|
print(status_txt, end='')
|
|
cmd_run('rm -f build/*.o build/*/*.o')
|
|
|
|
with open(f"{args.patches_dir}/{patch}.diff", 'w', encoding='utf-8') as fh:
|
|
fh.write(description[patch])
|
|
fh.write(f"\nbased-on: {based_on}\n")
|
|
|
|
if args.gen:
|
|
gen_files = get_gen_files()
|
|
for cmd in MAKE_GEN_CMDS:
|
|
cmd_chk(cmd)
|
|
cmd_chk(['rsync', '-a', *gen_files, f"{TMP_DIR}/{patch}/"])
|
|
else:
|
|
gen_files = [ ]
|
|
last_touch = int(time.time())
|
|
|
|
proc = cmd_pipe(['git', 'diff', based_on])
|
|
skipping = False
|
|
for line in proc.stdout:
|
|
if skipping:
|
|
if not re.match(r'^diff --git a/', line):
|
|
continue
|
|
skipping = False
|
|
elif re.match(r'^diff --git a/PATCH', line):
|
|
skipping = True
|
|
continue
|
|
if not re.match(r'^index ', line):
|
|
fh.write(line)
|
|
proc.communicate()
|
|
|
|
if args.gen:
|
|
e_tmp_dir = re.escape(TMP_DIR)
|
|
diff_re = re.compile(r'^(diff -Nurp) %s/[^/]+/(.*?) %s/[^/]+/(.*)' % (e_tmp_dir, e_tmp_dir))
|
|
minus_re = re.compile(r'^\-\-\- %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
|
|
plus_re = re.compile(r'^\+\+\+ %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
|
|
|
|
if parent == args.base_branch:
|
|
parent_dir = 'master'
|
|
else:
|
|
m = re.search(r'([^/]+)$', parent)
|
|
parent_dir = m[1]
|
|
|
|
proc = cmd_pipe(['diff', '-Nurp', f"{TMP_DIR}/{parent_dir}", f"{TMP_DIR}/{patch}"])
|
|
for line in proc.stdout:
|
|
line = diff_re.sub(r'\1 a/\2 b/\3', line)
|
|
line = minus_re.sub(r'--- a/\1', line)
|
|
line = plus_re.sub(r'+++ b/\1', line)
|
|
fh.write(line)
|
|
proc.communicate()
|
|
for fn in gen_files:
|
|
os.unlink(fn)
|
|
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
parser = argparse.ArgumentParser(description="Turn a git branch back into a diff files in the patches dir.", add_help=False)
|
|
parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
|
|
parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
|
|
parser.add_argument('--shell', '-s', action='store_true', help="Launch a shell for every patch/BASE/* branch updated, not just when a conflict occurs.")
|
|
parser.add_argument('--gen', metavar='DIR', nargs='?', const='', help='Include generated files. Optional DIR value overrides the default of using the "patches" dir.')
|
|
parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
|
|
parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
|
|
parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
|
|
args = parser.parse_args()
|
|
if args.gen == '':
|
|
args.gen = args.patches_dir
|
|
elif args.gen is not None:
|
|
args.patches_dir = args.gen
|
|
main()
|
|
|
|
# vim: sw=4 et ft=python
|