cb424d7448
verify-patch-sanity.py validates every active recipe .patch has internally- consistent hunk line counts — catching the 'malformed patch at line N' failure at commit/CI/preflight time instead of hours into a cook. This cycle hit that class three times (qtwaylandscanner, sddm, xwayland), each only discovered when cookbook tried to apply the patch. Running it across the repo found 29 latent malformed patches (validated against GNU patch: e.g. relibc/P3-sysv-ipc reproduces 'malformed patch at line 22'). They were harmless only because they sit in vendored recipes (baked, not re- applied) — but would fail on any version-bump re-derivation. --fix recounts the hunk headers (body untouched) and repaired all 29. Wired into build-preflight.sh (Phase 1.0D) and redbear-ci.yml, with a unit test (test-patch-sanity.sh). Skips archived/legacy trees and unvalidatable formats (empty placeholders, bare-@@ git hunks).
134 lines
4.0 KiB
Python
Executable File
134 lines
4.0 KiB
Python
Executable File
#!/usr/bin/python3
|
|
#
|
|
# Copyright 2025 Valve Corporation
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import argparse
|
|
import csv
|
|
import unittest
|
|
import sys
|
|
import subprocess
|
|
import os
|
|
import shlex
|
|
from unidecode import unidecode
|
|
|
|
def normalize(x):
|
|
return unidecode(x.lower())
|
|
|
|
def name(row):
|
|
return normalize(row[0])
|
|
|
|
def username(row):
|
|
return normalize(row[2])
|
|
|
|
def find_person(x):
|
|
x = normalize(x)
|
|
|
|
filename = 'people.csv'
|
|
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), filename)
|
|
with open(path, 'r') as f:
|
|
people = list(csv.reader(f, skipinitialspace=True))
|
|
|
|
# First, try to exactly match username
|
|
for row in people:
|
|
if username(row) == x:
|
|
return row
|
|
|
|
# Next, try to exactly match fullname
|
|
for row in people:
|
|
if name(row) == x:
|
|
return row
|
|
|
|
# Now we get fuzzy. Try to match a first name.
|
|
candidates = [r for r in people if name(r).split(' ')[0] == x]
|
|
if len(candidates) == 1:
|
|
return candidates[0]
|
|
|
|
# Or a last name?
|
|
candidates = [r for r in people if x in name(r).split(' ')]
|
|
if len(candidates) == 1:
|
|
return candidates[0]
|
|
|
|
# Well, frick.
|
|
return None
|
|
|
|
# Self-test... is it even worth find a unit test framework for this?
|
|
TEST_CASES = {
|
|
'gfxstrand': 'faith.ekstrand@collabora.com',
|
|
'Faith': 'faith.ekstrand@collabora.com',
|
|
'faith': 'faith.ekstrand@collabora.com',
|
|
'alyssa': 'alyssa.rosenzweig@intel.com',
|
|
'briano': 'ivan.briano@intel.com',
|
|
'schurmann': 'daniel@schuermann.dev',
|
|
'Schürmann': 'daniel@schuermann.dev',
|
|
}
|
|
|
|
for test in TEST_CASES:
|
|
a, b = find_person(test), TEST_CASES[test]
|
|
if a is None or a[1] != b:
|
|
print(test, a, b)
|
|
assert(a is not None and a[1] == b)
|
|
|
|
# Now the tool itself
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
prog='rb',
|
|
description='Add review trailers')
|
|
parser.add_argument('person', nargs='+', help="Reviewer's username, first name, or full name")
|
|
parser.add_argument('-a', '--ack', action='store_true', help="Apply an acked-by tag")
|
|
parser.add_argument('-t', '--test', action='store_true', help="Apply an tested-by tag")
|
|
parser.add_argument('-d', '--dry-run', action='store_true',
|
|
help="Print trailer without applying")
|
|
parser.add_argument('-r', '--rebase', nargs='?',
|
|
help="Rebase on the specified branch applying tags to all commits")
|
|
args = parser.parse_args()
|
|
|
|
# If we are rebasing, let git reinvoke this script with the rebase related
|
|
# arguments stripped.
|
|
if args.rebase is not None:
|
|
relevant_args = [sys.argv[0]]
|
|
if args.ack:
|
|
relevant_args.append("--ack")
|
|
elif args.test:
|
|
relevant_args.append("--test")
|
|
|
|
relevant_args += args.person
|
|
|
|
cmd = f"python3 {' '.join(relevant_args)}"
|
|
rebase = ['git', 'rebase', '--exec', cmd, args.rebase]
|
|
|
|
if args.dry_run:
|
|
print(' '.join([shlex.quote(s) for s in rebase]))
|
|
returncode = 0
|
|
else:
|
|
returncode = subprocess.run(rebase).returncode
|
|
|
|
sys.exit(returncode)
|
|
|
|
for p in args.person:
|
|
person = find_person(p)
|
|
if person is None:
|
|
print(f'Could not uniquely identify {p}, skipping')
|
|
|
|
trailer = 'Reviewed-by'
|
|
if args.ack:
|
|
trailer = 'Acked-by'
|
|
elif args.test:
|
|
trailer = 'Tested-by'
|
|
|
|
trailer = f'{trailer}: {person[0]} <{person[1]}>'
|
|
|
|
if args.dry_run:
|
|
print(trailer)
|
|
continue
|
|
|
|
diff_index = subprocess.run("git diff-index --quiet --cached HEAD --".split(" "))
|
|
if diff_index.returncode != 0:
|
|
print("You have staged changes.")
|
|
print("Please commit before applying review tags.")
|
|
sys.exit(1)
|
|
|
|
env = os.environ.copy()
|
|
env['GIT_EDITOR'] = f'git interpret-trailers --trailer "{trailer}" --in-place'
|
|
subprocess.run(["git", "commit", "--amend"], env=env)
|