github2gerrit 0.1.10__py3-none-any.whl → 0.1.12__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
@@ -92,6 +92,13 @@ KNOWN_KEYS: set[str] = {
92
92
  # Gerrit REST auth
93
93
  "GERRIT_HTTP_USER",
94
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",
95
102
  }
96
103
 
97
104
  _ENV_REF = re.compile(r"\$\{ENV:([A-Za-z_][A-Za-z0-9_]*)\}")
@@ -131,19 +138,29 @@ def _coerce_value(raw: str) -> str:
131
138
  # Normalize escaped newline sequences into real newlines so that values
132
139
  # like SSH keys or known_hosts entries can be specified inline using
133
140
  # '\n' or '\r\n' in configuration files.
134
- 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
+ )
135
146
 
136
147
  # Additional sanitization for SSH private keys
137
- if ("-----BEGIN" in normalized_newlines and "PRIVATE KEY-----" in normalized_newlines) or (
138
- "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()
139
154
  ):
140
- # Clean up SSH key formatting: remove extra whitespace, normalize line endings
155
+ # Clean up SSH key formatting: remove extra whitespace, normalize
156
+ # line endings
141
157
  lines = normalized_newlines.split("\n")
142
158
  sanitized_lines = []
143
159
  for line in lines:
144
160
  cleaned = line.strip()
145
161
  if cleaned:
146
- # 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
147
164
  cleaned = cleaned.replace('"', "").replace("'", "")
148
165
  sanitized_lines.append(cleaned)
149
166
  normalized_newlines = "\n".join(sanitized_lines)
@@ -209,26 +226,35 @@ def _load_ini(path: Path) -> configparser.RawConfigParser:
209
226
  else:
210
227
  # No closing quote found; fall through
211
228
  # and keep original line
212
- 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
+ )
213
233
  out_lines.append(line)
214
234
  continue
215
235
 
216
- # 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
217
238
  # but contain embedded content that might confuse configparser
218
239
  elif rhs.startswith('"') and not rhs.endswith('"'):
219
- # This looks like a multi-line value that starts on the same line
220
- # 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
221
244
  content_lines = [rhs[1:]] # Remove opening quote
222
245
  i += 1
223
246
 
224
247
  while i < len(lines):
225
248
  current_line = lines[i]
226
- 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('\\"'):
227
252
  # Found closing quote - remove it and add final line
228
253
  final_content = current_line.rstrip()
229
254
  if final_content.endswith('"'):
230
255
  final_content = final_content[:-1]
231
- 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:
232
258
  content_lines.append(final_content)
233
259
  break
234
260
  else:
@@ -238,19 +264,27 @@ def _load_ini(path: Path) -> configparser.RawConfigParser:
238
264
  # Join all content and sanitize for SSH keys
239
265
  full_content = "\\n".join(content_lines)
240
266
 
241
- # 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
242
269
  key_name = left.split("=")[0].strip().upper()
243
270
  if "SSH" in key_name and "KEY" in key_name:
244
- # 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
245
273
  sanitized_lines = []
246
274
  for content_line in content_lines:
247
275
  cleaned = content_line.strip()
248
- # Preserve SSH key headers/footers but clean base64 content
276
+ # Preserve SSH key headers/footers but clean base64
277
+ # content
249
278
  if cleaned.startswith("-----") or not cleaned:
250
279
  sanitized_lines.append(cleaned)
251
280
  else:
252
- # Remove any embedded quotes and whitespace from base64 content
253
- 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
+ )
254
288
  if cleaned:
255
289
  sanitized_lines.append(cleaned)
256
290
  full_content = "\\n".join(sanitized_lines)
@@ -391,7 +425,10 @@ def filter_known(
391
425
 
392
426
  def _is_github_actions_context() -> bool:
393
427
  """Detect if running in GitHub Actions environment."""
394
- 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
+ )
395
432
 
396
433
 
397
434
  def _is_local_cli_context() -> bool:
@@ -417,7 +454,9 @@ def derive_gerrit_parameters(organization: str | None) -> dict[str, str]:
417
454
  org = organization.strip().lower()
418
455
  return {
419
456
  "GERRIT_SSH_USER_G2G": f"{org}.gh2gerrit",
420
- "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
+ ),
421
460
  "GERRIT_SERVER": f"gerrit.{org}.org",
422
461
  }
423
462
 
@@ -436,10 +475,10 @@ def apply_parameter_derivation(
436
475
  - gerrit_ssh_user_g2g_email: releng+[org]-gh2gerrit@linuxfoundation.org
437
476
  - gerrit_server: gerrit.[org].org
438
477
 
439
- Derivation behavior depends on execution context:
440
- - GitHub Actions: Automatic derivation when organization is available
441
- - Local CLI: Requires G2G_ENABLE_DERIVATION=true for automatic
442
- 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
443
482
 
444
483
  Args:
445
484
  cfg: Configuration dictionary to augment
@@ -454,7 +493,9 @@ def apply_parameter_derivation(
454
493
 
455
494
  # Check execution context to determine derivation strategy
456
495
  is_github_actions = _is_github_actions_context()
457
- 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 (
458
499
  "1",
459
500
  "true",
460
501
  "yes",
@@ -463,8 +504,8 @@ def apply_parameter_derivation(
463
504
 
464
505
  if not enable_derivation:
465
506
  log.debug(
466
- "Parameter derivation disabled for local CLI usage. "
467
- "Set G2G_ENABLE_DERIVATION=true to enable automatic derivation."
507
+ "Parameter derivation disabled. Set G2G_ENABLE_DERIVATION=true to "
508
+ "enable automatic derivation."
468
509
  )
469
510
  return cfg
470
511
 
@@ -486,7 +527,7 @@ def apply_parameter_derivation(
486
527
  newly_derived[key] = value
487
528
 
488
529
  if newly_derived:
489
- log.info(
530
+ log.debug(
490
531
  "Derived parameters applied for organization '%s' (%s): %s",
491
532
  organization,
492
533
  "GitHub Actions" if is_github_actions else "Local CLI",
@@ -495,7 +536,9 @@ def apply_parameter_derivation(
495
536
  # Save newly derived parameters to configuration file for future use
496
537
  # Default to true for local CLI, false for GitHub Actions
497
538
  default_auto_save = "false" if _is_github_actions_context() else "true"
498
- 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 (
499
542
  "1",
500
543
  "true",
501
544
  "yes",
@@ -505,7 +548,7 @@ def apply_parameter_derivation(
505
548
  # Save to config in local CLI mode to create persistent configuration
506
549
  try:
507
550
  save_derived_parameters_to_config(organization, newly_derived)
508
- log.info(
551
+ log.debug(
509
552
  "Automatically saved derived parameters to configuration "
510
553
  "file for organization '%s'. "
511
554
  "This creates a persistent configuration that you can "
@@ -542,7 +585,9 @@ def save_derived_parameters_to_config(
542
585
  return
543
586
 
544
587
  if config_path is None:
545
- 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
+ )
546
591
 
547
592
  config_file = Path(config_path).expanduser()
548
593
 
@@ -550,7 +595,8 @@ def save_derived_parameters_to_config(
550
595
  # Only update when a configuration file already exists
551
596
  if not config_file.exists():
552
597
  log.debug(
553
- "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",
554
600
  config_file,
555
601
  )
556
602
  return