github2gerrit 0.1.9__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.
@@ -173,7 +173,10 @@ class CommitNormalizer:
173
173
 
174
174
  def _is_conventional_commit(self, title: str) -> bool:
175
175
  """Check if title is already in conventional commit format."""
176
- pattern = r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+?\))?\s*!?\s*:\s*.+"
176
+ pattern = (
177
+ r"^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)"
178
+ r"(\(.+?\))?\s*!?\s*:\s*.+"
179
+ )
177
180
  return bool(re.match(pattern, title, re.IGNORECASE))
178
181
 
179
182
  def _is_automation_pr(self, title: str, author: str) -> bool:
@@ -187,7 +190,9 @@ class CommitNormalizer:
187
190
  "greenkeeper[bot]",
188
191
  ]
189
192
 
190
- if any(author.lower().startswith(bot.lower()) for bot in automation_authors):
193
+ if any(
194
+ author.lower().startswith(bot.lower()) for bot in automation_authors
195
+ ):
191
196
  return True
192
197
 
193
198
  # Check for automation patterns in title
@@ -199,11 +204,17 @@ class CommitNormalizer:
199
204
  r"pre-commit.*autoupdate",
200
205
  ]
201
206
 
202
- return any(re.search(pattern, title, re.IGNORECASE) for pattern in automation_patterns)
207
+ return any(
208
+ re.search(pattern, title, re.IGNORECASE)
209
+ for pattern in automation_patterns
210
+ )
203
211
 
204
212
  def _detect_preferences(self) -> None:
205
213
  """Detect repository preferences for conventional commits."""
206
- log.debug("Detecting conventional commit preferences for workspace: %s", self.workspace)
214
+ log.debug(
215
+ "Detecting conventional commit preferences for workspace: %s",
216
+ self.workspace,
217
+ )
207
218
 
208
219
  # Check .pre-commit-config.yaml
209
220
  self._check_precommit_config()
@@ -262,14 +273,16 @@ class CommitNormalizer:
262
273
 
263
274
  for title_pattern in titles:
264
275
  # Extract conventional commit type from pattern
265
- if title_pattern.startswith("/") and title_pattern.endswith("/i"):
276
+ if title_pattern.startswith(
277
+ "/"
278
+ ) and title_pattern.endswith("/i"):
266
279
  pattern = title_pattern[1:-2] # Remove /pattern/i
267
280
  if ":" in pattern:
268
281
  commit_type = pattern.split(":")[0]
269
282
  if commit_type in CONVENTIONAL_COMMIT_TYPES:
270
- self.preferences.preferred_types[commit_type] = self._get_capitalization(
283
+ self.preferences.preferred_types[
271
284
  commit_type
272
- )
285
+ ] = self._get_capitalization(commit_type)
273
286
 
274
287
  break # Use first found config
275
288
 
@@ -309,11 +322,15 @@ class CommitNormalizer:
309
322
  match = re.match(r"^([a-zA-Z]+)", message)
310
323
  if match:
311
324
  commit_type = match.group(1).lower()
312
- type_counts[commit_type] = type_counts.get(commit_type, 0) + 1
325
+ type_counts[commit_type] = (
326
+ type_counts.get(commit_type, 0) + 1
327
+ )
313
328
 
314
329
  # Track capitalization
315
330
  if commit_type not in capitalization_examples:
316
- capitalization_examples[commit_type] = match.group(1)
331
+ capitalization_examples[commit_type] = match.group(
332
+ 1
333
+ )
317
334
 
318
335
  # Update preferences based on analysis
319
336
  if type_counts:
@@ -330,7 +347,9 @@ class CommitNormalizer:
330
347
  # Update preferred types
331
348
  for commit_type in type_counts:
332
349
  if commit_type in CONVENTIONAL_COMMIT_TYPES:
333
- self.preferences.preferred_types[commit_type] = self._apply_capitalization(commit_type)
350
+ self.preferences.preferred_types[commit_type] = (
351
+ self._apply_capitalization(commit_type)
352
+ )
334
353
 
335
354
  except Exception as e:
336
355
  log.debug("Failed to analyze git history: %s", e)
@@ -356,7 +375,9 @@ class CommitNormalizer:
356
375
  title_lower = title.lower()
357
376
 
358
377
  # Check for dependabot patterns first
359
- if "dependabot" in author.lower() or any(re.search(pattern, title_lower) for pattern in DEPENDABOT_PATTERNS):
378
+ if "dependabot" in author.lower() or any(
379
+ re.search(pattern, title_lower) for pattern in DEPENDABOT_PATTERNS
380
+ ):
360
381
  return self.preferences.dependency_type
361
382
 
362
383
  # Check for pre-commit.ci patterns
