pull-request-fixer 0.1.5__tar.gz → 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. pull_request_fixer-0.1.7/LICENSES/Apache-2.0.txt +201 -0
  2. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/PKG-INFO +2 -2
  3. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/pyproject.toml +9 -0
  4. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/_version.py +2 -2
  5. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/blocked_pr_scanner.py +2 -4
  6. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/cli.py +12 -11
  7. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/file_fixer.py +0 -2
  8. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/git_config.py +0 -1
  9. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/github_client.py +3 -2
  10. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/graphql_queries.py +2 -2
  11. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/models.py +1 -0
  12. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/pr_file_fixer.py +21 -14
  13. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/pr_scanner.py +2 -2
  14. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/progress_tracker.py +31 -21
  15. pull_request_fixer-0.1.5/LICENSE +0 -1
  16. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/.gitignore +0 -0
  17. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/.pre-commit-hooks.yaml +0 -0
  18. /pull_request_fixer-0.1.5/LICENSES/Apache-2.0.txt → /pull_request_fixer-0.1.7/LICENSE +0 -0
  19. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/README.md +0 -0
  20. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/scripts/README.md +0 -0
  21. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/__init__.py +0 -0
  22. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/exceptions.py +0 -0
  23. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/pr_fixer.py +0 -0
  24. {pull_request_fixer-0.1.5 → pull_request_fixer-0.1.7}/src/pull_request_fixer/py.typed +0 -0
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pull-request-fixer
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: Pull request formatter and fixer with GitHub integration
5
5
  Project-URL: Homepage, https://github.com/lfreleng-actions/pull-request-fixer
6
6
  Project-URL: Repository, https://github.com/lfreleng-actions/pull-request-fixer.git
@@ -233,6 +233,15 @@ markers = [
233
233
  ]
234
234
 
235
235
  [tool.coverage.run]
236
+ # Keep the transient coverage database out of the working tree.
237
+ # coverage.py resolves a relative data_file against the current
238
+ # directory, so the default '.coverage' lands wherever pytest runs and
239
+ # can be picked up by tests that inspect on-disk content. TEMP is
240
+ # set on Windows, where /tmp does not exist; elsewhere it is normally
241
+ # unset and the /tmp fallback applies. An explicit value also
242
+ # satisfies python-test-action's data_file configuration check.
243
+ # COVERAGE_FILE (env) still takes precedence when set.
244
+ data_file = "${TEMP-/tmp}/.coverage.pull-request-fixer"
236
245
  source = ["pull_request_fixer"]
237
246
  omit = [
238
247
  "*/tests/*",
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.1.5'
22
- __version_tuple__ = version_tuple = (0, 1, 5)
21
+ __version__ = version = '0.1.7'
22
+ __version_tuple__ = version_tuple = (0, 1, 7)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -90,7 +90,6 @@ class BlockedPRScanner:
90
90
  organization, include_drafts=include_drafts
91
91
  )
92
92
  finally:
93
- # Stop progress tracker before yielding results
94
93
  if self.progress_tracker:
95
94
  self.progress_tracker.stop()
96
95
 
@@ -166,9 +165,8 @@ class BlockedPRScanner:
166
165
  - pr_data: Raw PR data dict from GraphQL
167
166
  - is_blocked: Boolean indicating if PR is blocked
