2929logging .basicConfig (level = logging .DEBUG )
3030logger = logging .getLogger (__name__ )
3131
32- # preferred repository order
33- repo_order = [
34- "activitywatch" ,
35- "aw-server" ,
36- "aw-server-rust" ,
37- "aw-webui" ,
38- "aw-watcher-afk" ,
39- "aw-watcher-window" ,
40- "aw-qt" ,
41- "aw-core" ,
42- "aw-client" ,
43- ]
32+
33+ def main ():
34+ prev_release = run ("git describe --tags --abbrev=0" ).strip ()
35+ next_release = "master"
36+
37+ parser = argparse .ArgumentParser (description = "Generate changelog from git history" )
38+
39+ parser .add_argument ("--org" , default = "ActivityWatch" , help = "GitHub organization" )
40+ parser .add_argument ("--repo" , default = "activitywatch" , help = "GitHub repository" )
41+ parser .add_argument (
42+ "--project-title" , default = "ActivityWatch" , help = "Project title"
43+ )
44+
45+ parser .add_argument (
46+ "--range" , default = f"{ prev_release } ...{ next_release } " , help = "Git commit range"
47+ )
48+ parser .add_argument ("--path" , default = "." , help = "Path to git repo" )
49+ parser .add_argument (
50+ "--output" , default = "changelog.md" , help = "Path to output changelog"
51+ )
52+ args = parser .parse_args ()
53+
54+ since , until = args .range .split ("..." , 1 )
55+
56+ # preferred output order for submodules
57+ repo_order = [
58+ "activitywatch" ,
59+ "aw-server" ,
60+ "aw-server-rust" ,
61+ "aw-webui" ,
62+ "aw-watcher-afk" ,
63+ "aw-watcher-window" ,
64+ "aw-qt" ,
65+ "aw-core" ,
66+ "aw-client" ,
67+ ]
68+
69+ build (
70+ args .org ,
71+ args .repo ,
72+ args .project_title ,
73+ commit_range = (since , until ),
74+ output_path = args .output ,
75+ repo_order = repo_order ,
76+ )
4477
4578
4679class CommitMsg :
@@ -53,25 +86,26 @@ class CommitMsg:
5386class Commit :
5487 id : str
5588 msg : str
89+ org : str
5690 repo : str
5791
5892 @property
5993 def msg_processed (self ) -> str :
6094 """Generates links from commit and issue references (like 0c14d77, #123) to correct repo and such"""
6195 s = self .msg
6296 s = re .sub (
63- r "[^(-]https://github.com/ActivityWatch /([\-\w\d]+)/(issues|pulls)/(\d+)" ,
64- r "[#\3](https://github.com/ActivityWatch /\1/issues/\3)" ,
97+ rf "[^(-]https://github.com/{ self . org } /([\-\w\d]+)/(issues|pulls)/(\d+)" ,
98+ rf "[#\3](https://github.com/{ self . org } /\1/issues/\3)" ,
6599 s ,
66100 )
67101 s = re .sub (
68102 r"#(\d+)" ,
69- rf"[#\1](https://github.com/ActivityWatch /{ self .repo } /issues/\1)" ,
103+ rf"[#\1](https://github.com/{ self . org } /{ self .repo } /issues/\1)" ,
70104 s ,
71105 )
72106 s = re .sub (
73107 r"[\s\(][0-9a-f]{7}[\s\)]" ,
74- rf"[`\0`](https://github.com/ActivityWatch /{ self .repo } /issues/\0)" ,
108+ rf"[`\0`](https://github.com/{ self . org } /{ self .repo } /issues/\0)" ,
75109 s ,
76110 )
77111 return s
@@ -101,7 +135,7 @@ def type_str(self) -> str:
101135 return f"{ _type } " + (f"({ subtype } )" if subtype else "" )
102136
103137 def format (self ) -> str :
104- commit_link = commit_linkify (self .id , self .repo ) if self .id else ""
138+ commit_link = commit_linkify (self .id , self .org , self . repo ) if self .id else ""
105139
106140 return f"{ self .msg_processed } " + (f" ({ commit_link } )" if commit_link else "" )
107141
@@ -116,59 +150,63 @@ def run(cmd, cwd=".") -> str:
116150 return p .stdout
117151
118152
119- def pr_linkify (prid : str , repo : str ) -> str :
120- return f"[#{ prid } ](https://github.com/ActivityWatch /{ repo } /pulls/{ prid } )"
153+ def pr_linkify (prid : str , org : str , repo : str ) -> str :
154+ return f"[#{ prid } ](https://github.com/{ org } /{ repo } /pulls/{ prid } )"
121155
122156
123- def commit_linkify (commitid : str , repo : str ) -> str :
124- return f"[`{ commitid } `](https://github.com/ActivityWatch /{ repo } /commit/{ commitid } )"
157+ def commit_linkify (commitid : str , org : str , repo : str ) -> str :
158+ return f"[`{ commitid } `](https://github.com/{ org } /{ repo } /commit/{ commitid } )"
125159
126160
127161def wrap_details (title , body , wraplines = 5 ):
128162 """Wrap lines into a <details> element if body is longer than `wraplines`"""
129163 out = f"\n \n ### { title } "
130- if body .count ("\n " ) > wraplines :
131- out += "\n <details><summary>Click to expand</summary>"
132- out += f"\n <p>\n \n { body .rstrip ()} \n \n </p>\n "
133- if body .count ("\n " ) > wraplines :
134- out += "</details>"
164+ wrap = body .strip ().count ("\n " ) > wraplines
165+ if wrap :
166+ out += "\n <details><summary>Click to expand</summary>\n <p>"
167+ out += f"\n { body .rstrip ()} \n \n "
168+ if wrap :
169+ out += "</p>\n </details>"
135170 return out
136171
137172
138173contributor_emails = set ()
139174
140175
141- def summary_repo (path : str , commitrange : str , filter_types : List [str ]) -> str :
142- if commitrange .endswith ("0000000" ):
176+ def summary_repo (
177+ org : str ,
178+ repo : str ,
179+ path : str ,
180+ commit_range : Tuple [str , str ],
181+ filter_types : List [str ],
182+ repo_order : List [str ],
183+ ) -> str :
184+ if commit_range [1 ] == "0000000" :
143185 # Happens when a submodule has been removed
144186 return ""
145- if commitrange . startswith ( "0000000" ) :
187+ if commit_range [ 0 ] == "0000000" :
146188 # Happens when a submodule has been added
147- commitrange = "" # no range = all commits
189+ commit_range = ( "" , "" ) # no range = all commits for new submodule
148190
149- dirname = run ("bash -c 'basename $(pwd)'" , cwd = path ).strip ()
150- out = f"\n ## 📦 { dirname } "
191+ out = f"\n ## 📦 { repo } "
151192
152193 feats = ""
153194 fixes = ""
154195 misc = ""
155196
156197 # pretty format is modified version of: https://stackoverflow.com/a/1441062/965332
157198 summary_bundle = run (
158- f"git log { commitrange } --no-decorate --pretty=format:'%h%x09%an%x09%ae%x09%s'" ,
199+ f"git log { '...' . join ( commit_range ) if any ( commit_range ) else '' } --no-decorate --pretty=format:'%h%x09%an%x09%ae%x09%s'" ,
159200 cwd = path ,
160201 )
202+ print (f"Found { len (summary_bundle .splitlines ())} commits in { repo } " )
161203 for line in summary_bundle .split ("\n " ):
162204 if line :
163205 _id , _author , email , msg = line .split ("\t " )
164206 # will add author email to contributor list
165207 # the `contributor_emails` is global and collected later
166208 contributor_emails .add (email )
167- commit = Commit (
168- id = _id ,
169- msg = msg ,
170- repo = dirname ,
171- )
209+ commit = Commit (id = _id , msg = msg , org = org , repo = repo )
172210
173211 entry = f"\n - { commit .format ()} "
174212 if commit .type == "feat" :
@@ -192,7 +230,7 @@ def summary_repo(path: str, commitrange: str, filter_types: List[str]) -> str:
192230 # TODO: Fix issue where subsubmodules can appear twice (like aw-webui)
193231 # TODO: Use specific order (aw-webui should be one of the first, for example)
194232 summary_subrepos = run (
195- f"git submodule summary --cached { commitrange . split ( '...' ) [0 ]} " , cwd = path
233+ f"git submodule summary --cached { commit_range [0 ]} " , cwd = path
196234 )
197235 subrepos = {}
198236 for header , * _ in [s .split ("\n " ) for s in summary_subrepos .split ("\n \n " )]:
@@ -204,15 +242,21 @@ def summary_repo(path: str, commitrange: str, filter_types: List[str]) -> str:
204242 # Submodule may have been deleted
205243 continue
206244
207- _ , name , commitrange , count = header .split (" " )
245+ _ , name , crange , count = header .split (" " )
246+ commit_range = tuple (crange .split ("..." , 1 )) # type: ignore
208247 count = count .strip ().lstrip ("(" ).rstrip ("):" )
209248 logger .info (
210- f"Found { name } , looking up range: { commitrange } ({ count } commits)"
249+ f"Found { name } , looking up range: { commit_range } ({ count } commits)"
211250 )
212251 name = name .strip ("." ).strip ("/" )
213252
214253 subrepos [name ] = summary_repo (
215- f"{ path } /{ name } " , commitrange , filter_types = filter_types
254+ org ,
255+ repo ,
256+ f"{ path } /{ name } " ,
257+ commit_range ,
258+ filter_types = filter_types ,
259+ repo_order = repo_order ,
216260 )
217261
218262 # pick subrepos in repo_order, and remove from dict
@@ -266,35 +310,33 @@ def remove_duplicates(s: List[str], minlen=10, only_sections=True) -> List[str]:
266310 return out
267311
268312
269- def build (filter_types = ["build" , "ci" , "tests" , "test" ]):
270- prev_release = run ("git describe --tags --abbrev=0" ).strip ()
271- next_release = "master"
272-
273- parser = argparse .ArgumentParser (description = "Generate changelog from git history" )
274- parser .add_argument (
275- "--range" , default = f"{ prev_release } ...{ next_release } " , help = "Git commit range"
276- )
277- parser .add_argument ("--path" , default = "." , help = "Path to git repo" )
278- parser .add_argument (
279- "--output" , default = "changelog.md" , help = "Path to output changelog"
280- )
281- args = parser .parse_args ()
282-
283- since , until = args .range .split ("..." )
284- tag = until
285-
313+ def build (
314+ org : str ,
315+ repo : str ,
316+ project_name : str ,
317+ commit_range : Tuple [str , str ],
318+ output_path : str ,
319+ repo_order : List [str ],
320+ filter_types = ["build" , "ci" , "tests" , "test" ],
321+ ):
286322 # provides a commit summary for the repo and subrepos, recursively looking up subrepos
287323 # NOTE: this must be done *before* `get_all_contributors` is called,
288324 # as the latter relies on summary_repo looking up all users and storing in a global.
289325 logger .info ("Generating commit summary" )
326+ since , tag = commit_range
290327 output_changelog = summary_repo (
291- "." , commitrange = args .range , filter_types = filter_types
328+ org ,
329+ repo ,
330+ "." ,
331+ commit_range = commit_range ,
332+ filter_types = filter_types ,
333+ repo_order = repo_order ,
292334 )
293335
294336 output_changelog = f"""
295337# Changelog
296338
297- Changes since { since }
339+ Changes since { since } :
298340
299341{ output_changelog }
300342 """ .strip ()
@@ -316,29 +358,34 @@ def build(filter_types=["build", "ci", "tests", "test"]):
316358
317359 # Header starts here
318360 logger .info ("Building final output" )
319- output = f"These are the release notes for ActivityWatch version { tag } ." .strip ()
320- output += "\n \n "
321- output += "**New to ActivityWatch?** Check out the [website](https://activitywatch.net) and the [README](https://github.com/ActivityWatch/activitywatch/blob/master/README.md)."
361+ output = f"These are the release notes for { project_name } version { tag } ." .strip ()
322362 output += "\n \n "
323- output += """# Installation
363+
364+ # hardcoded for now
365+ if repo == "activitywatch" :
366+ output += "**New to ActivityWatch?** Check out the [website](https://activitywatch.net) and the [README](https://github.com/ActivityWatch/activitywatch/blob/master/README.md)."
367+ output += "\n \n "
368+ output += """# Installation
324369
325370See the [getting started guide in the documentation](https://docs.activitywatch.net/en/latest/getting-started.html).
326- """ .strip ()
327- output += "\n \n "
328- output += f"""# Downloads
371+ """ .strip ()
372+ output += "\n \n "
373+ output += f"""# Downloads
329374
330375 - [**Windows**](https://github.com/ActivityWatch/activitywatch/releases/download/{ tag } /activitywatch-{ tag } -windows-x86_64-setup.exe) (.exe, installer)
331376 - [**macOS**](https://github.com/ActivityWatch/activitywatch/releases/download/{ tag } /activitywatch-{ tag } -macos-x86_64.dmg) (.dmg)
332377 - [**Linux**](https://github.com/ActivityWatch/activitywatch/releases/download/{ tag } /activitywatch-{ tag } -linux-x86_64.zip) (.zip)
333- """ .strip ()
334- output += "\n \n "
378+ """ .strip ()
379+ output += "\n \n "
380+
335381 output += output_contributors .strip () + "\n \n "
336382 output += output_changelog .strip () + "\n \n "
337383
338- output = output .replace ("# activitywatch" , "# activitywatch (bundle repo)" )
339- with open (args .output , "w" ) as f :
384+ if repo == "activitywatch" :
385+ output = output .replace ("# activitywatch" , "# activitywatch (bundle repo)" )
386+ with open (output_path , "w" ) as f :
340387 f .write (output )
341- print (f"Wrote { len (output .splitlines ())} lines to { args . output } " )
388+ print (f"Wrote { len (output .splitlines ())} lines to { output_path } " )
342389
343390
344391def _resolve_email (email : str ) -> Optional [str ]:
@@ -494,4 +541,4 @@ def get_twitter_of_ghusers(ghusers: Collection[str]):
494541
495542
496543if __name__ == "__main__" :
497- build ()
544+ main ()
0 commit comments