|
4 | 4 | import sys |
5 | 5 | from .version import __version__ |
6 | 6 |
|
7 | | -def generate_release_notes(project_id, endstr = ' <br>', since=None, quiet=False, target_branch=None, **config): |
| 7 | +def generate_release_notes(project_id, endstr=' <br>', since=None, target_branch=None, **config): |
8 | 8 | """ |
9 | | - Generate the release notes of a gitlab project from the last release |
| 9 | + Generate the release notes of a gitlab project. |
10 | 10 |
|
11 | 11 | Parameters |
12 | 12 | ---------- |
13 | 13 | project_id: int |
14 | 14 | ID of the project |
| 15 | + endstr: str |
| 16 | + String to use for line endings (e.g., ' <br>' for HTML, '\n' for plain text) |
| 17 | + since: Optional[str] |
| 18 | + A date string (YYYY-MM-DDTHH:MM:SSZ) or a tag/reference. |
| 19 | + If provided, release notes will be generated for MRs merged after this point. |
| 20 | + Overrides fetching MRs since the last release. |
| 21 | + target_branch: Optional[str] |
| 22 | + The target branch to filter merge requests by. |
15 | 23 | config: dict |
| 24 | + Additional keyword arguments for the gitlab.Gitlab client, such as: |
16 | 25 | url: Optional[str] = None, |
17 | 26 | private_token: Optional[str] = None, |
18 | | - oauth_token: Optional[str] = None, |
19 | | - job_token: Optional[str] = None, |
20 | | - ssl_verify: Union[bool, str] = True, |
21 | | - http_username: Optional[str] = None, |
22 | | - http_password: Optional[str] = None, |
23 | | - timeout: Optional[float] = None, |
24 | | - api_version: str = '4', |
25 | | - session: Optional[requests.sessions.Session] = None, |
26 | | - per_page: Optional[int] = None, |
27 | | - pagination: Optional[str] = None, |
28 | | - order_by: Optional[str] = None, |
29 | | - user_agent: str = 'python-gitlab/3.1.0', |
30 | | - retry_transient_errors: bool = False, |
| 27 | + # ... (other gitlab.Gitlab config options) |
31 | 28 | """ |
32 | 29 |
|
33 | 30 | gl = gitlab.Gitlab(**config) |
34 | 31 | project = gl.projects.get(project_id) |
35 | 32 |
|
36 | | - if not project.mergerequests.list(get_all=False,state='merged', target_branch=target_branch): |
37 | | - raise ValueError(f"There is no merged merge request for project {project_id} {project.name}") |
38 | | - |
39 | | - log = "" |
| 33 | + # Initial check for any merged MRs (can be refined if needed) |
| 34 | + # This check doesn't use target_branch yet, it's a general check. |
| 35 | + # Consider if this check should also incorporate target_branch if provided. |
| 36 | + # For now, keeping it as is. |
| 37 | + initial_mr_check_params = {'state': 'merged', 'per_page': 1} |
| 38 | + if target_branch: # Optionally make the initial check more specific |
| 39 | + initial_mr_check_params['target_branch'] = target_branch |
| 40 | + |
| 41 | + if not project.mergerequests.list(get_all=False, **initial_mr_check_params): |
| 42 | + error_message = f"There are no merged merge requests for project {project_id} ({project.name})" |
| 43 | + if target_branch: |
| 44 | + error_message += f" on branch '{target_branch}'" |
| 45 | + raise ValueError(error_message) |
40 | 46 |
|
41 | 47 | if since: |
42 | | - log_pending = f"Changelog of {project.name} since {since}:{endstr}" |
43 | | - last_date = since |
44 | | - elif not project.releases.list(get_all=False): |
45 | | - log_pending = f"Changelog of {project.name}:{endstr}" |
46 | | - last_date = '0000-01-01T00:00:00Z' |
| 48 | + log = f"Changelog of {project.name}" |
| 49 | + if target_branch: |
| 50 | + log += f" for branch '{target_branch}'" |
| 51 | + log += f" since {since}:{endstr}" |
| 52 | + last_date = since # Assuming 'since' is a date string 'YYYY-MM-DDTHH:MM:SSZ' |
| 53 | + # If 'since' can be a tag, you'd need to resolve its date |
47 | 54 | else: |
48 | | - last_release = project.releases.list(get_all=False)[0] |
49 | | - log_pending = f"Changelog since release {last_release.name} of {project.name}:{endstr}" |
50 | | - last_date = last_release.released_at |
| 55 | + releases = project.releases.list() |
| 56 | + if not releases: |
| 57 | + log = f"Changelog of {project.name}" |
| 58 | + if target_branch: |
| 59 | + log += f" for branch '{target_branch}'" |
| 60 | + log += f":{endstr}" |
| 61 | + last_date = '0000-01-01T00:00:00Z' |
| 62 | + else: |
| 63 | + last_release = releases[0] |
| 64 | + log = f"Changelog since release {last_release.name} of {project.name}" |
| 65 | + if target_branch: |
| 66 | + log += f" for branch '{target_branch}'" |
| 67 | + log += f":{endstr}" |
| 68 | + last_date = last_release.released_at |
51 | 69 |
|
52 | 70 | page = 1 |
53 | | - list_mrs = project.mergerequests.list(state='merged', |
54 | | - get_all=False, |
55 | | - order_by='updated_at', |
56 | | - updated_after=last_date, |
57 | | - target_branch=target_branch, |
58 | | - page=page) |
59 | | - if not list_mrs: |
60 | | - if not quiet: |
61 | | - log += log_pending |
62 | | - log += f"There is no merged merge request after {last_date}{endstr}" |
63 | | - return log |
64 | | - |
65 | | - log += log_pending |
| 71 | + list_mrs_params = { |
| 72 | + 'state': 'merged', |
| 73 | + 'order_by': 'updated_at', |
| 74 | + 'updated_after': last_date, |
| 75 | + # 'scope': 'all' # Ensure we get MRs the user has access to, default is 'created_by_me' or 'assigned_to_me' |
| 76 | + # The python-gitlab default for list() is all MRs the user can view. |
| 77 | + } |
| 78 | + if target_branch: |
| 79 | + list_mrs_params['target_branch'] = target_branch |
| 80 | + |
| 81 | + # Fetch the first page |
| 82 | + list_mrs = project.mergerequests.list(page=page, get_all=False, **list_mrs_params) |
| 83 | + |
| 84 | + found_mrs = False |
66 | 85 | while list_mrs: |
| 86 | + found_mrs = True |
67 | 87 | for mr in list_mrs: |
68 | 88 | line = f" * {mr.title} (@{mr.author['username']}){endstr}" |
69 | 89 | log += line |
70 | 90 |
|
71 | 91 | page += 1 |
72 | | - list_mrs = project.mergerequests.list(state='merged', |
73 | | - get_all=False, |
74 | | - order_by='updated_at', |
75 | | - updated_after=last_date, |
76 | | - target_branch=target_branch, |
77 | | - page=page |
78 | | - ) |
| 92 | + list_mrs = project.mergerequests.list(page=page, get_all=False, **list_mrs_params) |
| 93 | + |
| 94 | + if not found_mrs: |
| 95 | + no_mrs_message = "There is no new merged merge request" |
| 96 | + if target_branch: |
| 97 | + no_mrs_message += f" for branch '{target_branch}'" |
| 98 | + if since: |
| 99 | + no_mrs_message += f" since {since}." |
| 100 | + else: |
| 101 | + no_mrs_message += f" after {last_date}." |
| 102 | + log += no_mrs_message |
| 103 | + return log |
79 | 104 |
|
80 | 105 | return log |
81 | 106 |
|
82 | 107 |
|
83 | 108 | def main(): |
84 | 109 | import argparse |
85 | | - parser = argparse.ArgumentParser(os.path.basename(sys.argv[0]), |
86 | | - description="Generate release notes for a gitlab repository \ |
87 | | - based on merge requests titles since last release") |
| 110 | + parser = argparse.ArgumentParser("Generate release notes for a gitlab repository \ |
| 111 | + based on merge requests titles since last release") |
88 | 112 |
|
89 | 113 | # Required |
90 | 114 | parser.add_argument("project_id", type=int) |
91 | 115 | # Optional |
92 | | - parser.add_argument("--url", default="https://gitlab.com", required=False) |
93 | | - parser.add_argument("--private_token", type=str, required=False, default=None) |
94 | | - parser.add_argument('--version', action='version', version=__version__) |
95 | | - parser.add_argument('--html', action='store_true') |
96 | | - parser.add_argument('--since', type=datetime.date.fromisoformat, required=False, default=None) |
97 | | - parser.add_argument('--target_branch', type=str, required=False, default=None) |
98 | | - parser.add_argument('--quiet', action='store_true') |
| 116 | + parser.add_argument("--url", default="https://gitlab.com", required=False, help="GitLab base URL, e.g., https://gitlab.com") |
| 117 | + parser.add_argument("--private_token", type=str, required=False, default=None, help="GitLab private token") |
| 118 | + parser.add_argument('--version', action='version', version=__version__, help="Show the version and exit") |
| 119 | + parser.add_argument('--html', action='store_true', help="Generate HTML output") |
| 120 | + # Add CLI arguments for since and target_branch if you want to use them from CLI |
| 121 | + parser.add_argument("--since", type=str, required=False, default=None, |
| 122 | + help="Generate notes since this date (YYYY-MM-DDTHH:MM:SSZ) or tag.") |
| 123 | + parser.add_argument("--target_branch", type=str, required=False, default=None, |
| 124 | + help="Filter merge requests by target branch.") |
| 125 | + |
99 | 126 |
|
100 | 127 | args = parser.parse_args() |
101 | 128 |
|
102 | 129 | if args.html: |
103 | 130 | endstr = ' <br>' |
104 | 131 | else: |
105 | 132 | endstr = '\n' |
106 | | - notes = generate_release_notes(args.project_id, |
107 | | - url=args.url, |
108 | | - endstr=endstr, |
109 | | - since=args.since, |
110 | | - target_branch=args.target_branch, |
111 | | - quiet=args.quiet, |
112 | | - private_token=args.private_token, |
113 | | - ) |
114 | | - if notes: |
115 | | - print(notes) |
| 133 | + |
| 134 | + # Pass since and target_branch to generate_release_notes |
| 135 | + notes = generate_release_notes( |
| 136 | + args.project_id, |
| 137 | + url=args.url, |
| 138 | + endstr=endstr, |
| 139 | + private_token=args.private_token, |
| 140 | + since=args.since, |
| 141 | + target_branch=args.target_branch |
| 142 | + ) |
| 143 | + print(notes) |
| 144 | + |
116 | 145 |
|
117 | 146 | if __name__ == "__main__": |
118 | 147 | main() |
0 commit comments