168
167
  """
169
- # For this use case, we need to fetch ALL PRs, not just blocked ones
170
- # This requires using the lower-level iteration methods
171
- # For now, we'll note this is a TODO and focus on the blocked-only case
168
+ # Fetching every PR (not just blocked ones) needs the lower-level
169
+ # iteration methods; only blocked-only scanning is supported today.
172
170
  raise NotImplementedError(
173
171
  "Scanning all PRs (not just blocked) is not yet implemented. "
174
172
  "Use scan_organization_for_blocked_prs() for blocked-only scanning."
@@ -3,6 +3,8 @@
3
3
 
4
4
  """Command-line interface for pull-request-fixer."""
5
5
 
6
+ # aislop-ignore-file complexity/file-too-large -- cohesive CLI entry point
7
+
6
8
  from __future__ import annotations
7
9
 
8
10
  import asyncio
@@ -101,7 +103,6 @@ def parse_target(target: str) -> tuple[str, str]:
101
103
  parsed = urlparse(parse_input)
102
104
  hostname = (parsed.hostname or "").lower()
103
105
  if hostname in ("github.com", "www.github.com"):
104
- # Extract org from the URL path: /ORG or /ORG/...
105
106
  org = parsed.path.lstrip("/").split("/")[0]
106
107
  if org:
107
108
  return ("org", org)
@@ -143,6 +144,7 @@ app = typer.Typer(
143
144
  )
144
145
 
145
146
 
147
+ # aislop-ignore-next-line complexity/function-too-long -- top-level CLI command
146
148
  def main(
147
149
  target: str = typer.Argument(
148
150
  None,
@@ -353,7 +355,6 @@ def main(
353
355
  else:
354
356
  git_config_mode = GitConfigMode.USER_INHERIT
355
357
 
356
- # Validate that at least one fix option is enabled
357
358
  if not fix_title and not fix_body and not fix_files:
358
359
  console.print(
359
360
  "[yellow]Warning:[/yellow] No fix options specified. "
@@ -372,7 +373,6 @@ def main(
372
373
  )
373
374
  raise typer.Exit(1)
374
375
 
375
- # Validate file fixing options
376
376
  if fix_files:
377
377
  if not file_pattern:
378
378
  console.print(
@@ -419,7 +419,6 @@ def main(
419
419
  )
420
420
 
421
421
  if target_type == "pr":
422
- # Process single PR
423
422
  asyncio.run(
424
423
  process_single_pr(
425
424
  pr_url=target_value,
@@ -555,7 +554,6 @@ async def process_single_pr(
555
554
 
556
555
  owner, repo_name, pr_number = pr_info
557
556
 
558
- # Fetch PR data using GraphQL to check blocked status
559
557
  query = """