@@ -402,7 +423,8 @@ class CommitNormalizer:
402
423
  for prefix in prefixes_to_remove:
403
424
  title = re.sub(prefix, "", title, flags=re.IGNORECASE).strip()
404
425
 
405
- # Ensure first letter is lowercase (will be adjusted by capitalization later)
426
+ # Ensure first letter is lowercase (will be adjusted by capitalization
427
+ # later)
406
428
  if title and title[0].isupper():
407
429
  title = title[0].lower() + title[1:]
408
430
 
@@ -415,7 +437,10 @@ class CommitNormalizer:
415
437
 
416
438
  # Add scope if preferred for dependency updates
417
439
  scope = ""
418
- if commit_type == self.preferences.dependency_type and self.preferences.use_scope:
440
+ if (
441
+ commit_type == self.preferences.dependency_type
442
+ and self.preferences.use_scope
443
+ ):
419
444
  scope = f"({self.preferences.dependency_scope})"
420
445
 
421
446
  return f"{formatted_type}{scope}: {title}"
@@ -439,7 +464,9 @@ class CommitNormalizer:
439
464
  return "lower"
440
465
 
441
466
 
442
- def normalize_commit_title(title: str, author: str, workspace: Path | None = None) -> str:
467
+ def normalize_commit_title(
468
+ title: str, author: str, workspace: Path | None = None
469
+ ) -> str:
443
470
  """
444
471
  Normalize a commit title to conventional commit format.
445
472
 
@@ -455,7 +482,9 @@ def normalize_commit_title(title: str, author: str, workspace: Path | None = Non
455
482
  return normalizer.normalize_commit_title(title, author)
456
483
 
457
484
 
458
- def should_normalize_commit(title: str, author: str, workspace: Path | None = None) -> bool:
485
+ def should_normalize_commit(
486
+ title: str, author: str, workspace: Path | None = None
487
+ ) -> bool:
459
488
  """
460
489
  Check if a commit title should be normalized.
461
490
 
