github2gerrit 0.1.10__py3-none-any.whl → 0.1.11__py3-none-any.whl
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.
- github2gerrit/cli.py +793 -198
- github2gerrit/commit_normalization.py +44 -15
- github2gerrit/config.py +76 -30
- github2gerrit/core.py +1571 -267
- github2gerrit/duplicate_detection.py +222 -98
- github2gerrit/external_api.py +76 -25
- github2gerrit/gerrit_query.py +286 -0
- github2gerrit/gerrit_rest.py +53 -18
- github2gerrit/gerrit_urls.py +90 -33
- github2gerrit/github_api.py +19 -6
- github2gerrit/gitutils.py +43 -14
- github2gerrit/mapping_comment.py +345 -0
- github2gerrit/models.py +15 -1
- github2gerrit/orchestrator/__init__.py +25 -0
- github2gerrit/orchestrator/reconciliation.py +589 -0
- github2gerrit/pr_content_filter.py +65 -17
- github2gerrit/reconcile_matcher.py +595 -0
- github2gerrit/rich_display.py +502 -0
- github2gerrit/rich_logging.py +316 -0
- github2gerrit/similarity.py +65 -19
- github2gerrit/ssh_agent_setup.py +59 -22
- github2gerrit/ssh_common.py +30 -11
- github2gerrit/ssh_discovery.py +67 -20
- github2gerrit/trailers.py +340 -0
- github2gerrit/utils.py +6 -2
- {github2gerrit-0.1.10.dist-info → github2gerrit-0.1.11.dist-info}/METADATA +76 -24
- github2gerrit-0.1.11.dist-info/RECORD +31 -0
- {github2gerrit-0.1.10.dist-info → github2gerrit-0.1.11.dist-info}/WHEEL +1 -2
- github2gerrit-0.1.10.dist-info/RECORD +0 -24
- github2gerrit-0.1.10.dist-info/top_level.txt +0 -1
- {github2gerrit-0.1.10.dist-info → github2gerrit-0.1.11.dist-info}/entry_points.txt +0 -0
- {github2gerrit-0.1.10.dist-info → github2gerrit-0.1.11.dist-info}/licenses/LICENSE +0 -0
@@ -3,9 +3,10 @@
|
|
3
3
|
#
|
4
4
|
# PR Content Filtering Module
|
5
5
|
#
|
6
|
-
# This module provides an extensible, rule-based system for filtering and
|
7
|
-
# GitHub pull request content for Gerrit consumption. It supports
|
8
|
-
# tools (Dependabot, pre-commit.ci, etc.) with
|
6
|
+
# This module provides an extensible, rule-based system for filtering and
|
7
|
+
# cleaning GitHub pull request content for Gerrit consumption. It supports
|
8
|
+
# multiple automation tools (Dependabot, pre-commit.ci, etc.) with
|
9
|
+
# author-specific filtering rules.
|
9
10
|
#
|
10
11
|
# Key features:
|
11
12
|
# - Author-specific filtering rules
|
@@ -29,7 +30,8 @@ log = logging.getLogger("github2gerrit.pr_content_filter")
|
|
29
30
|
|
30
31
|
# Common patterns used across filters
|
31
32
|
_HTML_DETAILS_PATTERN = re.compile(
|
32
|
-
r"<details[^>]*>\s*<summary[^>]*>(.*?)</summary>\s*(.*?)</details>",
|
33
|
+
r"<details[^>]*>\s*<summary[^>]*>(.*?)</summary>\s*(.*?)</details>",
|
34
|
+
re.IGNORECASE | re.DOTALL,
|
33
35
|
)
|
34
36
|
_MARKDOWN_LINK_PATTERN = re.compile(r"\[([^\]]+)\]\([^)]+\)")
|
35
37
|
_HTML_TAG_PATTERN = re.compile(r"<[^>]+>")
|
@@ -50,8 +52,12 @@ class FilterConfig:
|
|
50
52
|
author_rules: dict[str, str] = field(default_factory=dict)
|
51
53
|
|
52
54
|
# Rule-specific configurations
|
53
|
-
dependabot_config: DependabotConfig = field(
|
54
|
-
|
55
|
+
dependabot_config: DependabotConfig = field(
|
56
|
+
default_factory=lambda: DependabotConfig()
|
57
|
+
)
|
58
|
+
precommit_config: PrecommitConfig = field(
|
59
|
+
default_factory=lambda: PrecommitConfig()
|
60
|
+
)
|
55
61
|
|
56
62
|
|
57
63
|
@dataclass
|
@@ -149,12 +155,18 @@ class DependabotRule(FilterRule):
|
|
149
155
|
|
150
156
|
def _remove_compatibility_images(self, content: str) -> str:
|
151
157
|
"""Remove Dependabot compatibility score images."""
|
152
|
-
pattern = re.compile(
|
158
|
+
pattern = re.compile(
|
159
|
+
r"!\[.*?\]\(https://camo\.githubusercontent\.com/[^)]*\)",
|
160
|
+
re.IGNORECASE | re.DOTALL,
|
161
|
+
)
|
153
162
|
return pattern.sub("", content)
|
154
163
|
|
155
164
|
def _truncate_at_dependabot_commands(self, content: str) -> str:
|
156
165
|
"""Truncate at Dependabot command instructions."""
|
157
|
-
pattern = re.compile(
|
166
|
+
pattern = re.compile(
|
167
|
+
r"Dependabot will resolve any conflicts",
|
168
|
+
re.IGNORECASE | re.MULTILINE,
|
169
|
+
)
|
158
170
|
match = pattern.search(content)
|
159
171
|
if match:
|
160
172
|
return content[: match.start()].rstrip()
|
@@ -210,7 +222,10 @@ class PRContentFilter:
|
|
210
222
|
# Check author-specific rules first
|
211
223
|
if author in self.config.author_rules:
|
212
224
|
rule_name = self.config.author_rules[author]
|
213
|
-
return any(
|
225
|
+
return any(
|
226
|
+
rule.__class__.__name__.lower().startswith(rule_name.lower())
|
227
|
+
for rule in self.rules
|
228
|
+
)
|
214
229
|
|
215
230
|
# Check if any rule matches
|
216
231
|
return any(rule.matches(title, body, author) for rule in self.rules)
|
@@ -232,7 +247,9 @@ class PRContentFilter:
|
|
232
247
|
config_key = rule.get_config_key()
|
233
248
|
rule_config = getattr(self.config, config_key, None)
|
234
249
|
if rule_config:
|
235
|
-
filtered_body = rule.apply(
|
250
|
+
filtered_body = rule.apply(
|
251
|
+
title, filtered_body, rule_config
|
252
|
+
)
|
236
253
|
|
237
254
|
# Apply global post-processing
|
238
255
|
filtered_body = self._post_process(title, filtered_body)
|
@@ -263,6 +280,9 @@ class PRContentFilter:
|
|
263
280
|
# Clean up whitespace
|
264
281
|
processed = self._clean_whitespace(processed)
|
265
282
|
|
283
|
+
# Remove trailing ellipses
|
284
|
+
processed = self._remove_trailing_ellipses(processed)
|
285
|
+
|
266
286
|
return processed
|
267
287
|
|
268
288
|
def _remove_emoji_codes(self, content: str) -> str:
|
@@ -283,14 +303,18 @@ class PRContentFilter:
|
|
283
303
|
|
284
304
|
# Fix lines that started with emoji codes and now have leading space
|
285
305
|
if cleaned_line.startswith(" ") and not line.startswith(" "):
|
286
|
-
# This line originally started with an emoji, remove the
|
306
|
+
# This line originally started with an emoji, remove the
|
307
|
+
# leading space
|
287
308
|
cleaned_line = cleaned_line.lstrip()
|
288
309
|
|
289
310
|
# Fix markdown headers that lost their emoji but kept leading space
|
290
|
-
# e.g., "### :sparkles: New features" -> "### New features"
|
311
|
+
# e.g., "### :sparkles: New features" -> "### New features"
|
312
|
+
# not "### New features"
|
291
313
|
if cleaned_line.startswith("### "):
|
292
314
|
# Ensure exactly one space after ###
|
293
|
-
header_text = cleaned_line[
|
315
|
+
header_text = cleaned_line[
|
316
|
+
4:
|
317
|
+
].lstrip() # Remove ### and any spaces
|
294
318
|
cleaned_line = f"### {header_text}" if header_text else "###"
|
295
319
|
elif cleaned_line.startswith("## "):
|
296
320
|
# Same for ## headers
|
@@ -309,7 +333,8 @@ class PRContentFilter:
|
|
309
333
|
for i, line in enumerate(cleaned_lines):
|
310
334
|
final_lines.append(line)
|
311
335
|
|
312
|
-
# If this line looks like a heading (not starting with #) and should
|
336
|
+
# If this line looks like a heading (not starting with #) and should
|
337
|
+
# have
|
313
338
|
# a blank line after it, add one
|
314
339
|
if (
|
315
340
|
i < len(cleaned_lines) - 1
|
@@ -380,7 +405,8 @@ class PRContentFilter:
|
|
380
405
|
)
|
381
406
|
|
382
407
|
if is_duplicate:
|
383
|
-
# Remove the duplicate line and any immediately following empty
|
408
|
+
# Remove the duplicate line and any immediately following empty
|
409
|
+
# lines
|
384
410
|
remaining_lines = lines[first_content_line_idx + 1 :]
|
385
411
|
while remaining_lines and not remaining_lines[0].strip():
|
386
412
|
remaining_lines = remaining_lines[1:]
|
@@ -407,6 +433,24 @@ class PRContentFilter:
|
|
407
433
|
|
408
434
|
return cleaned
|
409
435
|
|
436
|
+
def _remove_trailing_ellipses(self, content: str) -> str:
|
437
|
+
"""Remove trailing ellipses that are often left by truncated content."""
|
438
|
+
lines = content.splitlines()
|
439
|
+
cleaned_lines = []
|
440
|
+
|
441
|
+
for line in lines:
|
442
|
+
# Remove lines that are just "..." or whitespace + "..."
|
443
|
+
stripped = line.strip()
|
444
|
+
if stripped == "..." or stripped == "…":
|
445
|
+
continue
|
446
|
+
|
447
|
+
# Remove trailing ellipses from lines
|
448
|
+
cleaned_line = re.sub(r"\s*\.{3,}\s*$", "", line)
|
449
|
+
cleaned_line = re.sub(r"\s*…\s*$", "", cleaned_line)
|
450
|
+
cleaned_lines.append(cleaned_line)
|
451
|
+
|
452
|
+
return "\n".join(cleaned_lines)
|
453
|
+
|
410
454
|
def add_rule(self, rule: FilterRule) -> None:
|
411
455
|
"""Add a custom filtering rule."""
|
412
456
|
self.rules.append(rule)
|
@@ -434,7 +478,9 @@ def create_default_filter() -> PRContentFilter:
|
|
434
478
|
return PRContentFilter(config)
|
435
479
|
|
436
480
|
|
437
|
-
def filter_pr_body(
|
481
|
+
def filter_pr_body(
|
482
|
+
title: str, body: str | None, author: str | None = None
|
483
|
+
) -> str:
|
438
484
|
"""
|
439
485
|
Main entry point for PR body filtering with default configuration.
|
440
486
|
|
@@ -454,7 +500,9 @@ def filter_pr_body(title: str, body: str | None, author: str | None = None) -> s
|
|
454
500
|
|
455
501
|
|
456
502
|
# Legacy compatibility functions
|
457
|
-
def should_filter_pr_body(
|
503
|
+
def should_filter_pr_body(
|
504
|
+
title: str, body: str | None, author: str | None = None
|
505
|
+
) -> bool:
|
458
506
|
"""Legacy function for checking if filtering should be applied."""
|
459
507
|
if not body:
|
460
508
|
return False
|