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

67 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-08-07 09:36 +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): 

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: 

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}": 

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 options.repo(["checkout", master]) 

102 print( # noqa: T201 

103 tools.indent( 

104 f""" 

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

106 

107 To complete the release: 

108 git push origin beta/{version} 

109 

110 To revert this beta branch: 

111 git branch -D beta/{version} 

112 """ 

113 ), 

114 file=sys.stderr, 

115 ) 

116 elif options.mode in {"micro", "minor", "major"}: 

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

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

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

120 options.error( 

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

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

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

124 ) 

125 return 

126 local = match.group("beta") 

127 if local != version: 

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

129 

130 # create an empty commit to mark the release 

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

132 

133 # tag 

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

135 

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

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

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

139 

140 # bump version 

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

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

143 

144 # commit 

145 options.repo.commit( 

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

147 ) 

148 

149 print( # noqa: T201 

150 tools.indent( 

151 f""" 

152 The release is almost complete. 

153 

154 To complete the release: 

155 git push origin release/{version} 

156 git push origin master 

157 

158 To revert this release: 

159 git reset --hard HEAD~1 

160 git tag -d release/{version} 

161 """ 

162 ), 

163 file=sys.stderr, 

164 ) 

165 else: 

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

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

168 

169 

170if __name__ == "__main__": 

171 main()