pypiserver/bin/bumpver.py

276 lines
7.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python
#
# NEED POSIX (i.e. *Cygwin* on Windows).
"""
Script to bump, commit and tag new versions.
USAGE:
bumpver
bumpver [-n] [-f] [-c] [-a] [-t <message>] <new-ver>
Without <new-ver> prints version extracted from current file.
Don't add a 'v' prefix!
OPTIONS:
-a, --amend Amend current commit for setting the "chore(ver): ..." msg.
-f, --force Bump (and optionally) commit/tag if version exists/is same.
-n, --dry-run Do not write files - just pretend.
-c, --commit Commit afterwardswith a commit-message describing version bump.
-t, --tag=<msg> Adds a signed tag with the given message (commit implied).
2018-06-13 02:58:31 +02:00
- Pre-releases: when working on some version
X.YbN # Beta release
X.YrcN or X.YcN # Release Candidate
X.Y # Final release
- Post-release:
X.YaN.postM # Post-release of an alpha release
X.YrcN.postM # Post-release of a release candidate
- Dev-release:
X.YaN.devM # Developmental release of an alpha release
X.Y.postN.devM # Developmental release of a post-release
EXAMPLE:
bumpver -t 'Mostly model changes' 1.6.2b0
"""
import functools as fnt
import os.path as osp
import re
import sys
from datetime import datetime
import docopt
my_dir = osp.dirname(__file__)
VFILE = osp.join(my_dir, "..", "pypiserver", "__init__.py")
VFILE_regex_version = re.compile(r'version *= *__version__ *= *"([^"]+)"')
VFILE_regex_datetime = re.compile(r'__updated__ *= *"([^"]+)"')
VFILE_regex_date = re.compile(r'__updated__ *= *"([^"\s]+)\s')
Feature/markdown conversion (#503) * feat: markdown conversion logo and badges * feat: markdown conversion fix logo path * feat: markdown table changes * feat: markdown table alignment * feat: markdown check toc * feat: markdown toc additions * feat: markdown quickstart section * feat: dependabot more details section in quick start * feat: dependabot correct bold * feat: markdown client side config, pip * feat: markdown typo in title * feat: markdown typo in configuring pip * feat: markdown apache like authentication section an initial cut to view the markdown in github * feat: markdown typo in markdown link * feat: markdown remove trailing colon * feat: markdown typo in shell markdown * feat: markdown standardize on 4 space indent in shell code block * feat: markdown complete markdown for section up to alternate installation methods * feat: markdown add more of the contents to test with * feat: markdown contents * feat: markdown contents * feat: markdown contents * feat: markdown dquote> dquote> recipes * feat: markdown dquote> dquote> recipes * feat: markdown dquote> dquote> up to licensing * feat: markdown dquote> dquote> contents * Update README.md Missing exclamation mark * Update README.md missing link * Update README.md remove duplicated text * Update README.md bold differences it Table of contents * Update README.md additional bold changes in table of contents * Update README.md broken link * Update README.md typo in link fix * Update README.md change code block to text as shell highlighting was showing some items in red * Update README.md code block shell to text * Update README.md correct pypi-server update section * feat: markdown dquote> dquote> link back to TOC title * Update README.md change link to TOC title * Update README.md link test * Update README.md link update * Update README.md link update * Update README.md link update * feat: markdown links * Update README.md change the level of indent for uploading packages remotely * Update README.md add link to python-pam * feat: markdown apache link to TOC not working. * Update README.md grammar * Update README.md typo bold * feat: markdown undo bolded text in TOC and titles as linking does not work * feat: markdown remove bold from TOC * feat: feature more link issues * feat: markdown fixing broken links * feat: markdown change text slightly as markdown only links to plain text * feat: markdown typo * feat: markdown more link typos * Update README.md typo in link * Update README.md link will not work with braces in the titles * feat: markdown run mdformat and apply changes, :) lint! * feat: markdown - check via mdformat - remove old check script - update test-requirements.txt * feat: markdown correct the errors in the mdformat run command * feat: markdown for testing remove all the actual jobs * feat: markdown re-run mdformat * feat: markdown put the jobs back in after testing the mdformat cmd for passing and failing via workflow dispatch * feat: markdown remove references to README.md * feat: markdown change action to workflow dispatch for testing * feat: markdown - update docker igore - alter unit test to look for version number after md changes * feat: markdown black linting * feat: markdown update comments * feat: markdown update bumpver to look at md rather than rst file * feat: markdown replace workflow dispatch with pull request to get ready for the final PR * feat: markdown-delete-original delete the original rst file * feat: markdown-delete-original change ci to workflow dispatch for testing * feat: markdown-delete-original revert workflow dispatch * feat: markdown-badge-links set the links back to the original URLs. * feat: markdown-badge-links fix brackets * feat: markdown update the version and date * feat: markdown conversion markdown changes to conform to mdformat tooling.
2023-08-15 11:16:30 +02:00
RFILE = osp.join(my_dir, "..", "README.md")
PYTEST_ARGS = [osp.join("tests", "test_docs.py")]
class CmdException(Exception):
pass
def get_current_date_info() -> (str, str):
now = datetime.now()
new_datetime = now.strftime("%Y-%m-%d %H:%M:%S%z")
new_date = now.strftime("%Y-%m-%d")
return (new_datetime, new_date)
@fnt.lru_cache()
def read_txtfile(fpath):
with open(fpath, "rt", encoding="utf-8") as fp:
return fp.read()
def extract_file_regexes(fpath, regexes):
"""
:param regexes:
A sequence of regexes to "search", having a single capturing-group.
:return:
One groups per regex, or raise if any regex did not match.
"""
txt = read_txtfile(fpath)
matches = [regex.search(txt) for regex in regexes]
if not all(matches):
raise CmdException(
"Failed extracting current versions with: %s"
"\n matches: %s" % (regexes, matches)
)
return [m.group(1) for m in matches]
def replace_substrings(files, subst_pairs):
for fpath in files:
txt = read_txtfile(fpath)
replacements = []
for old, new in subst_pairs:
replacements.append((old, new, txt.count(old)))
txt = txt.replace(old, new)
yield (txt, fpath, replacements)
def format_syscmd(cmd):
if isinstance(cmd, (list, tuple)):
cmd = " ".join('"%s"' % s if " " in s else s for s in cmd)
else:
assert isinstance(cmd, str), cmd
return cmd
def strip_ver2_commonprefix(ver1, ver2):
cprefix = osp.commonprefix([ver1, ver2])
if cprefix:
striplen = cprefix.rfind(".")
if striplen > 0:
striplen += 1
else:
striplen = len(cprefix)
ver2 = ver2[striplen:]
return ver2
def run_testcases():
import pytest
retcode = pytest.main(PYTEST_ARGS)
if retcode:
raise CmdException(
"Doc TCs failed(%s), probably version-bumping has failed!" % retcode
)
def exec_cmd(cmd):
import subprocess as sbp
err = sbp.call(cmd, stderr=sbp.STDOUT)
if err:
raise CmdException("Failed(%i) on: %s" % (err, format_syscmd(cmd)))
def do_commit(new_ver, old_ver, dry_run, amend, ver_files):
import pathlib
cmt_msg = "chore(ver): bump %s-->%s" % (old_ver, new_ver)
ver_files = [pathlib.Path(f).as_posix() for f in ver_files]
git_add = ["git", "add"] + ver_files
git_cmt = ["git", "commit", "-m", cmt_msg]
if amend:
git_cmt.append("--amend")
commands = [git_add, git_cmt]
for cmd in commands:
cmd_str = format_syscmd(cmd)
if dry_run:
yield "DRYRUN: %s" % cmd_str
else:
yield "EXEC: %s" % cmd_str
exec_cmd(cmd)
def do_tag(tag, tag_msg, dry_run, force):
cmd = ["git", "tag", tag, "-s", "-m", tag_msg]
if force:
cmd.append("--force")
cmd_str = format_syscmd(cmd)
if dry_run:
yield "DRYRUN: %s" % cmd_str
else:
yield "EXEC: %s" % cmd_str
exec_cmd(cmd)
def bumpver(
new_ver, dry_run=False, force=False, amend=False, tag_name_or_commit=None
):
"""
:param tag_name_or_commit:
if true, do `git commit`, if string, also `git tag` with that as msg.
"""
if amend:
## Restore previous version before extracting it.
cmd = "git checkout HEAD~ --".split()
cmd.append(VFILE)
cmd.append(RFILE)
exec_cmd(cmd)
regexes = [VFILE_regex_version, VFILE_regex_datetime, VFILE_regex_date]
old_ver, old_datetime, old_date = extract_file_regexes(VFILE, regexes)
if not new_ver:
yield old_ver
yield old_datetime
yield old_date
else:
if new_ver == old_ver:
msg = "Version '%s'already bumped"
if force:
msg += ", but --force effected."
yield msg % new_ver
else:
msg += "!\n Use of --force recommended."
raise CmdException(msg % new_ver)
new_datetime, new_date = get_current_date_info()
ver_files = [osp.normpath(f) for f in [VFILE, RFILE]]
subst_pairs = [
(old_ver, new_ver),
(old_datetime, new_datetime),
(old_date, new_date),
]
for repl in replace_substrings(ver_files, subst_pairs):
new_txt, fpath, replacements = repl
if not dry_run:
with open(fpath, "wt", encoding="utf-8") as fp:
fp.write(new_txt)
yield "%s: " % fpath
for old, new, nrepl in replacements:
yield " %i x (%24s --> %s)" % (nrepl, old, new)
yield "...now launching DocTCs..."
run_testcases()
if tag_name_or_commit is not None:
yield from do_commit(new_ver, old_ver, dry_run, amend, ver_files)
if isinstance(tag_name_or_commit, str):
tag = "v%s" % new_ver
yield from do_tag(tag, tag_name_or_commit, dry_run, force)
def main(*args):
opts = docopt.docopt(__doc__, argv=args)
new_ver = opts["<new-ver>"]
assert not new_ver or new_ver[0] != "v", (
"Version '%s' must NOT start with `v`!" % new_ver
)
commit = opts["--commit"]
tag = opts["--tag"]
if tag:
tag_name_or_commit = tag
elif commit:
tag_name_or_commit = True
else:
tag_name_or_commit = None
try:
for i in bumpver(
new_ver,
opts["--dry-run"],
opts["--force"],
opts["--amend"],
tag_name_or_commit,
):
print(i)
except CmdException as ex:
sys.exit(str(ex))
except Exception as ex:
print("Unexpected error happened.")
raise ex
if __name__ == "__main__":
main(*sys.argv[1:])