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

66 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-07 20:20 +0000

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

2 

3This script will either create a new beta branch: 

4 

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

6 

7Or 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""" 

12from __future__ import annotations 

13 

14import argparse 

15import logging 

16import re 

17import sys 

18from pathlib import Path 

19 

20from . import cli, scm, tools 

21 

22log = logging.getLogger(__name__) 

23 

24 

25def 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 

38def 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__) 

68def 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 

169if __name__ == "__main__": 

170 main()