github2gerrit/config.py CHANGED
@@ -77,6 +77,7 @@ KNOWN_KEYS: set[str] = {
77
77
  "ALLOW_GHE_URLS",
78
78
  "DRY_RUN",
79
79
  "ALLOW_DUPLICATES",
80
+ "DUPLICATE_TYPES",
80
81
  "ISSUE_ID",
81
82
  "G2G_VERBOSE",
82
83
  "G2G_SKIP_GERRIT_COMMENTS",
@@ -91,6 +92,13 @@ KNOWN_KEYS: set[str] = {
91
92
  # Gerrit REST auth
92
93
  "GERRIT_HTTP_USER",
93
94
  "GERRIT_HTTP_PASSWORD",
95
+ # Reconciliation configuration
96
+ "REUSE_STRATEGY",
97
+ "SIMILARITY_SUBJECT",
98
+ "SIMILARITY_FILES",
99
+ "ALLOW_ORPHAN_CHANGES",
100
+ "PERSIST_SINGLE_MAPPING_COMMENT",
101
+ "LOG_RECONCILE_JSON",
94
102
  }
95
103
 
96
104
  _ENV_REF = re.compile(r"\$\{ENV:([A-Za-z_][A-Za-z0-9_]*)\}")
@@ -130,19 +138,29 @@ def _coerce_value(raw: str) -> str:
130
138
  # Normalize escaped newline sequences into real newlines so that values
131
139
  # like SSH keys or known_hosts entries can be specified inline using
132
140
  # '\n' or '\r\n' in configuration files.
133
- normalized_newlines = unquoted.replace("\\r\\n", "\n").replace("\\n", "\n").replace("\r\n", "\n")
141
+ normalized_newlines = (
142
+ unquoted.replace("\\r\\n", "\n")
143
+ .replace("\\n", "\n")
144
+ .replace("\r\n", "\n")
145
+ )
134
146
 
135
147
  # Additional sanitization for SSH private keys
136
- if ("-----BEGIN" in normalized_newlines and "PRIVATE KEY-----" in normalized_newlines) or (
137
- "ssh-" in normalized_newlines.lower() and "key" in normalized_newlines.lower()
148
+ if (
149
+ "-----BEGIN" in normalized_newlines
150
+ and "PRIVATE KEY-----" in normalized_newlines
151
+ ) or (
152
+ "ssh-" in normalized_newlines.lower()
153
+ and "key" in normalized_newlines.lower()
138
154
  ):
139
- # Clean up SSH key formatting: remove extra whitespace, normalize line endings
155
+ # Clean up SSH key formatting: remove extra whitespace, normalize
156
+ # line endings
140
157
  lines = normalized_newlines.split("\n")
141
158
  sanitized_lines = []
142
159
  for line in lines:
143
160
  cleaned = line.strip()
144
161
  if cleaned:
145
- # Remove any stray quotes that might have been embedded in the key content
162
+ # Remove any stray quotes that might have been embedded in the
163
+ # key content
146
164
  cleaned = cleaned.replace('"', "").replace("'", "")
147
165
  sanitized_lines.append(cleaned)
148
166
  normalized_newlines = "\n".join(sanitized_lines)
@@ -208,26 +226,35 @@ def _load_ini(path: Path) -> configparser.RawConfigParser:
208
226
  else:
209
227
  # No closing quote found; fall through
210
228
  # and keep original line
211
- log.debug("Multi-line quote not properly closed for line: %s", line[:50])
229
+ log.debug(
230
+ "Multi-line quote not properly closed for line: %s",
231
+ line[:50],
232
+ )
212
233
  out_lines.append(line)
213
234
  continue
214
235
 
215
- # Handle SSH private keys and other values that start with a quote
236
+ # Handle SSH private keys and other values that start with a
237
+ # quote
216
238
  # but contain embedded content that might confuse configparser
217
239
  elif rhs.startswith('"') and not rhs.endswith('"'):
218
- # This looks like a multi-line value that starts on the same line
219
- # Collect all content until we find a line ending with a quote
240
+ # This looks like a multi-line value that starts on the
241
+ # same line
242
+ # Collect all content until we find a line ending with a
243
+ # quote
220
244
  content_lines = [rhs[1:]] # Remove opening quote
221
245
  i += 1
222
246
 
223
247
  while i < len(lines):
224
248
  current_line = lines[i]
225
- if current_line.strip().endswith('"') and not current_line.strip().endswith('\\"'):
249
+ if current_line.strip().endswith(
250
+ '"'
251
+ ) and not current_line.strip().endswith('\\"'):
226
252
  # Found closing quote - remove it and add final line
227
253
  final_content = current_line.rstrip()
228
254
  if final_content.endswith('"'):
229
255
  final_content = final_content[:-1]
230
- if final_content: # Only add if there's content after removing quote
256
+ # Only add if there's content after removing quote
257
+ if final_content:
231
258
  content_lines.append(final_content)
232
259
  break
233
260
  else:
@@ -237,19 +264,27 @@ def _load_ini(path: Path) -> configparser.RawConfigParser:
237
264
  # Join all content and sanitize for SSH keys
238
265
  full_content = "\\n".join(content_lines)
239
266
 
240
- # Special handling for SSH private keys - remove extra whitespace and line breaks
267
+ # Special handling for SSH private keys - remove extra
268
+ # whitespace and line breaks
241
269
  key_name = left.split("=")[0].strip().upper()
242
270
  if "SSH" in key_name and "KEY" in key_name:
243
- # For SSH keys, clean up base64 content by removing whitespace within lines
271
+ # For SSH keys, clean up base64 content by removing
272
+ # whitespace within lines
244
273
  sanitized_lines = []
245
274
  for content_line in content_lines:
246
275
  cleaned = content_line.strip()
247
- # Preserve SSH key headers/footers but clean base64 content
276
+ # Preserve SSH key headers/footers but clean base64
277
+ # content
248
278
  if cleaned.startswith("-----") or not cleaned:
249
279
  sanitized_lines.append(cleaned)
250
280
  else:
251
- # Remove any embedded quotes and whitespace from base64 content
252
- cleaned = cleaned.replace('"', "").replace("'", "").strip()
281
+ # Remove any embedded quotes and whitespace from
282
+ # base64 content
283
+ cleaned = (
284
+ cleaned.replace('"', "")
285
+ .replace("'", "")
286
+ .strip()
287
+ )
253
288
  if cleaned:
254
289
  sanitized_lines.append(cleaned)
255
290
  full_content = "\\n".join(sanitized_lines)
@@ -390,7 +425,10 @@ def filter_known(
390
425
 
391
426
  def _is_github_actions_context() -> bool:
392
427
  """Detect if running in GitHub Actions environment."""
393
- return os.getenv("GITHUB_ACTIONS") == "true" or os.getenv("GITHUB_EVENT_NAME", "").strip() != ""
428
+ return (
429
+ os.getenv("GITHUB_ACTIONS") == "true"
430
+ or os.getenv("GITHUB_EVENT_NAME", "").strip() != ""
431
+ )
394
432
 
395
433
 
396
434
  def _is_local_cli_context() -> bool:
@@ -416,7 +454,9 @@ def derive_gerrit_parameters(organization: str | None) -> dict[str, str]:
416
454
  org = organization.strip().lower()
417
455
  return {
418
456
  "GERRIT_SSH_USER_G2G": f"{org}.gh2gerrit",
419
- "GERRIT_SSH_USER_G2G_EMAIL": (f"releng+{org}-gh2gerrit@linuxfoundation.org"),
457
+ "GERRIT_SSH_USER_G2G_EMAIL": (
458
+ f"releng+{org}-gh2gerrit@linuxfoundation.org"
459
+ ),
420
460
  "GERRIT_SERVER": f"gerrit.{org}.org",
421
461
  }
422
462
 
@@ -435,10 +475,10 @@ def apply_parameter_derivation(
435
475
  - gerrit_ssh_user_g2g_email: releng+[org]-gh2gerrit@linuxfoundation.org
436
476
  - gerrit_server: gerrit.[org].org
437
477
 
438
- Derivation behavior depends on execution context:
439
- - GitHub Actions: Automatic derivation when organization is available
440
- - Local CLI: Requires G2G_ENABLE_DERIVATION=true for automatic
441
- derivation
478
+ Derivation behavior:
479
+ - Default: Automatic derivation enabled (G2G_ENABLE_DERIVATION=true by
480
+ default)
481
+ - Can be disabled by setting G2G_ENABLE_DERIVATION=false
442
482
 
443
483
  Args:
444
484
  cfg: Configuration dictionary to augment
@@ -453,7 +493,9 @@ def apply_parameter_derivation(
453
493
 
454
494
  # Check execution context to determine derivation strategy
455
495
  is_github_actions = _is_github_actions_context()
456
- enable_derivation = is_github_actions or os.getenv("G2G_ENABLE_DERIVATION", "").strip().lower() in (
496
+ enable_derivation = os.getenv(
497
+ "G2G_ENABLE_DERIVATION", "true"
498
+ ).strip().lower() in (
457
499
  "1",
458
500
  "true",
459
501
  "yes",
@@ -462,8 +504,8 @@ def apply_parameter_derivation(
462
504
 
463
505
  if not enable_derivation:
464
506
  log.debug(
465
- "Parameter derivation disabled for local CLI usage. "
466
- "Set G2G_ENABLE_DERIVATION=true to enable automatic derivation."
507
+ "Parameter derivation disabled. Set G2G_ENABLE_DERIVATION=true to "
508
+ "enable automatic derivation."
467
509
  )
468
510
  return cfg
469
511
 
@@ -485,7 +527,7 @@ def apply_parameter_derivation(
485
527
  newly_derived[key] = value
486
528
 
487
529
  if newly_derived:
488
- log.info(
530
+ log.debug(
489
531
  "Derived parameters applied for organization '%s' (%s): %s",
490
532
  organization,
491
533
  "GitHub Actions" if is_github_actions else "Local CLI",
@@ -494,7 +536,9 @@ def apply_parameter_derivation(
494
536
  # Save newly derived parameters to configuration file for future use
495
537
  # Default to true for local CLI, false for GitHub Actions
496
538
  default_auto_save = "false" if _is_github_actions_context() else "true"
497
- auto_save_enabled = os.getenv("G2G_AUTO_SAVE_CONFIG", default_auto_save).strip().lower() in (
539
+ auto_save_enabled = os.getenv(
540
+ "G2G_AUTO_SAVE_CONFIG", default_auto_save
541
+ ).strip().lower() in (
498
542
  "1",
499
543
  "true",
500
544
  "yes",
@@ -504,7 +548,7 @@ def apply_parameter_derivation(
504
548
  # Save to config in local CLI mode to create persistent configuration
505
549
  try:
506
550
  save_derived_parameters_to_config(organization, newly_derived)
507
- log.info(
551
+ log.debug(
508
552
  "Automatically saved derived parameters to configuration "
509
553
  "file for organization '%s'. "
510
554
  "This creates a persistent configuration that you can "
@@ -541,7 +585,9 @@ def save_derived_parameters_to_config(
541
585
  return
542
586
 
543
587
  if config_path is None:
544
- config_path = os.getenv("G2G_CONFIG_PATH", "").strip() or DEFAULT_CONFIG_PATH
588
+ config_path = (
589
+ os.getenv("G2G_CONFIG_PATH", "").strip() or DEFAULT_CONFIG_PATH
590
+ )
545
591
 
546
592
  config_file = Path(config_path).expanduser()
547
593
 
@@ -549,7 +595,8 @@ def save_derived_parameters_to_config(
549
595
  # Only update when a configuration file already exists
550
596
  if not config_file.exists():
551
597
  log.debug(
552
- "Configuration file does not exist; skipping auto-save of derived parameters: %s",
598
+ "Configuration file does not exist; skipping auto-save of "
599
+ "derived parameters: %s",
553
600
  config_file,
554
601
  )
555
602
  return