Annotation of embedaddon/rsync/packaging/release-rsync, revision 1.1.1.4
1.1.1.4 ! misho 1: #!/usr/bin/env -S python3 -B
! 2:
1.1 misho 3: # This script expects the directory ~/samba-rsync-ftp to exist and to be a
4: # copy of the /home/ftp/pub/rsync dir on samba.org. When the script is done,
5: # the git repository in the current directory will be updated, and the local
6: # ~/samba-rsync-ftp dir will be ready to be rsynced to samba.org.
7:
1.1.1.4 ! misho 8: import os, sys, re, argparse, glob, shutil, signal
! 9: from datetime import datetime
! 10: from getpass import getpass
! 11:
! 12: sys.path = ['packaging'] + sys.path
! 13:
! 14: from pkglib import *
! 15:
! 16: os.environ['LESS'] = 'mqeiXR'; # Make sure that -F is turned off and -R is turned on.
! 17: dest = os.environ['HOME'] + '/samba-rsync-ftp'
! 18: ORIGINAL_PATH = os.environ['PATH']
! 19:
! 20: def main():
! 21: now = datetime.now()
! 22: cl_today = now.strftime('* %a %b %d %Y')
! 23: year = now.strftime('%Y')
! 24: ztoday = now.strftime('%d %b %Y')
! 25: today = ztoday.lstrip('0')
! 26:
! 27: mandate_gensend_hook()
! 28:
! 29: curdir = os.getcwd()
! 30:
! 31: signal.signal(signal.SIGINT, signal_handler)
! 32:
! 33: if cmd_txt_chk(['packaging/prep-auto-dir']) == '':
! 34: die('You must setup an auto-build-save dir to use this script.');
1.1 misho 35:
1.1.1.4 ! misho 36: auto_dir, gen_files = get_gen_files(True)
! 37: gen_pathnames = [ os.path.join(auto_dir, fn) for fn in gen_files ]
! 38:
! 39: dash_line = '=' * 74
! 40:
! 41: print(f"""\
! 42: {dash_line}
1.1 misho 43: == This will release a new version of rsync onto an unsuspecting world. ==
1.1.1.4 ! misho 44: {dash_line}
! 45: """)
1.1 misho 46:
1.1.1.4 ! misho 47: if not os.path.isdir(dest):
! 48: die(dest, "dest does not exist")
! 49: if not os.path.isdir('.git'):
! 50: die("There is no .git dir in the current directory.")
! 51: if os.path.lexists('a'):
! 52: die('"a" must not exist in the current directory.')
! 53: if os.path.lexists('b'):
! 54: die('"b" must not exist in the current directory.')
! 55: if os.path.lexists('patches.gen'):
! 56: die('"patches.gen" must not exist in the current directory.')
! 57:
! 58: check_git_state(args.master_branch, True, 'patches')
! 59:
! 60: curversion = get_rsync_version()
! 61:
! 62: # All version values are strings!
! 63: lastversion, last_protocol_version, pdate = get_NEWS_version_info()
! 64: protocol_version, subprotocol_version = get_protocol_versions()
! 65:
! 66: version = curversion
! 67: m = re.search(r'pre(\d+)', version)
! 68: if m:
! 69: version = re.sub(r'pre\d+', 'pre' + str(int(m[1]) + 1), version)
! 70: else:
! 71: version = version.replace('dev', 'pre1')
! 72:
! 73: ans = input(f"Please enter the version number of this release: [{version}] ")
! 74: if ans == '.':
! 75: version = re.sub(r'pre\d+', '', version)
! 76: elif ans != '':
! 77: version = ans
! 78: if not re.match(r'^[\d.]+(pre\d+)?$', version):
! 79: die(f'Invalid version: "{version}"')
! 80:
! 81: v_ver = 'v' + version
! 82: rsync_ver = 'rsync-' + version
! 83:
! 84: if os.path.lexists(rsync_ver):
! 85: die(f'"{rsync_ver}" must not exist in the current directory.')
! 86:
! 87: out = cmd_txt_chk(['git', 'tag', '-l', v_ver])
! 88: if out != '':
! 89: print(f"Tag {v_ver} already exists.")
! 90: ans = input("\nDelete tag or quit? [Q/del] ")
! 91: if not re.match(r'^del', ans, flags=re.I):
! 92: die("Aborted")
! 93: cmd_chk(['git', 'tag', '-d', v_ver])
! 94:
! 95: version = re.sub(r'[-.]*pre[-.]*', 'pre', version)
! 96: if 'pre' in version and not curversion.endswith('dev'):
! 97: lastversion = curversion
! 98:
! 99: ans = input(f"Enter the previous version to produce a patch against: [{lastversion}] ")
! 100: if ans != '':
! 101: lastversion = ans
! 102: lastversion = re.sub(r'[-.]*pre[-.]*', 'pre', lastversion)
! 103:
! 104: rsync_lastver = 'rsync-' + lastversion
! 105: if os.path.lexists(rsync_lastver):
! 106: die(f'"{rsync_lastver}" must not exist in the current directory.')
! 107:
! 108: m = re.search(r'(pre\d+)', version)
! 109: pre = m[1] if m else ''
! 110:
! 111: release = '0.1' if pre else '1'
! 112: ans = input(f"Please enter the RPM release number of this release: [{release}] ")
! 113: if ans != '':
! 114: release = ans
! 115: if pre:
! 116: release += '.' + pre
! 117:
! 118: finalversion = re.sub(r'pre\d+', '', version)
! 119: proto_changed = protocol_version != last_protocol_version
! 120: if proto_changed:
! 121: if finalversion in pdate:
! 122: proto_change_date = pdate[finalversion]
! 123: else:
! 124: while True:
! 125: ans = input("On what date did the protocol change to {protocol_version} get checked in? (dd Mmm yyyy) ")
! 126: if re.match(r'^\d\d \w\w\w \d\d\d\d$', ans):
! 127: break
! 128: proto_change_date = ans
! 129: else:
! 130: proto_change_date = ' ' * 11
! 131:
! 132: if 'pre' in lastversion:
! 133: if not pre:
! 134: die("You should not diff a release version against a pre-release version.")
! 135: srcdir = srcdiffdir = lastsrcdir = 'src-previews'
! 136: skipping = ' ** SKIPPING **'
! 137: elif pre:
! 138: srcdir = srcdiffdir = 'src-previews'
! 139: lastsrcdir = 'src'
! 140: skipping = ' ** SKIPPING **'
! 141: else:
! 142: srcdir = lastsrcdir = 'src'
! 143: srcdiffdir = 'src-diffs'
! 144: skipping = ''
! 145:
! 146: print(f"""
! 147: {dash_line}
! 148: version is "{version}"
! 149: lastversion is "{lastversion}"
! 150: dest is "{dest}"
! 151: curdir is "{curdir}"
! 152: srcdir is "{srcdir}"
! 153: srcdiffdir is "{srcdiffdir}"
! 154: lastsrcdir is "{lastsrcdir}"
! 155: release is "{release}"
1.1 misho 156:
157: About to:
158: - tweak SUBPROTOCOL_VERSION in rsync.h, if needed
1.1.1.4 ! misho 159: - tweak the version in version.h and the spec files
! 160: - tweak NEWS.md to ensure header values are correct
1.1 misho 161: - generate configure.sh, config.h.in, and proto.h
162: - page through the differences
1.1.1.4 ! misho 163: """)
! 164: ans = input("<Press Enter to continue> ")
1.1 misho 165:
1.1.1.4 ! misho 166: specvars = {
! 167: 'Version:': finalversion,
! 168: 'Release:': release,
! 169: '%define fullversion': f'%{{version}}{pre}',
! 170: 'Released': version + '.',
! 171: '%define srcdir': srcdir,
! 172: }
! 173:
! 174: tweak_files = 'version.h rsync.h NEWS.md'.split()
! 175: tweak_files += glob.glob('packaging/*.spec')
! 176: tweak_files += glob.glob('packaging/*/*.spec')
! 177:
! 178: for fn in tweak_files:
! 179: with open(fn, 'r', encoding='utf-8') as fh:
! 180: old_txt = txt = fh.read()
! 181: if fn == 'version.h':
! 182: txt = f'#define RSYNC_VERSION "{version}"\n'
! 183: elif '.spec' in fn:
! 184: for var, val in specvars.items():
! 185: x_re = re.compile(r'^%s .*' % re.escape(var), re.M)
! 186: txt = replace_or_die(x_re, var + ' ' + val, txt, f"Unable to update {var} in {fn}")
! 187: x_re = re.compile(r'^\* \w\w\w \w\w\w \d\d \d\d\d\d (.*)', re.M)
! 188: txt = replace_or_die(x_re, r'%s \1' % cl_today, txt, f"Unable to update ChangeLog header in {fn}")
! 189: elif fn == 'rsync.h':
! 190: x_re = re.compile('(#define\s+SUBPROTOCOL_VERSION)\s+(\d+)')
! 191: repl = lambda m: m[1] + ' ' + ('0' if not pre or not proto_changed else '1' if m[2] == '0' else m[2])
! 192: txt = replace_or_die(x_re, repl, txt, f"Unable to find SUBPROTOCOL_VERSION define in {fn}")
! 193: elif fn == 'NEWS.md':
! 194: efv = re.escape(finalversion)
! 195: x_re = re.compile(r'^<.+>\s+# NEWS for rsync %s \(UNRELEASED\)\s+## Changes in this version:\n' % efv
! 196: + r'(\n### PROTOCOL NUMBER:\s+- The protocol number was changed to \d+\.\n)?')
! 197: rel_day = 'UNRELEASED' if pre else today
! 198: repl = (f'<a name="{finalversion}"></a>\n\n# NEWS for rsync {finalversion} ({rel_day})\n\n'
! 199: + '## Changes in this version:\n')
! 200: if proto_changed:
! 201: repl += f'\n### PROTOCOL NUMBER:\n\n - The protocol number was changed to {protocol_version}.\n'
! 202: good_top = re.sub(r'\(.*?\)', '(UNRELEASED)', repl, 1)
! 203: msg = f"The top lines of {fn} are not in the right format. It should be:\n" + good_top
! 204: txt = replace_or_die(x_re, repl, txt, msg)
! 205: x_re = re.compile(r'^(\| )(\S{2} \S{3} \d{4})(\s+\|\s+%s\s+\| ).{11}(\s+\| )\S{2}(\s+\|+)$' % efv, re.M)
! 206: repl = lambda m: m[1] + (m[2] if pre else ztoday) + m[3] + proto_change_date + m[4] + protocol_version + m[5]
! 207: txt = replace_or_die(x_re, repl, txt, f'Unable to find "| ?? ??? {year} | {finalversion} | ... |" line in {fn}')
! 208: else:
! 209: die(f"Unrecognized file in tweak_files: {fn}")
! 210:
! 211: if txt != old_txt:
! 212: print(f"Updating {fn}")
! 213: with open(fn, 'w', encoding='utf-8') as fh:
! 214: fh.write(txt)
! 215:
! 216: cmd_chk(['packaging/year-tweak'])
! 217:
! 218: print(dash_line)
! 219: cmd_run("git diff --color | less -p '^diff .*'")
! 220:
! 221: srctar_name = f"{rsync_ver}.tar.gz"
! 222: pattar_name = f"rsync-patches-{version}.tar.gz"
! 223: diff_name = f"{rsync_lastver}-{version}.diffs.gz"
! 224: srctar_file = os.path.join(dest, srcdir, srctar_name)
! 225: pattar_file = os.path.join(dest, srcdir, pattar_name)
! 226: diff_file = os.path.join(dest, srcdiffdir, diff_name)
! 227: lasttar_file = os.path.join(dest, lastsrcdir, rsync_lastver + '.tar.gz')
1.1 misho 228:
1.1.1.4 ! misho 229: print(f"""\
! 230: {dash_line}
1.1 misho 231:
232: About to:
1.1.1.4 ! misho 233: - git commit all changes
! 234: - generate the manpages
! 235: - merge the {args.master_branch} branch into the patch/{args.master_branch}/* branches
! 236: - update the files in the "patches" dir and OPTIONALLY (if you type 'y') to
! 237: run patch-update with the --make option (which opens a shell on error)
! 238: """)
! 239: ans = input("<Press Enter OR 'y' to continue> ")
! 240:
! 241: s = cmd_run(['git', 'commit', '-a', '-m', f'Preparing for release of {version}'])
! 242: if s.returncode:
! 243: die('Aborting')
! 244:
! 245: cmd_chk('make gen')
! 246:
! 247: print(f'Creating any missing patch branches.')
! 248: s = cmd_run(f'packaging/branch-from-patch --branch={args.master_branch} --add-missing')
! 249: if s.returncode:
! 250: die('Aborting')
! 251:
! 252: print('Updating files in "patches" dir ...')
! 253: s = cmd_run(f'packaging/patch-update --branch={args.master_branch}')
! 254: if s.returncode:
! 255: die('Aborting')
! 256:
! 257: if re.match(r'^y', ans, re.I):
! 258: print(f'\nRunning smart-make on all "patch/{args.master_branch}/*" branches ...')
! 259: cmd_run(f"packaging/patch-update --branch={args.master_branch} --skip-check --make")
! 260:
! 261: if os.path.isdir('patches/.git'):
! 262: s = cmd_run(f"cd patches && git commit -a -m 'The patches for {version}.'")
! 263: if s.returncode:
! 264: die('Aborting')
1.1 misho 265:
1.1.1.4 ! misho 266: print(f"""\
! 267: {dash_line}
1.1 misho 268:
269: About to:
1.1.1.4 ! misho 270: - create signed tag for this release: {v_ver}
! 271: - create release diffs, "{diff_name}"
! 272: - create release tar, "{srctar_name}"
! 273: - generate {rsync_ver}/patches/* files
! 274: - create patches tar, "{pattar_name}"
! 275: - update top-level README.md, NEWS.md, TODO, and ChangeLog
1.1 misho 276: - update top-level rsync*.html manpages
277: - gpg-sign the release files
1.1.1.4 ! misho 278: - update hard-linked top-level release files{skipping}
! 279: """)
! 280: ans = input("<Press Enter to continue> ")
! 281:
! 282: # TODO: is there a better way to ensure that our passphrase is in the agent?
! 283: cmd_run("touch TeMp; gpg --sign TeMp; rm TeMp*")
! 284:
! 285: out = cmd_txt(f"git tag -s -m 'Version {version}.' {v_ver}", capture='combined')
! 286: print(out, end='')
! 287: if 'bad passphrase' in out or 'failed' in out:
! 288: die('Aborting')
! 289:
! 290: if os.path.isdir('patches/.git'):
! 291: out = cmd_txt(f"cd patches && git tag -s -m 'Version {version}.' {v_ver}", capture='combined')
! 292: print(out, end='')
! 293: if 'bad passphrase' in out or 'failed' in out:
! 294: die('Aborting')
! 295:
! 296: os.environ['PATH'] = ORIGINAL_PATH
! 297:
! 298: # Extract the generated files from the old tar.
! 299: tweaked_gen_files = [ os.path.join(rsync_lastver, fn) for fn in gen_files ]
! 300: cmd_run(['tar', 'xzf', lasttar_file, *tweaked_gen_files])
! 301: os.rename(rsync_lastver, 'a')
! 302:
! 303: print(f"Creating {diff_file} ...")
! 304: cmd_chk(['rsync', '-a', *gen_pathnames, 'b/'])
! 305:
! 306: sed_script = r's:^((---|\+\+\+) [ab]/[^\t]+)\t.*:\1:' # CAUTION: must not contain any single quotes!
! 307: cmd_chk(f"(git diff v{lastversion} {v_ver} -- ':!.github'; diff -upN a b | sed -r '{sed_script}') | gzip -9 >{diff_file}")
! 308: shutil.rmtree('a')
! 309: os.rename('b', rsync_ver)
! 310:
! 311: print(f"Creating {srctar_file} ...")
! 312: cmd_chk(f"git archive --format=tar --prefix={rsync_ver}/ {v_ver} | tar xf -")
! 313: cmd_chk(f"support/git-set-file-times --quiet --prefix={rsync_ver}/")
! 314: cmd_chk(['fakeroot', 'tar', 'czf', srctar_file, '--exclude=.github', rsync_ver])
! 315: shutil.rmtree(rsync_ver)
! 316:
! 317: print(f'Updating files in "{rsync_ver}/patches" dir ...')
! 318: os.mkdir(rsync_ver, 0o755)
! 319: os.mkdir(f"{rsync_ver}/patches", 0o755)
! 320: cmd_chk(f"packaging/patch-update --skip-check --branch={args.master_branch} --gen={rsync_ver}/patches".split())
! 321:
! 322: print(f"Creating {pattar_file} ...")
! 323: cmd_chk(['fakeroot', 'tar', 'chzf', pattar_file, rsync_ver + '/patches'])
! 324: shutil.rmtree(rsync_ver)
! 325:
! 326: print(f"Updating the other files in {dest} ...")
! 327: md_files = 'README.md NEWS.md INSTALL.md'.split()
! 328: html_files = [ fn for fn in gen_pathnames if fn.endswith('.html') ]
! 329: cmd_chk(['rsync', '-a', *md_files, *html_files, dest])
! 330: cmd_chk(["packaging/md2html"] + [ dest +'/'+ fn for fn in md_files ])
! 331:
! 332: cmd_chk(f"git log --name-status | gzip -9 >{dest}/ChangeLog.gz")
! 333:
! 334: for fn in (srctar_file, pattar_file, diff_file):
! 335: asc_fn = fn + '.asc'
! 336: if os.path.lexists(asc_fn):
! 337: os.unlink(asc_fn)
! 338: res = cmd_run(['gpg', '--batch', '-ba', fn])
! 339: if res.returncode != 0 and res.returncode != 2:
! 340: die("gpg signing failed")
! 341:
! 342: if not pre:
! 343: for find in f'{dest}/rsync-*.gz {dest}/rsync-*.asc {dest}/src-previews/rsync-*diffs.gz*'.split():
! 344: for fn in glob.glob(find):
! 345: os.unlink(fn)
! 346: top_link = [
! 347: srctar_file, f"{srctar_file}.asc",
! 348: pattar_file, f"{pattar_file}.asc",
! 349: diff_file, f"{diff_file}.asc",
! 350: ]
! 351: for fn in top_link:
! 352: os.link(fn, re.sub(r'/src(-\w+)?/', '/', fn))
1.1 misho 353:
1.1.1.4 ! misho 354: print(f"""\
! 355: {dash_line}
1.1 misho 356:
357: Local changes are done. When you're satisfied, push the git repository
358: and rsync the release files. Remember to announce the release on *BOTH*
359: rsync-announce@lists.samba.org and rsync@lists.samba.org (and the web)!
1.1.1.4 ! misho 360: """)
! 361:
! 362:
! 363: def replace_or_die(regex, repl, txt, die_msg):
! 364: m = regex.search(txt)
! 365: if not m:
! 366: die(die_msg)
! 367: return regex.sub(repl, txt, 1)
! 368:
! 369:
! 370: def signal_handler(sig, frame):
! 371: die("\nAborting due to SIGINT.")
! 372:
1.1 misho 373:
1.1.1.4 ! misho 374: if __name__ == '__main__':
! 375: parser = argparse.ArgumentParser(description="Prepare a new release of rsync in the git repo & ftp dir.", add_help=False)
! 376: parser.add_argument('--branch', '-b', dest='master_branch', default='master', help="The branch to release. Default: master.")
! 377: parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
! 378: args = parser.parse_args()
! 379: main()
1.1 misho 380:
1.1.1.4 ! misho 381: # vim: sw=4 et ft=python
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>