Coverage for src/hatch_ci/script.py: 84%

1"""create a beta branch or release a beta branch

2

3 This script will either create a new beta branch:

4

5 hatch-ci make-beta ./src/package_name/__init__.py

6

7 Or will release the beta branch and will move inot the next minor

8

9 hatch-ci {major|minor|micro} ./src/package_name/__init__.py

10

11"""

12 from __future__ import annotations

13

14 import argparse

15 import logging

16 import re

17 import sys

18 from pathlib import Path

19

20 from . import cli, scm, tools

21

22 log = logging.getLogger(__name__)

23

24

25 def add_arguments(parser: argparse.ArgumentParser):

26 parser.add_argument("--master", help="the 'master' branch")

27 parser.add_argument(

28 "-w",

29 "--workdir",

30 help="git working dir",

31 default=Path("."),

32 type=Path,

33 )

34 parser.add_argument("mode", choices=["micro", "minor", "major", "make-beta"])

35 parser.add_argument("initfile", metavar="__init__.py", type=Path)

36

37

38 def process_options(

39 options: argparse.Namespace, error: cli.ErrorFn

40) -> argparse.Namespace:

41 try:

42 options.repo = repo = scm.GitRepo(options.workdir)

43 repo.status()

44 except scm.GitError:

45 error(

46 "no git directory",

47 "It looks the repository is not a git repo",

48 hint="init the git directory",

49 )

50 log.info("working dir set to '%s'", options.workdir)

51 try:

52 branch = repo.head.shorthand

53 log.info("current branch set to '%s'", branch)

54 except scm.GitError:

55 error(

56 "invalid git repository",

57 """

58 It looks the repository doesn't have any branch,

59 you should:

60 git checkout --orphan <branch-name>

61 """,

62 hint="create a git branch",

63 )

64 return options

65

66

67@cli.cli(add_arguments, process_options, __doc__)

68 def main(options) -> None:

69 # master branch

70 master = options.master or (

71 options.repo.config["init.defaultbranch"]

72 if "init.defaultbranch" in options.repo.config

73 else "master"

74 )

75

76 if options.repo.status(untracked_files="no", ignored=False): 76 ↛ 77line 76 didn't jump to line 77, because the condition on line 76 was never true

77 options.error(f"modified files in {options.repo.workdir}")

78 if not options.initfile.exists():

79 options.error(f"cannot find version file {options.initfile}")

80

81 version = tools.get_module_var(options.initfile, "__version__")

82 log.info("got version %s for branch '%s'", version, options.repo.head.shorthand)

83 if not version: 83 ↛ 84line 83 didn't jump to line 84, because the condition on line 83 was never true

84 raise tools.InvalidVersionError(f"cannot find a version in {options.initfile}")

85

86 # fetching all remotes

87 options.repo(["fetch", "--all"])

88

89 if options.mode == "make-beta":

90 if options.repo.head.name != f"refs/heads/{master}": 90 ↛ 91line 90 didn't jump to line 91, because the condition on line 90 was never true

91 options.error(

92 f"wrong branch '{options.repo.head.name}', expected '{master}'"

93 )

94

95 for branch in [*options.repo.branches.local, *options.repo.branches.remote]:

96 if not branch.endswith(f"beta/{version}"):

97 continue

98 options.error(f"branch '{branch}' already present")

99 log.info("creating branch '%s'", f"/beta/{version}")

100 options.repo.branch(f"beta/{version}", master)

101 print( # noqa: T201

102 tools.indent(

103 f"""

104 The release branch beta/{version} has been created.

105

106 To complete the release:

107 git push origin beta/{version}

108

109 To revert this beta branch:

110 git branch -D beta/{version}

111 """

112 ),

113 file=sys.stderr,

114 )

115 elif options.mode in {"micro", "minor", "major"}: 115 ↛ 165line 115 didn't jump to line 165, because the condition on line 115 was never false

116 # we need to be in the beta/N.M.O branch

117 expr = re.compile(r"refs/heads/beta/(?P<beta>\d+([.]\d+)*)$")

118 if not (match := expr.search(options.repo.head.name)):

119 options.error(

120 f"wrong branch '{options.repo.head.shorthand}'",

121 f"expected to be in 'beta/{version}' branch",

122 f"git checkout beta/{version}",

123 )

124 return

125 local = match.group("beta")

126 if local != version: 126 ↛ 127line 126 didn't jump to line 127, because the condition on line 126 was never true

127 options.error(f"wrong version file {version=} != {local}")

128

129 # create an empty commit to mark the release

130 options.repo(["commit", "--allow-empty", "-m", f"released {version}"])

131

132 # tag

133 options.repo(["tag", "-a", f"release/{version}", "-m", f"released {version}"])

134

135 # switch to master (and incorporate the commit message)

136 options.repo(["checkout", master])

137 options.repo(["merge", f"beta/{version}"])

138

139 # bump version

140 new_version = tools.bump_version(version, options.mode)

141 tools.set_module_var(options.initfile, "__version__", new_version)

142

143 # commit

144 options.repo.commit(

145 options.initfile, f"version bump {version} -> {new_version}"

146 )

147

148 print( # noqa: T201

149 tools.indent(

150 f"""

151 The release is almost complete.

152

153 To complete the release:

154 git push origin release/{version}

155 git push origin master

156

157 To revert this release:

158 git reset --hard HEAD~1

159 git tag -d release/{version}

160 """

161 ),

162 file=sys.stderr,

163 )

164 else:

165 options.error(f"unsupported mode {options.mode=}")

166 raise RuntimeError(f"unsupported mode {options.mode=}")

167

168

169 if __name__ == "__main__":

170 main()