560
558
  query($owner: String!, $repo: String!, $number: Int!) {
561
559
  repository(owner: $owner, name: $repo) {
@@ -600,7 +598,8 @@ async def process_single_pr(
600
598
  }
601
599
 
602
600
  response = await client.graphql(query, variables)
603
- pr_data = response.get("repository", {}).get("pullRequest")
601
+ repository = response.get("repository", {})
602
+ pr_data = repository.get("pullRequest")
604
603
 
605
604
  if not pr_data:
606
605
  console.print("[red]Error:[/red] Could not fetch PR data")
@@ -1058,6 +1057,7 @@ async def _collect_org_prs(
1058
1057
  return prs_to_process
1059
1058
 
1060
1059
 
1060
+ # aislop-ignore-next-line complexity/function-too-long -- organization scan flow
1061
1061
  async def scan_and_fix_organization(
1062
1062
  org: str,
1063
1063
  token: str,
@@ -1116,7 +1116,6 @@ async def scan_and_fix_organization(
1116
1116
  async with GitHubClient(token) as client: # type: ignore[attr-defined]
1117
1117
  await _validate_org_token(client, quiet=quiet)
1118
1118
 
1119
- # Create progress tracker for visual feedback
1120
1119
  progress_tracker = (
1121
1120
  None if quiet else ProgressTracker(org, show_pr_stats=True)
1122
1121
  )
@@ -1274,6 +1273,7 @@ async def scan_and_fix_organization(
1274
1273
  raise typer.Exit(1) from e
1275
1274
 
1276
1275
 
1276
+ # aislop-ignore-next-line complexity/function-too-long -- single-PR processing flow
1277
1277
  async def process_pr(
1278
1278
  client: GitHubClient,
1279
1279
  owner: str,
@@ -1526,10 +1526,8 @@ def parse_commit_message(message: str) -> tuple[str, str]:
1526
1526
  # Found a non-trailer, non-empty line, stop looking
1527
1527
  break
1528
1528
 
1529
- # Get body without trailers
1530
1529
  body_lines = body_lines[:trailer_start_idx]
1531
1530
 
1532
- # Remove trailing empty lines
1533
1531
  while body_lines and not body_lines[-1].strip():
1534
1532
  body_lines.pop()
1535
1533
 
@@ -1630,14 +1628,14 @@ async def rerun_failed_checks(
1630
1628
  pr_number: PR number
1631
1629
  """
1632
1630
  try:
1633
- # Get PR to find head SHA
1634
1631
  pr_endpoint = f"/repos/{owner}/{repo}/pulls/{pr_number}"
1635
1632
  pr_data_response = await client._request("GET", pr_endpoint)
1636
1633
 
1637
1634
  if not pr_data_response or not isinstance(pr_data_response, dict):
1638
1635
  return False
1639
1636
 
1640
- head_sha = pr_data_response.get("head", {}).get("sha")
1637
+ head = pr_data_response.get("head", {})
1638
+ head_sha = head.get("sha")
1641
1639
  if not head_sha:
1642
1640
  return False
1643
1641
 
@@ -1669,6 +1667,7 @@ async def rerun_failed_checks(
1669
1667
  f"/repos/{owner}/{repo}/check-runs/{run_id}/rerequest"
1670
1668
  )
1671
1669
  await client._request("POST", rerun_endpoint)
1670
+ # aislop-ignore-next-line ai-slop/python-broad-except -- best-effort check rerun
1672
1671
  except Exception:
1673
1672
  # Silently ignore errors - not all checks support re-run
1674
1673
  pass
@@ -1725,6 +1724,7 @@ async def create_pr_comment(
1725
1724
 
1726
1725
  await client._request("POST", endpoint, json=data)
1727
1726
 
1727
+ # aislop-ignore-next-line ai-slop/python-broad-except -- best-effort PR comment
1728
1728
  except Exception:
1729
1729
  # Silently ignore errors - commenting is best-effort
1730
1730
  pass
@@ -1834,6 +1834,7 @@ async def create_file_fix_comment(
1834
1834
 
1835
1835
  await client._request("POST", endpoint, json=data)
1836
1836
 
1837
+ # aislop-ignore-next-line ai-slop/python-broad-except -- best-effort PR comment
1837
1838
  except Exception:
1838
1839
  # Silently ignore errors - commenting is best-effort
1839
1840
  pass
@@ -48,7 +48,6 @@ class FileFixer:
48
48
  if not path.is_file():
49
49
  continue
50
50
 
51
- # Get relative path for matching
52
51
  try:
53
52
  rel_path = path.relative_to(root_dir)
54
53
  rel_path_str = str(rel_path)
@@ -174,7 +173,6 @@ class FileFixer:
174
173
  removed_count = 0
175
174
 
176
175
  for line in lines:
177
- # Check for context markers
178
176
  if context_start_regex and context_start_regex.search(line):
179
177
  in_context = True
180
178
  new_lines.append(line)
@@ -156,7 +156,6 @@ def configure_git_identity(
156
156
  _set_repo_git_config(repo_dir, "gpg.format", gpg_format)
157
157
  applied_config["gpg.format"] = gpg_format
158
158
 
159
- # Handle SSH-specific configuration
160
159
  if gpg_format == "ssh":
161
160
  # Copy SSH signing key
162
161
  ssh_key = _get_global_git_config("user.signingkey")
@@ -3,6 +3,8 @@
3
3
 
4
4
  """GitHub API client for repository and PR operations."""
5
5
 
6
+ # aislop-ignore-file complexity/file-too-large -- cohesive GitHub API client
7
+
6
8
  from __future__ import annotations
7
9
 
8
10
  import base64
@@ -501,7 +503,6 @@ class GitHubClient:
501
503
  ref_data = await self.get_reference(owner, repo, f"heads/{branch}")
502
504
  current_commit_sha = ref_data["object"]["sha"]
503
505
 
504
- # Get current commit to get tree SHA
505
506
  commit_data = await self._request(
506
507
  "GET",
507
508
  f"/repos/{owner}/{repo}/git/commits/{current_commit_sha}",
@@ -527,7 +528,6 @@ class GitHubClient:
527
528
  "sha": blob_sha,
528
529
  }
529
530
 
530
- # Create all blobs concurrently
531
531
  tree_items = await asyncio.gather(
532
532
  *[create_blob_for_file(file_info) for file_info in files]
533
533
  )
@@ -544,6 +544,7 @@ class GitHubClient:
544
544
 
545
545
  return commit_sha
546
546
 
547
+ # aislop-ignore-next-line complexity/too-many-params -- GitHub contents API shape
547
548
  async def update_file(
548
549
  self,
549
550
  owner: str,
@@ -3,8 +3,8 @@
3
3
 
4
4
  """GraphQL queries for efficient organization scanning."""
5
5
 
6
- # Lightweight query to list repositories without PR nodes for accurate counting
7
- # This is the SAME query used by dependamerge for efficient repo enumeration
6
+ # Mirrors dependamerge's repo enumeration query so organization counts stay
7
+ # consistent; excludes PR nodes to keep the response small.
8
8
  ORG_REPOS_ONLY = """
9
9
  query($org: String!, $reposCursor: String) {
10
10
  organization(login: $org) {
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
13
13
  from pathlib import Path
14
14
 
15
15
 
16
+ # aislop-ignore-next-line ruff/UP042 -- StrEnum requires Python 3.11+, project targets 3.10
16
17
  class OutputFormat(str, Enum):
17
18
  """Output format options."""
18
19
 
@@ -1,6 +1,8 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  # SPDX-FileCopyrightText: 2025 The Linux Foundation
3
3
 
4
+ # aislop-ignore-file complexity/file-too-large -- cohesive PR file fixer module
5
+
4
6
  """Fix files in pull requests by cloning, modifying, and pushing changes.
5
7
 
6
8
  This module provides functionality for fixing files in GitHub pull requests
@@ -55,7 +57,6 @@ class PRFileFixer:
55
57
  Returns:
56
58
  Sanitized message with tokens removed
57
59
  """
58
- # Remove common token patterns
59
60
  patterns = [
60
61
  r"ghp_[a-zA-Z0-9]{36}", # GitHub personal access tokens
61
62
  r"ghs_[a-zA-Z0-9]{36}", # GitHub server tokens
@@ -70,6 +71,7 @@ class PRFileFixer:
70
71
 
71
72
  return sanitized
72
73
 
74
+ # aislop-ignore-next-line complexity/function-too-long -- top-level fix flow
73
75
  async def fix_pr_by_url( # noqa: PLR0911
74
76
  self,
75
77
  pr_url: str,
@@ -166,16 +168,21 @@ class PRFileFixer:
166
168
  head_repo = head.get("repo", {})
167
169
  clone_url = head_repo.get("clone_url", "")
168
170
 
171
+ user = pr_data.get("user", {})
172
+ author = user.get("login", "")
173
+ base = pr_data.get("base", {})
174
+ base_ref = base.get("ref", "")
175
+
169
176
  pr_info = PRInfo(
170
177
  number=pr_number,
171
178
  title=pr_data.get("title", ""),
172
179
  repository=f"{owner}/{repo}",
173
180
  url=pr_url,
174
- author=pr_data.get("user", {}).get("login", ""),
181
+ author=author,
175
182
  is_draft=pr_data.get("draft", False),
176
183
  head_ref=head_ref,
177
184
  head_sha=head_sha,
178
- base_ref=pr_data.get("base", {}).get("ref", ""),
185
+ base_ref=base_ref,
179
186
  mergeable=pr_data.get("mergeable", "unknown"),
180
187
  merge_state_status=pr_data.get("mergeable_state", "unknown"),
181
188
  )
@@ -256,6 +263,7 @@ class PRFileFixer:
256
263
  error=str(e),
257
264
  )
258
265
 
266
+ # aislop-ignore-next-line complexity/function-too-long -- git-based fix flow
259
267
  async def _fix_pr_with_git( # noqa: PLR0911
260
268
  self,
261
269
  pr_info: PRInfo,
@@ -387,7 +395,6 @@ class PRFileFixer:
387
395
  total_changes += 1
388
396
  self.logger.debug(f"Modified {file_path.name}")
389
397
 
390
- # Handle no files modified or dry-run mode
391
398
  if not files_modified:
392
399
  message = "No files required changes"
393
400
  return GitHubFixResult(
@@ -610,10 +617,12 @@ class PRFileFixer:
610
617
  file_modifications=[],
611
618
  )
612
619
 
613
- files_modified, file_modifications, files_to_update = (
614
- await self._apply_fixes_to_files(
615
- matching_files, owner, repo, branch, spec
616
- )
620
+ (
621
+ files_modified,
622
+ file_modifications,
623
+ files_to_update,
624
+ ) = await self._apply_fixes_to_files(
625
+ matching_files, owner, repo, branch, spec
617
626
  )
618
627
 
619
628
  # Second pass: batch update all modified files in a single commit.
@@ -703,9 +712,9 @@ class PRFileFixer:
703
712
  ]
704
713
 
705
714
  # Clone the PR branch to enumerate all files in the repository.
706
- clone_url = (
707
- pr_data.get("head", {}).get("repo", {}).get("clone_url", "")
708
- )
715
+ head = pr_data.get("head", {})
716
+ head_repo = head.get("repo", {})
717
+ clone_url = head_repo.get("clone_url", "")
709
718
  if not clone_url:
710
719
  return None
711
720
 
@@ -794,9 +803,7 @@ class PRFileFixer:
794
803
  )
795
804
  )
796
805
  if not spec.dry_run:
797
- files_to_update.append(
798
- {"path": filename, "content": modified}
799
- )
806
+ files_to_update.append({"path": filename, "content": modified})
800
807
 
801
808
  return files_modified, file_modifications, files_to_update
802
809
 
@@ -1,6 +1,8 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  # SPDX-FileCopyrightText: 2025 The Linux Foundation
3
3
 
4
+ # aislop-ignore-file complexity/file-too-large -- cohesive PR scanner module
5
+
4
6
  """Scanner for identifying pull requests across GitHub organizations.
5
7
 
6
8
  This module provides functionality for scanning GitHub organizations to find
@@ -94,7 +96,6 @@ class PRScanner:
94
96
  total_repos = await self._count_org_repositories(organization)
95
97
  if self.progress_tracker and total_repos > 0:
96
98
  self.progress_tracker.update_total_repositories(total_repos)
97
- # Start the progress tracker now that we have the count
98
99
  self.progress_tracker.start()
99
100
 
100
101
  self.logger.debug(f"Found {total_repos} repositories in {organization}")
@@ -503,7 +504,6 @@ class PRScanner:
503
504
  # Check merge state status (lowercase from GraphQL)
504
505
  merge_state = (pr.get("mergeStateStatus") or "").lower()
505
506
 
506
- # Check for merge conflicts via merge_state as well
507
507
  if merge_state == "dirty" and "Merge conflicts" not in blocking_reasons:
508
508
  blocking_reasons.append("Merge conflicts")
509
509
 
@@ -1,43 +1,52 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  # SPDX-FileCopyrightText: 2025 The Linux Foundation
3
3
 
4
+ # aislop-ignore-file complexity/file-too-large -- cohesive progress tracker
5
+
4
6
  from contextlib import suppress
5
7
  from datetime import datetime, timedelta
6
- from typing import Any
8
+ from typing import TYPE_CHECKING, Any
7
9
 
8
10
  _rich_available = False
9
11
 
10
- try:
12
+ if TYPE_CHECKING:
11
13
  from rich.console import Console
12
14
  from rich.live import Live
13
15
  from rich.text import Text
16
+ else:
17
+ # At runtime, prefer the real Rich classes and fall back to lightweight
18
+ # stubs so the CLI keeps working when Rich is not installed.
19
+ try:
20
+ from rich.console import Console
21
+ from rich.live import Live
22
+ from rich.text import Text
14
23
 
15
- _rich_available = True
16
- except ImportError:
24
+ _rich_available = True
25
+ except ImportError:
17
26
 
18
- class Live: # type: ignore[no-redef]
19
- def __init__(self, *args: Any, **kwargs: Any) -> None:
20
- pass
27
+ class Live:
28
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
29
+ pass
21
30
 
22
- def start(self) -> None:
23
- pass
31
+ def start(self) -> None:
32
+ pass
24
33
 
25
- def stop(self) -> None:
26
- pass
34
+ def stop(self) -> None:
35
+ pass
27
36
 
28
- def update(self, *args: Any) -> None:
29
- pass
37
+ def update(self, *args: Any) -> None:
38
+ pass
30
39
 
31
- class Text: # type: ignore[no-redef]
32
- def __init__(self, *args: Any, **kwargs: Any) -> None:
33
- pass
40
+ class Text:
41
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
42
+ pass
34
43
 
35
- def append(self, *args: Any, **kwargs: Any) -> None:
36
- pass
44
+ def append(self, *args: Any, **kwargs: Any) -> None:
45
+ pass
37
46
 
38
- class Console: # type: ignore[no-redef]
39
- def __init__(self, *args: Any, **kwargs: Any) -> None:
40
- pass
47
+ class Console:
48
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
49
+ pass
41
50
 
42
51
 
43
52
  class ProgressTracker:
@@ -285,6 +294,7 @@ class ProgressTracker:
285
294
 
286
295
  # Only print if display has changed to avoid spam
287
296
  if current_display != self._last_display:
297
+ # aislop-ignore-next-line python-print-debug -- fallback console output
288
298
  print(f"\r{current_display}\n", end="", flush=True)
289
299
  self._last_display = current_display
290
300
 
@@ -1 +0,0 @@
1
- LICENSES/Apache-2.0.txt