| #!/bin/bash | |
| #set -x | |
| # Enable error tracing but don't exit on error for the whole script | |
| set -E | |
| # Global variables | |
| LINUX_DIR="$HOME/linux" | |
| TEMP_PATCH="" | |
| PENDING_DIR="$HOME/pending/series" | |
| WORKTREE_DIR="$HOME/git-worktrees" | |
| # Function to create a temporary git worktree | |
| create_git_worktree() { | |
| local base_branch="$1" | |
| local branch_name="$2" | |
| local linux_dir="$3" | |
| mkdir -p "$WORKTREE_DIR" | |
| # Generate a unique path with timestamp | |
| local worktree_path="${WORKTREE_DIR}/${branch_name}-$(date +%s)" | |
| # If the directory already exists for some reason, remove it | |
| if [ -d "$worktree_path" ]; then | |
| rm -rf "$worktree_path" | |
| fi | |
| cd "$linux_dir" | |
| # Verify the base branch exists | |
| if ! git rev-parse --verify "$base_branch" >/dev/null 2>&1; then | |
| echo "Error: Branch $base_branch does not exist" >&2 | |
| return 1 | |
| fi | |
| # Create the worktree | |
| local worktree_result=$(git worktree add --detach "$worktree_path" "$base_branch" 2>&1) | |
| if [ $? -ne 0 ]; then | |
| echo "Error creating worktree: $worktree_result" >&2 | |
| return 1 | |
| fi | |
| echo "$worktree_path" | |
| } | |
| # Function to remove a git worktree | |
| remove_git_worktree() { | |
| local worktree_path="$1" | |
| local linux_dir="$2" | |
| # Check if the worktree path exists | |
| if [ ! -d "$worktree_path" ]; then | |
| return 0 | |
| fi | |
| cd "$linux_dir" | |
| # First make sure any ongoing git operations are aborted | |
| cd "$worktree_path" 2>/dev/null | |
| if [ $? -eq 0 ]; then | |
| # If there's a rebase in progress, abort it | |
| if [ -d ".git/rebase-apply" ]; then | |
| git rebase --abort >/dev/null 2>&1 | |
| fi | |
| # If there's an am in progress, abort it | |
| if [ -d ".git/rebase-apply" ]; then | |
| git am --abort >/dev/null 2>&1 | |
| fi | |
| # Reset and clean the worktree | |
| git reset --hard >/dev/null 2>&1 | |
| git clean -fdx >/dev/null 2>&1 | |
| cd "$linux_dir" | |
| fi | |
| # Try to remove the worktree | |
| if ! git worktree remove --force "$worktree_path" >/dev/null 2>&1; then | |
| # If git worktree remove fails, try more aggressive cleanup | |
| echo "Warning: Failed to remove worktree via git, using rm -rf" >&2 | |
| rm -rf "$worktree_path" | |
| # Prune the worktree list | |
| git worktree prune >/dev/null 2>&1 | |
| fi | |
| } | |
| # Function to generate unique message ID | |
| generate_message_id() { | |
| local timestamp=$(date +%Y%m%d%H%M%S) | |
| local random=$(openssl rand -hex 8) | |
| echo "<${timestamp}-${random}@stable.kernel.org>" | |
| } | |
| # Function to generate unique response filename | |
| generate_response_filename() { | |
| local mbox_file="$1" | |
| local base_dir="$HOME/Mail/stable/respo" | |
| local subject=$(formail -xSubject: < "$mbox_file" | tr '\n' ' ') | |
| local sender=$(formail -xFrom: < "$mbox_file" | tr -dc '[:alnum:]@.<> _-') | |
| local date_str=$(formail -xDate: < "$mbox_file") | |
| local timestamp=$(date -d "$date_str" +%Y%m%d%H%M%S 2>/dev/null || date +%Y%m%d%H%M%S) | |
| local message_id=$(formail -xMessage-ID: < "$mbox_file" | tr -dc '[:alnum:]@._-') | |
| # Clean subject (take first 30 chars, remove special chars) | |
| local clean_subject=$(echo "$subject" | tr -dc '[:alnum:] ' | tr ' ' '_' | cut -c1-30) | |
| # Clean sender email (extract just the email part if possible, otherwise use whole string) | |
| local clean_sender=$(echo "$sender" | grep -o '[^< ]*@[^> ]*' || echo "$sender" | tr -dc '[:alnum:]@._-') | |
| # Create unique filename | |
| local filename="${clean_sender}-${timestamp}-${clean_subject}-${message_id}.response" | |
| echo "${base_dir}/${filename}" | |
| } | |
| check_response_exists() { | |
| local mbox_file="$1" | |
| local response_file=$(generate_response_filename "$mbox_file") | |
| if [ -f "$response_file" ]; then | |
| echo "Response file already exists: $response_file" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Function to decode UTF-8 MIME encoded text using Python | |
| decode_mime_header() { | |
| local encoded_text="$1" | |
| [ -z "$encoded_text" ] && return 1 | |
| python3 -c ' | |
| import sys | |
| import email.header | |
| import email.quoprimime | |
| import email.base64mime | |
| def decode_header(text): | |
| # Add a maximum iteration count to prevent infinite loops | |
| max_iterations = 100 | |
| iteration = 0 | |
| # Handle quoted-printable and base64 encoded UTF-8 headers | |
| while "=?UTF-8?" in text and iteration < max_iterations: | |
| iteration += 1 | |
| start = text.find("=?UTF-8?") | |
| end = text.find("?=", start) + 2 | |
| if end <= 1: # No closing "?=" found | |
| break | |
| encoded_part = text[start:end] | |
| try: | |
| # Extract encoding type (B or Q) and encoded text | |
| parts = encoded_part.split("?") | |
| if len(parts) != 5: | |
| # Skip this part and continue with the rest of the text | |
| text = text[:start] + text[end:] | |
| continue | |
| charset, encoding, encoded_text = parts[1:4] | |
| if encoding.upper() == "B": | |
| decoded = email.base64mime.decode(encoded_text.encode()) | |
| elif encoding.upper() == "Q": | |
| decoded = email.quoprimime.header_decode(encoded_text) | |
| else: | |
| # Skip unsupported encoding | |
| text = text[:start] + text[end:] | |
| continue | |
| # Replace encoded part with decoded text | |
| new_text = text[:start] + decoded.decode(charset) + text[end:] | |
| # Ensure we made progress | |
| if new_text == text: | |
| # If no change happened, skip this part to avoid infinite loop | |
| text = text[:start] + text[end:] | |
| else: | |
| text = new_text | |
| except Exception: | |
| # If decoding fails, skip this part | |
| text = text[:start] + text[end:] | |
| continue | |
| # If we hit the maximum iterations, fall back to using standard library | |
| if iteration >= max_iterations: | |
| try: | |
| # Try the standard Python email.header module as a fallback | |
| decoded_parts = email.header.decode_header(text) | |
| return str(email.header.make_header(decoded_parts)) | |
| except Exception: | |
| # If even that fails, return as is | |
| pass | |
| return text | |
| print(decode_header(sys.argv[1])) | |
| ' "$encoded_text" 2>/dev/null || echo "$encoded_text" | |
| } | |
| # Function to normalize author string for comparison | |
| normalize_author() { | |
| local author="$1" | |
| local normalized="" | |
| # First decode any UTF-8 MIME encoding | |
| normalized=$(decode_mime_header "$author") | |
| # Remove extra quotes and normalize whitespace | |
| normalized=$(echo "$normalized" | sed -E ' | |
| # Remove surrounding quotes if they exist | |
| s/^"([^"]+)"$/\1/ | |
| # Remove extra spaces around < and > | |
| s/[[:space:]]*<[[:space:]]*/</g | |
| s/[[:space:]]*>[[:space:]]*/>/g | |
| # Normalize spaces between parts | |
| s/[[:space:]]+/ /g | |
| # Trim leading/trailing whitespace | |
| s/^[[:space:]]+// | |
| s/[[:space:]]+$// | |
| ') | |
| echo "$normalized" | |
| } | |
| # Function to check if we should ignore this mail based on sender | |
| should_ignore_mail() { | |
| local mbox_file="$1" | |
| local from=$(formail -xFrom: < "$mbox_file" | sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') | |
| if check_response_exists "$MBOX_FILE"; then | |
| exit 0 | |
| fi | |
| # List of authors to ignore | |
| if [[ "$from" =~ ^"Sasha Levin".*$ ]] || \ | |
| [[ "$from" =~ ^"Linux Kernel Distribution System".*$ ]] || \ | |
| [[ "$from" =~ ^"Greg Kroah-Hartman".*$ ]]; then | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Function to check if mail contains a git patch | |
| is_git_patch() { | |
| local mbox_file="$1" | |
| # We need to use formail -c to process the entire email including headers and body | |
| formail -c < "$mbox_file" | awk ' | |
| BEGIN { | |
| found=0 | |
| p=0 | |
| in_headers=1 | |
| has_patch_subject=0 | |
| has_diff=0 | |
| } | |
| # Track when we move from email headers to body | |
| in_headers && /^$/ { in_headers=0; next } | |
| # Look for [PATCH] in subject line while in headers | |
| in_headers && /^Subject:.*\[PATCH/ { has_patch_subject=1; next } | |
| # Look for patch separator | |
| !in_headers && /^---$/ { p=NR; next } | |
| # Look for diff or index after separator | |
| p && (/^diff --git/ || /^index [0-9a-f]/) { has_diff=1; exit } | |
| # Exit if we go too far past the separator | |
| NR > p+20 && p { exit } | |
| END { exit !(has_patch_subject && has_diff) } | |
| ' | |
| } | |
| # Function to decode UTF-8 email subject | |
| decode_subject() { | |
| local encoded_subject="$1" | |
| echo "$encoded_subject" | decode_mime_header | |
| } | |
| # Function to extract series info from subject | |
| extract_series_info() { | |
| local subject="$1" | |
| # Pattern to match [PATCH X/N] format | |
| local part_pattern='\[PATCH.*[[:space:]]([0-9]+)/([0-9]+)\]' | |
| if [[ $subject =~ $part_pattern ]]; then | |
| local current="${BASH_REMATCH[1]}" | |
| local total="${BASH_REMATCH[2]}" | |
| # Remove leading zeros to avoid octal interpretation | |
| current=$((10#$current)) | |
| total=$((10#$total)) | |
| echo "$current $total" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Function to get message ID from mail | |
| get_message_id() { | |
| local mbox_file="$1" | |
| formail -xMessage-ID: < "$mbox_file" | tr -d '[:space:]' | |
| } | |
| # Function to get in-reply-to ID from mail | |
| get_in_reply_to() { | |
| local mbox_file="$1" | |
| formail -xIn-Reply-To: < "$mbox_file" | tr -d '[:space:]' | |
| } | |
| # Function to generate series directory name | |
| get_series_dir() { | |
| local message_id="$1" | |
| local in_reply_to="$2" | |
| # Use the first message's ID (either this message if it's first, or the one it replies to) | |
| local series_id="${in_reply_to:-$message_id}" | |
| # Create a safe directory name from the message ID | |
| echo "${series_id}" | sed 's/[<>]//g' | tr -c '[:alnum:]' '_' | |
| } | |
| # Function to generate a more unique patch filename | |
| generate_patch_filename() { | |
| local base_dir="$1" | |
| local part="$2" | |
| local subject="$3" | |
| local sender="$4" | |
| local timestamp="$5" | |
| local message_id="$6" | |
| # Clean subject to use in filename (take first 30 chars, remove special chars) | |
| local clean_subject=$(echo "$subject" | tr -dc '[:alnum:] ' | tr ' ' '_' | cut -c1-30) | |
| # Clean sender email (extract just the email part if possible, otherwise use whole string) | |
| local clean_sender=$(echo "$sender" | grep -o '[^< ]*@[^> ]*' || echo "$sender" | tr -dc '[:alnum:]@._-') | |
| # Create unique filename combining all elements | |
| local clean_message_id=$(echo "$message_id" | tr -dc '[:alnum:]@._-') | |
| echo "${part}-${clean_sender}-${timestamp}-${clean_subject}-${clean_message_id}.mbox" | |
| } | |
| # Function to strip leading zeros from a number | |
| strip_leading_zeros() { | |
| local num="$1" | |
| # Use parameter expansion to remove leading zeros | |
| # Then use 10#$num to ensure base-10 interpretation | |
| num="${num##0}" | |
| # If num is empty, it was zero, so return 0 | |
| if [ -z "$num" ]; then | |
| echo "0" | |
| else | |
| echo "$((10#$num))" # Force base-10 interpretation | |
| fi | |
| } | |
| # Function to store patch in series directory | |
| store_patch() { | |
| local mbox_file="$1" | |
| local series_dir="$2" | |
| local part=$(strip_leading_zeros "$3") | |
| # Extract additional information for filename | |
| local subject=$(formail -xSubject: < "$mbox_file" | tr '\n' ' ') | |
| local sender=$(formail -xFrom: < "$mbox_file" | tr -dc '[:alnum:]@.<> _-') | |
| local date_str=$(formail -xDate: < "$mbox_file") | |
| local timestamp=$(date -d "$date_str" +%Y%m%d%H%M%S 2>/dev/null || date +%Y%m%d%H%M%S) | |
| local message_id=$(formail -xMessage-ID: < "$mbox_file") | |
| # Generate unique filename | |
| local filename=$(generate_patch_filename "$series_dir" "$part" "$subject" "$sender" "$timestamp" "$message_id") | |
| mkdir -p "$series_dir" | |
| cp "$mbox_file" "$series_dir/$filename" | |
| # Create a symlink with just the part number for backward compatibility | |
| ln -sf "$filename" "$series_dir/$part.mbox" | |
| } | |
| # Function to check if series is complete | |
| is_series_complete() { | |
| local series_dir="$1" | |
| local total_parts="$2" | |
| for ((i=1; i<=total_parts; i++)); do | |
| if [ ! -f "$series_dir/$i.mbox" ]; then | |
| return 1 | |
| fi | |
| done | |
| return 0 | |
| } | |
| # Function to clean subject for searching | |
| clean_subject() { | |
| local subject="$1" | |
| local cleaned_subject | |
| # Special handling for FAILED patch subjects | |
| if [[ "$subject" =~ FAILED:.*\"([^\"]+)\".*failed[[:space:]]to[[:space:]]apply ]]; then | |
| # Extract the actual patch subject from within quotes | |
| cleaned_subject="${BASH_REMATCH[1]}" | |
| else | |
| # Regular subject cleaning | |
| cleaned_subject=$(echo "$subject" | \ | |
| sed -E 's/\[[^]]*\]//g' | \ | |
| tr '\n' ' ' | \ | |
| tr -s ' ' | \ | |
| sed -E 's/^[[:space:]]+|[[:space:]]+$//g') # Trim whitespace | |
| fi | |
| # Remove leading "Re: " if present | |
| cleaned_subject=$(echo "$cleaned_subject" | sed -E 's/^Re: *//') | |
| echo "$cleaned_subject" | |
| } | |
| # Function to find commit by subject in origin/master | |
| find_commit_by_subject() { | |
| local subject="$1" | |
| local linux_dir="$2" | |
| local cleaned_subject | |
| cd "$linux_dir" | |
| cleaned_subject=$(clean_subject "$subject") | |
| cleaned_subject=$(printf '%s' "$cleaned_subject" | sed 's/[[\.*^$/]/\\&/g') | |
| git log origin/master --format="%H" --grep="^${cleaned_subject}$" -1 | |
| } | |
| # Function to apply previous patches in series | |
| apply_series_patches() { | |
| local series_dir="$1" | |
| local current_part="$2" | |
| local worktree_path="$3" | |
| cd "$worktree_path" | |
| # Clean up any previous rebase-apply directory that might exist | |
| if [ -d ".git/rebase-apply" ]; then | |
| rm -rf ".git/rebase-apply" | |
| fi | |
| # Reset to ensure clean state | |
| git reset --hard >/dev/null 2>&1 | |
| # Apply all patches up to but not including the current one | |
| for ((i=1; i<current_part; i++)); do | |
| local patch_file="$series_dir/$i.mbox" | |
| if [ ! -f "$patch_file" ]; then | |
| echo "Error: Patch file $i does not exist in series" | |
| return 1 | |
| fi | |
| # Try to apply the patch | |
| local apply_result=$(git am "$patch_file" 2>&1) | |
| if [ $? -ne 0 ]; then | |
| echo "Error: Failed to apply patch $i in series" | |
| echo "Error details: $apply_result" | |
| # Make sure to clean up | |
| git am --abort >/dev/null 2>&1 | |
| return 1 | |
| fi | |
| done | |
| return 0 | |
| } | |
| # Function to extract kernel versions from subject | |
| extract_kernel_versions() { | |
| local subject="$1" | |
| local active_versions_file="$HOME/stable-queue/active_kernel_versions" | |
| local found_versions=() | |
| local range_start="" | |
| local range_end="" | |
| # First check for version ranges (e.g., "5.10-6.1" or "5.10.y-6.1.y" or "v5.10-v6.1" or "v6.1-v5.4") | |
| if [[ "$subject" =~ (^|[^0-9.])(v)?([0-9]+\.[0-9]+)(\.y)?-(v)?([0-9]+\.[0-9]+)(\.y)?([^0-9.]|$) ]]; then | |
| local ver1="${BASH_REMATCH[3]}" | |
| local ver2="${BASH_REMATCH[6]}" | |
| # Determine which is higher and lower for the range check | |
| if [ "$(printf "%s\n%s" "$ver1" "$ver2" | sort -V | head -n1)" = "$ver2" ]; then | |
| range_start="$ver2" | |
| range_end="$ver1" | |
| else | |
| range_start="$ver1" | |
| range_end="$ver2" | |
| fi | |
| # Read all versions and filter those within range | |
| while IFS= read -r version; do | |
| # Version is in range if: | |
| # 1. It's >= range_start (version is not smallest when sorted with range_start) | |
| # 2. It's <= range_end (version is not largest when sorted with range_end) | |
| if [ "$(printf "%s\n%s" "$range_start" "$version" | sort -V | head -n1)" = "$range_start" ] && \ | |
| [ "$(printf "%s\n%s" "$version" "$range_end" | sort -V | head -n1)" = "$version" ]; then | |
| found_versions+=("$version") | |
| fi | |
| done < "$active_versions_file" | |
| fi | |
| # If no range found, check for individual versions | |
| if [ ${#found_versions[@]} -eq 0 ]; then | |
| while IFS= read -r version; do | |
| # Check for version numbers with optional v prefix and optional .y suffix | |
| # Use a more strict pattern that requires the exact X.Y or X.Y.y format | |
| if printf "%s" "$subject" | grep -E -q "(^|[^0-9.])${version}(\.y)?([^0-9.]|$)|(^|[[:space:]]|\[)v${version}(\.y)?([^0-9.]|$)"; then | |
| found_versions+=("$version") | |
| fi | |
| done < "$active_versions_file" | |
| fi | |
| if [ ${#found_versions[@]} -gt 0 ]; then | |
| # Sort versions in ascending order | |
| result=$(printf "%s\n" "${found_versions[@]}" | sort -V | tr '\n' ' ' | sed 's/ $//') | |
| echo "$result" | |
| return 0 | |
| fi | |
| cat "$active_versions_file" | |
| } | |
| # Function to extract commit SHA1 from email body | |
| extract_commit_sha1() { | |
| local email_body="$1" | |
| local sha1="" | |
| # First pattern: Look for "commit [SHA1] upstream" with case insensitivity | |
| # Using -i flag makes grep ignore case when matching | |
| sha1=$(echo "$email_body" | grep -i -E "commit [0-9a-f]{40} upstream" | \ | |
| sed -E 's/.*[Cc][Oo][Mm][Mm][Ii][Tt] ([0-9a-f]{40}) [Uu][Pp][Ss][Tt][Rr][Ee][Aa][Mm].*/\1/') | |
| if [ -n "$sha1" ]; then | |
| echo "$sha1" | |
| return 0 | |
| fi | |
| # Second pattern: Look for "[ Upstream commit [SHA1] ]" with case insensitivity | |
| sha1=$(echo "$email_body" | grep -i -E "\[[[:space:]]*upstream[[:space:]]+commit[[:space:]]+[0-9a-f]{40}[[:space:]]*\]" | \ | |
| sed -E 's/.*\[[[:space:]]*[Uu][Pp][Ss][Tt][Rr][Ee][Aa][Mm][[:space:]]+[Cc][Oo][Mm][Mm][Ii][Tt][[:space:]]+([0-9a-f]{40})[[:space:]]*\].*/\1/') | |
| if [ -n "$sha1" ]; then | |
| echo "$sha1" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Function to extract patch author with normalization | |
| extract_patch_author() { | |
| local mbox_file="$1" | |
| local author=$(formail -xFrom: < "$mbox_file") | |
| normalize_author "$author" | |
| } | |
| # Function to get commit author with normalization | |
| get_commit_author() { | |
| local linux_dir="$1" | |
| local sha1="$2" | |
| cd "$linux_dir" | |
| local author=$(git log -1 --format="%an <%ae>" "$sha1") | |
| normalize_author "$author" | |
| } | |
| # Function to compare authors and check if they match | |
| authors_match() { | |
| local author1="$1" | |
| local author2="$2" | |
| # Normalize both authors | |
| local norm1=$(normalize_author "$author1") | |
| local norm2=$(normalize_author "$author2") | |
| # Extract email parts for comparison | |
| local email1=$(echo "$norm1" | grep -o '<[^>]*>' || echo "") | |
| local email2=$(echo "$norm2" | grep -o '<[^>]*>' || echo "") | |
| # If emails match and are non-empty, authors match | |
| if [ -n "$email1" ] && [ "$email1" = "$email2" ]; then | |
| return 0 | |
| fi | |
| # If emails don't match or are empty, compare full normalized strings | |
| [ "$norm1" = "$norm2" ] | |
| } | |
| # Function to get sorted kernel versions | |
| get_sorted_versions() { | |
| sort -rV "$HOME/stable-queue/active_kernel_versions" | |
| } | |
| # Function to check if version1 is newer than version2 | |
| is_version_newer() { | |
| local v1="$1" | |
| local v2="$2" | |
| if [ "$(echo -e "$v1\n$v2" | sort -V | tail -n1)" = "$v1" ]; then | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| # Function to check newer kernels for commit | |
| check_newer_kernels() { | |
| local sha1="$1" | |
| local target_versions="$2" | |
| local linux_dir="$3" | |
| local -n c_results=$4 | |
| cd "$linux_dir" | |
| local all_versions=($(get_sorted_versions)) | |
| local target_array=($target_versions) | |
| local newest_target=${target_array[0]} | |
| local temp_dir=$(mktemp -d) | |
| local pids=() | |
| local checked_versions=() | |
| # Function to check a single branch and write results to temp file | |
| check_single_branch() { | |
| local version="$1" | |
| local branch="pending-${version}" | |
| local result_file="$temp_dir/$version" | |
| # Check if branch exists | |
| if ! git rev-parse --verify "$branch" >/dev/null 2>&1; then | |
| echo "$version.y | Branch not found" > "$result_file" | |
| return | |
| fi | |
| # Check if commit is an ancestor using merge-base | |
| if [ "$(git merge-base "$branch" "$sha1")" = "$sha1" ]; then | |
| echo "$version.y | Present (exact SHA1)" > "$result_file" | |
| return | |
| fi | |
| # Try to find by subject if SHA1 not found | |
| local subject | |
| subject=$(git log -1 --format=%s "$sha1") | |
| if [ -n "$subject" ]; then | |
| # Search in the specific branch | |
| local found_commit | |
| found_commit=$(git log "$branch" --format=%H --grep="^${subject}$" -1) | |
| if [ -n "$found_commit" ]; then | |
| echo "$version.y | Present (different SHA1: ${found_commit:0:12})" > "$result_file" | |
| else | |
| echo "$version.y | Not found" > "$result_file" | |
| fi | |
| else | |
| echo "$version.y | Not found" > "$result_file" | |
| fi | |
| } | |
| # Launch parallel processes for each relevant version | |
| for version in "${all_versions[@]}"; do | |
| # Only check versions newer than our target using sort -V for proper semantic version comparison | |
| # If version is newer than newest_target, it will be the second item when sorted | |
| if [ "$(printf "%s\n%s" "$newest_target" "$version" | sort -V | head -n1)" = "$newest_target" ] && \ | |
| [ "$newest_target" != "$version" ]; then | |
| check_single_branch "$version" & | |
| pids+=($!) | |
| checked_versions+=("$version") | |
| fi | |
| done | |
| # Wait for all processes to complete | |
| for pid in "${pids[@]}"; do | |
| wait "$pid" | |
| done | |
| # Clear the results array | |
| c_results=() | |
| # Collect results in order (from newest to oldest) | |
| for version in "${checked_versions[@]}"; do | |
| if [ -f "$temp_dir/$version" ]; then | |
| c_results+=("$(cat "$temp_dir/$version")") | |
| fi | |
| done | |
| # Clean up | |
| rm -rf "$temp_dir" | |
| } | |
| # Function to validate commit exists in upstream | |
| validate_commit() { | |
| local sha1="$1" | |
| local linux_dir="$2" | |
| cd "$linux_dir" | |
| git merge-base --is-ancestor "$sha1" origin/master | |
| } | |
| # Function to compare patch with upstream | |
| compare_with_upstream() { | |
| local mbox_file="$1" | |
| local sha1="$2" | |
| local linux_dir="$3" | |
| local series_dir="$4" | |
| local current_part="$5" | |
| local debug_output=() | |
| if [[ ! $sha1 =~ ^[0-9a-f]{40}$ ]] || [ "$sha1" = "0000000000000000000000000000000000000000" ]; then | |
| return 0 | |
| fi | |
| cd "$linux_dir" | |
| # Extract subject to determine target kernel versions | |
| local subject=$(formail -xSubject: < "$mbox_file") | |
| local kernel_versions=($(extract_kernel_versions "$subject")) | |
| if [ ${#kernel_versions[@]} -eq 0 ]; then | |
| debug_output+=("No kernel versions found in subject, cannot compare") | |
| printf '%s\n' "${debug_output[@]}" | |
| return 1 | |
| fi | |
| # Use the newest version for comparison since it's closest to upstream | |
| local version=${kernel_versions[0]} | |
| local stable_branch="stable/linux-${version}.y" | |
| local temp_branch="temp-compare-${version}-$(date +%s)" | |
| # Create a worktree instead of checking out branches | |
| local worktree_path=$(create_git_worktree "$stable_branch" "$temp_branch" "$linux_dir") | |
| if [ -z "$worktree_path" ]; then | |
| debug_output+=("Failed to create worktree for ${stable_branch}") | |
| printf '%s\n' "${debug_output[@]}" | |
| return 1 | |
| fi | |
| cd "$worktree_path" | |
| # Clean up any previous rebase-apply directory that might exist | |
| if [ -d ".git/rebase-apply" ]; then | |
| rm -rf ".git/rebase-apply" | |
| fi | |
| # Reset to ensure clean state | |
| git reset --hard >/dev/null 2>&1 | |
| # If this is part of a series, apply previous patches first | |
| if [ -n "$series_dir" ] && [ "$current_part" -gt 1 ]; then | |
| if ! apply_series_patches "$series_dir" "$current_part" "$worktree_path"; then | |
| debug_output+=("Failed to apply previous patches in series for comparison only.") | |
| debug_output+=("This doesn't affect the main patch testing.") | |
| cd "$linux_dir" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| printf '%s\n' "${debug_output[@]}" | |
| return 1 | |
| fi | |
| fi | |
| # Try to apply the patch | |
| local apply_result=$(git am "$mbox_file" 2>&1) | |
| if [ $? -eq 0 ]; then | |
| # Get the SHA1 of our newly applied patch | |
| local new_sha1=$(git rev-parse HEAD) | |
| debug_output+=("") | |
| # Compare the ranges using range-diff | |
| if ! git range-diff "${sha1}^".."$sha1" "${new_sha1}^".."$new_sha1"; then | |
| debug_output+=("Failed to generate range-diff, but patch applies cleanly.") | |
| fi | |
| # Clean up after range-diff | |
| cd "$linux_dir" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| else | |
| # Clean up failed git-am | |
| git am --abort >/dev/null 2>&1 | |
| cd "$linux_dir" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| debug_output+=("Note: Couldn't generate comparison with upstream commit.") | |
| debug_output+=("This is just for the diff comparison and doesn't affect the patch application.") | |
| debug_output+=("Error: $apply_result") | |
| fi | |
| printf '%s\n' "${debug_output[@]}" | |
| } | |
| # Function to test commit on a branch | |
| test_commit_on_branch() { | |
| local sha1="$1" | |
| local version="$2" | |
| local linux_dir="$3" | |
| local mbox_file="$4" | |
| local series_dir="$5" | |
| local current_part="$6" | |
| local -n results=$7 | |
| local -n errors=$8 | |
| local result=0 | |
| cd "$linux_dir" | |
| local branch="stable/linux-${version}.y" | |
| local temp_branch="temp-${version}-${sha1:0:8}" | |
| # Create a worktree instead of checking out branches | |
| local worktree_path=$(create_git_worktree "$branch" "$temp_branch" "$linux_dir") | |
| if [ -z "$worktree_path" ]; then | |
| results+=("stable/linux-${version}.y | Failed (branch not found) | N/A") | |
| return 1 | |
| fi | |
| cd "$worktree_path" | |
| # Clean up any previous rebase-apply directory that might exist | |
| if [ -d ".git/rebase-apply" ]; then | |
| rm -rf ".git/rebase-apply" | |
| fi | |
| # Reset to ensure clean state | |
| git reset --hard >/dev/null 2>&1 | |
| # Apply series patches if needed | |
| if [ -n "$series_dir" ] && [ "$current_part" -gt 1 ]; then | |
| if ! apply_series_patches "$series_dir" "$current_part" "$worktree_path"; then | |
| results+=("stable/linux-${version}.y | Failed (series apply) | N/A") | |
| cd "$linux_dir" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| return 1 | |
| fi | |
| fi | |
| # Apply current patch | |
| local apply_result=$(git am "$mbox_file" 2>&1) | |
| if [ $? -ne 0 ]; then | |
| # Extract patch content and try to apply with --reject to get .rej files | |
| local temp_patch=$(mktemp) | |
| formail -I "" < "$mbox_file" | sed '1,/^$/d' > "$temp_patch" | |
| git apply --reject "$temp_patch" >/dev/null 2>&1 | |
| # Find and read any .rej files | |
| local reject_content="" | |
| while IFS= read -r -d '' rej_file; do | |
| reject_content+=$'\n'"$(cat "$rej_file")" | |
| rm -f "$rej_file" | |
| done < <(find . -name "*.rej" -print0) | |
| rm -f "$temp_patch" | |
| # Clean up | |
| git checkout -f >/dev/null 2>&1 | |
| git am --abort >/dev/null 2>&1 | |
| results+=("stable/linux-${version}.y | Failed | N/A") | |
| if [ -n "$reject_content" ]; then | |
| errors+=("Patch failed to apply on ${branch}. Reject:") | |
| errors+=("$reject_content") | |
| else | |
| errors+=("Patch failed to apply on ${branch}: $apply_result") | |
| fi | |
| cd "$linux_dir" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| return 1 | |
| fi | |
| # Run build test | |
| local build_output | |
| build_output=$(stable build log 2>&1) | |
| local build_ret=$? | |
| if [ $build_ret -ne 0 ]; then | |
| results+=("stable/linux-${version}.y | Success | Failed") | |
| if [ -n "$build_output" ]; then | |
| errors+=("Build error for ${branch}:") | |
| errors+=("$(echo "$build_output" | sed 's/^/ /')") | |
| errors+=("") | |
| else | |
| errors+=("Build error for ${branch}: (no output captured)") | |
| fi | |
| result=1 | |
| else | |
| results+=("stable/linux-${version}.y | Success | Success") | |
| fi | |
| cd "$linux_dir" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| return $result | |
| } | |
| # Function to check for fixes referencing a commit | |
| check_fixes_for_commit() { | |
| local sha1="$1" | |
| local linux_dir="$2" | |
| local -n result_array=$3 | |
| cd "$linux_dir" | |
| # Look for commits with Fixes: tag pointing to our commit | |
| local fixes_commits=$(git log origin/master --grep="Fixes: ${sha1:0:12}" --format="%H %s") | |
| if [ -n "$fixes_commits" ]; then | |
| result_array+=("Found fixes commits:") | |
| while IFS= read -r line; do | |
| local fix_sha1="${line%% *}" | |
| local fix_subject="${line#* }" | |
| result_array+=("${fix_sha1:0:12} ${fix_subject}") | |
| done <<< "$fixes_commits" | |
| fi | |
| } | |
| # Function to check if commit was reverted | |
| check_if_reverted() { | |
| local sha1="$1" | |
| local linux_dir="$2" | |
| local -n result_array=$3 | |
| cd "$linux_dir" | |
| # Look for revert commits in subject and body | |
| local revert_commits=$(git log origin/master --grep="This reverts commit ${sha1:0:12}\|^Revert \".*${sha1:0:12}.*\"" --format="%H %s") | |
| # Also look for Fixes: tags in revert commits | |
| local fixes_reverts=$(git log origin/master --grep="^Revert.*\|Fixes: ${sha1:0:12}" --format="%H %B" | \ | |
| awk -v sha="$sha1" ' | |
| /^[0-9a-f]{40}/ { commit=$1; subject=$0; sub(/^[0-9a-f]{40}[[:space:]]*/, "", subject); } | |
| /^Revert/ && /Fixes: '"${sha1:0:12}"'/ { print commit " " subject; } | |
| ') | |
| # Combine both results | |
| local all_reverts=$(printf "%s\n%s" "$revert_commits" "$fixes_reverts" | sort -u) | |
| if [ -n "$all_reverts" ]; then | |
| result_array+=("Found revert commits:") | |
| while IFS= read -r line; do | |
| if [ -n "$line" ]; then | |
| local revert_sha1="${line%% *}" | |
| local revert_subject="${line#* }" | |
| result_array+=("${revert_sha1:0:12} ${revert_subject}") | |
| fi | |
| done <<< "$all_reverts" | |
| fi | |
| } | |
| # Function to process single patch or series patch | |
| process_patch() { | |
| local mbox_file="$1" | |
| local series_dir="$2" | |
| local current_part="$3" | |
| local -n p_results=$4 | |
| local -n p_errors=$5 | |
| local worktree_path="$6" # Optional worktree path when called from test_series | |
| local failed=0 | |
| # Extract series info first | |
| local subject=$(formail -xSubject: < "$mbox_file") | |
| local series_info=$(extract_series_info "$subject") | |
| local is_series_part=0 | |
| local total_parts=0 | |
| if [ -n "$series_info" ]; then | |
| read current_part total_parts <<< "$series_info" | |
| is_series_part=1 | |
| fi | |
| # If this is part of a series and not the first patch, verify previous patches can be applied | |
| # Skip this check if we're called from test_series with a provided worktree | |
| if [ -n "$series_dir" ] && [ "$current_part" -gt 1 ] && [ -z "$worktree_path" ]; then | |
| # Try to apply previous patches in a temporary worktree | |
| cd "$LINUX_DIR" | |
| local temp_branch="temp-series-check-$(date +%s)" | |
| # Create a worktree | |
| worktree_path=$(create_git_worktree "HEAD" "$temp_branch" "$LINUX_DIR") | |
| if [ -z "$worktree_path" ]; then | |
| p_results+=("All branches | Failed (could not create worktree) | N/A") | |
| p_errors+=("Error: Cannot proceed - failed to create worktree") | |
| return 1 | |
| fi | |
| if ! apply_series_patches "$series_dir" "$current_part" "$worktree_path"; then | |
| cd "$LINUX_DIR" | |
| remove_git_worktree "$worktree_path" "$LINUX_DIR" | |
| p_results+=("All branches | Failed (previous patches in series failed to apply) | N/A") | |
| p_errors+=("Error: Cannot proceed - previous patches in series failed to apply") | |
| p_errors+=("This is part ${current_part}/${total_parts} of a series.") | |
| p_errors+=("Please ensure all previous patches in the series apply cleanly.") | |
| return 1 | |
| fi | |
| cd "$LINUX_DIR" | |
| remove_git_worktree "$worktree_path" "$LINUX_DIR" | |
| worktree_path="" # Reset to empty after cleanup | |
| fi | |
| # Extract subject to get kernel versions | |
| local subject=$(formail -xSubject: < "$mbox_file") | |
| local kernel_versions=$(extract_kernel_versions "$subject") | |
| # Extract email body | |
| local email_body=$(formail -I "" < "$mbox_file") | |
| local claimed_sha1=$(extract_commit_sha1 "$email_body" || true) | |
| local found_sha1="" | |
| local author_mismatch="" | |
| local diff_output="" | |
| # Find or validate SHA1 | |
| if [ -z "$claimed_sha1" ]; then | |
| found_sha1=$(find_commit_by_subject "$subject" "$LINUX_DIR") | |
| else | |
| if validate_commit "$claimed_sha1" "$LINUX_DIR"; then | |
| found_sha1="$claimed_sha1" | |
| else | |
| found_sha1=$(find_commit_by_subject "$subject" "$LINUX_DIR") | |
| fi | |
| fi | |
| # Use temporary SHA1 if none found | |
| if [ -z "$found_sha1" ]; then | |
| found_sha1="0000000000000000000000000000000000000000" | |
| fi | |
| # Compare authors if we found a commit | |
| if [ -n "$found_sha1" ] && [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| local patch_author=$(extract_patch_author "$mbox_file") | |
| local commit_author=$(get_commit_author "$LINUX_DIR" "$found_sha1") | |
| if ! authors_match "$patch_author" "$commit_author"; then | |
| author_mismatch="Backport author: $patch_author | |
| Commit author: $commit_author" | |
| fi | |
| fi | |
| # Compare with upstream if we have a valid SHA1 | |
| if [[ "$found_sha1" =~ ^[0-9a-f]{40}$ ]] && [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| diff_output=$(compare_with_upstream "$mbox_file" "$found_sha1" "$LINUX_DIR" "$series_dir" "$current_part") | |
| fi | |
| # Test on each kernel version | |
| # Skip individual testing if we're called with a worktree (as part of test_series) | |
| if [ -z "$worktree_path" ]; then | |
| for version in $kernel_versions; do | |
| if ! test_commit_on_branch "$found_sha1" "$version" "$LINUX_DIR" "$mbox_file" \ | |
| "$series_dir" "$current_part" p_results p_errors; then | |
| failed=1 | |
| fi | |
| done | |
| else | |
| # If we have a worktree provided, just test applying the patch to it | |
| cd "$worktree_path" | |
| # Get the actual branch name for reporting | |
| local branch_name="" | |
| # First try to extract from the worktree path which should have the format temp-series-VERSION-timestamp | |
| if [[ "$worktree_path" =~ temp-series-([0-9]+\.[0-9]+)- ]]; then | |
| branch_name="${BASH_REMATCH[1]}" | |
| # Fallback to checking the git branch | |
| else | |
| # Get current git branch | |
| branch_name=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD) | |
| # Extract version if it's in the branch name | |
| if [[ "$branch_name" =~ linux-([0-9]+\.[0-9]+)\.y ]]; then | |
| branch_name="${BASH_REMATCH[1]}" | |
| fi | |
| fi | |
| # Default if we couldn't determine the branch | |
| local full_branch_name="Current branch" | |
| if [ -n "$branch_name" ]; then | |
| full_branch_name="stable/linux-${branch_name}.y" | |
| fi | |
| # Clean up any previous rebase-apply directory that might exist | |
| if [ -d ".git/rebase-apply" ]; then | |
| rm -rf ".git/rebase-apply" | |
| fi | |
| # Reset to ensure clean state | |
| git reset --hard >/dev/null 2>&1 | |
| # Try to apply the patch | |
| local apply_result=$(git am "$mbox_file" 2>&1) | |
| if [ $? -ne 0 ]; then | |
| p_results+=("$full_branch_name | Failed to apply | N/A") | |
| p_errors+=("Error applying patch to worktree: $apply_result") | |
| git am --abort >/dev/null 2>&1 | |
| failed=1 | |
| else | |
| # Run build test | |
| local build_output | |
| build_output=$(stable build log 2>&1) | |
| local build_ret=$? | |
| if [ $build_ret -ne 0 ]; then | |
| p_results+=("$full_branch_name | Success | Failed") | |
| if [ -n "$build_output" ]; then | |
| p_errors+=("Build error:") | |
| p_errors+=("$(echo "$build_output" | sed 's/^/ /')") | |
| p_errors+=("") | |
| else | |
| p_errors+=("Build error: (no output captured)") | |
| fi | |
| failed=1 | |
| else | |
| p_results+=("$full_branch_name | Success | Success") | |
| fi | |
| fi | |
| fi | |
| # Check newer kernels if we have a valid SHA1 | |
| local -a newer_kernel_results=() | |
| if [[ "$found_sha1" =~ ^[0-9a-f]{40}$ ]] && [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| check_newer_kernels "$found_sha1" "$kernel_versions" "$LINUX_DIR" newer_kernel_results | |
| fi | |
| # Check for fixes and reverts if we have a valid SHA1 | |
| local -a fixes_results=() | |
| local -a revert_results=() | |
| if [[ "$found_sha1" =~ ^[0-9a-f]{40}$ ]] && [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| check_fixes_for_commit "$found_sha1" "$LINUX_DIR" fixes_results | |
| check_if_reverted "$found_sha1" "$LINUX_DIR" revert_results | |
| fi | |
| # Generate response for this patch | |
| generate_response "$mbox_file" "$claimed_sha1" "$found_sha1" \ | |
| "$(printf '%s\n' "${p_results[@]}")" "$diff_output" \ | |
| "$author_mismatch" "$(printf '%s\n' "${p_errors[@]}")" \ | |
| "$(printf '%s\n' "${newer_kernel_results[@]}")" \ | |
| "$(printf '%s\n' "${fixes_results[@]}")" \ | |
| "$(printf '%s\n' "${revert_results[@]}")" | |
| return $failed | |
| } | |
| # Function to test complete series | |
| test_series() { | |
| local series_dir="$1" | |
| local total_parts="$2" | |
| local linux_dir="$3" | |
| local failed=0 | |
| # Get the first patch to determine target kernel versions | |
| local first_patch="$series_dir/1.mbox" | |
| if [ ! -f "$first_patch" ]; then | |
| echo "Error: First patch in series not found: $first_patch" | |
| return 1 | |
| fi | |
| # Extract subject and kernel versions from first patch | |
| local subject=$(formail -xSubject: < "$first_patch") | |
| local kernel_versions=$(extract_kernel_versions "$subject") | |
| if [ -z "$kernel_versions" ]; then | |
| echo "Error: Cannot determine target kernel versions from patch subject" | |
| return 1 | |
| fi | |
| # Process each kernel version | |
| for version in $kernel_versions; do | |
| local stable_branch="stable/linux-${version}.y" | |
| echo "Testing series on $stable_branch..." | |
| # Process each patch in the series | |
| for ((i=1; i<=total_parts; i++)); do | |
| local mbox_file="$series_dir/$i.mbox" | |
| declare -a patch_results=() | |
| declare -a patch_errors=() | |
| # Create a worktree for this branch | |
| # Use a naming convention that includes the version for easier extraction later | |
| local temp_branch="temp-series-${version}-$(date +%s)" | |
| local worktree_path=$(create_git_worktree "$stable_branch" "$temp_branch" "$linux_dir") | |
| if [ -z "$worktree_path" ]; then | |
| echo "Error: Failed to create worktree for branch $stable_branch" | |
| failed=1 | |
| continue | |
| fi | |
| # If not the first patch, apply previous patches | |
| if [ $i -gt 1 ]; then | |
| if ! apply_series_patches "$series_dir" "$i" "$worktree_path"; then | |
| echo "Error: Failed to apply previous patches in series on $stable_branch" | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| failed=1 | |
| break | |
| fi | |
| fi | |
| # Now process the current patch | |
| if ! process_patch "$mbox_file" "$series_dir" "$i" patch_results patch_errors "$worktree_path"; then | |
| failed=1 | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| break | |
| fi | |
| # Clean up worktree after testing each patch | |
| remove_git_worktree "$worktree_path" "$linux_dir" | |
| done | |
| done | |
| return $failed | |
| } | |
| # Function to generate email response | |
| generate_response() { | |
| local mbox_file="$1" | |
| local claimed_sha1="$2" | |
| local found_sha1="$3" | |
| local results="$4" | |
| local diff_output="$5" | |
| local author_mismatch="$6" | |
| local build_errors="$7" | |
| local newer_kernel_results="$8" | |
| local fixes_results="$9" | |
| local revert_results="${10}" | |
| local response_file=$(generate_response_filename "$mbox_file") | |
| # Check if this is a stable-only commit | |
| local email_body=$(formail -I "" < "$mbox_file") | |
| local is_stable_only=0 | |
| if echo "$email_body" | grep -i "stable.only" >/dev/null 2>&1; then | |
| is_stable_only=1 | |
| fi | |
| { | |
| # Get the From, Subject, Message-ID, and Date from original email for threading | |
| formail -X From: -X Subject: < "$mbox_file" | |
| echo "Message-ID: $(generate_message_id)" | |
| echo "Date: $(date -R)" | |
| echo "In-Reply-To: $(formail -xMessage-ID: < "$mbox_file")" | |
| echo "From: $(git config user.name) <$(git config user.email)>" | |
| # Use original subject line directly from the email, just add Re: if needed | |
| local orig_subject=$(formail -xSubject: < "$mbox_file" | \ | |
| tr '\n' ' ' | \ | |
| tr -s ' ' | \ | |
| sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*$//g') | |
| # Only add "Re: " if it's not already there | |
| if [[ ! "$orig_subject" =~ ^Re: ]]; then | |
| echo "Subject: Re: $orig_subject" | |
| else | |
| echo "Subject: $orig_subject" | |
| fi | |
| # Add summary section to determine if there are issues | |
| local has_issues=0 | |
| local summary=() | |
| local is_series_part=0 | |
| local series_info=$(extract_series_info "$orig_subject") | |
| if [ -n "$series_info" ]; then | |
| read current_part total_parts <<< "$series_info" | |
| is_series_part=1 | |
| if [ "$current_part" -gt 1 ]; then | |
| summary+=("ℹ️ This is part ${current_part}/${total_parts} of a series") | |
| fi | |
| fi | |
| # Check for build failures | |
| if [[ "$results" == *"| Failed"* ]]; then | |
| has_issues=1 | |
| summary+=("❌ Build failures detected") | |
| fi | |
| # Check for missing or unverified commit (skip for stable-only commits) | |
| if [ $is_stable_only -eq 0 ]; then | |
| if [ -z "$found_sha1" ] || [ "$found_sha1" = "0000000000000000000000000000000000000000" ]; then | |
| has_issues=1 | |
| summary+=("⚠️ Could not find matching upstream commit") | |
| elif [ -n "$claimed_sha1" ] && [ "$claimed_sha1" != "$found_sha1" ]; then | |
| has_issues=1 | |
| summary+=("⚠️ Provided upstream commit SHA1 does not match found commit") | |
| elif [ -z "$claimed_sha1" ] && [ -n "$found_sha1" ]; then | |
| has_issues=1 | |
| summary+=("⚠️ Found matching upstream commit but patch is missing proper reference to it") | |
| fi | |
| fi | |
| # Check for fixes | |
| if [ -n "$fixes_results" ]; then | |
| has_issues=1 | |
| summary+=("⚠️ Found follow-up fixes in mainline") | |
| fi | |
| # Check for reverts | |
| if [ -n "$revert_results" ]; then | |
| has_issues=1 | |
| summary+=("❌ Commit was reverted in mainline") | |
| fi | |
| # Add appropriate email headers based on whether there are issues | |
| if [ $has_issues -eq 1 ]; then | |
| # If there are issues, send to author and CC stable | |
| local author_email=$(formail -xFrom: < "$mbox_file" | sed -e 's/.*<\([^>]*\)>.*/\1/') | |
| echo "To: $author_email" | |
| echo "Cc: stable@vger.kernel.org" | |
| else | |
| # If no issues, send only to stable | |
| echo "To: stable@vger.kernel.org" | |
| fi | |
| echo | |
| echo "[ Sasha's backport helper bot ]" | |
| echo | |
| echo "Hi," | |
| echo | |
| # Print summary section | |
| if [ $has_issues -eq 1 ]; then | |
| echo "Summary of potential issues:" | |
| printf '%s\n' "${summary[@]}" | |
| echo | |
| else | |
| # Check if patch applies and builds successfully | |
| if [[ "$results" == *"| Success | Success"* ]]; then | |
| echo "✅ All tests passed successfully. No issues detected." | |
| echo "No action required from the submitter." | |
| echo | |
| fi | |
| fi | |
| # Report on SHA1 verification and commit status | |
| if [ $is_stable_only -eq 1 ]; then | |
| echo "Note: This is a stable-only commit." | |
| elif [ -n "$claimed_sha1" ]; then | |
| if [ "$claimed_sha1" = "$found_sha1" ]; then | |
| echo "The upstream commit SHA1 provided is correct: $claimed_sha1" | |
| if [ -n "$author_mismatch" ]; then | |
| echo | |
| echo "WARNING: Author mismatch between patch and upstream commit:" | |
| echo "$author_mismatch" | |
| fi | |
| else | |
| echo "The claimed upstream commit SHA1 ($claimed_sha1) was not found." | |
| if [ -n "$found_sha1" ] && [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| echo "However, I found a matching commit: $found_sha1" | |
| if [ -n "$author_mismatch" ]; then | |
| echo | |
| echo "WARNING: Author mismatch between patch and found commit:" | |
| echo "$author_mismatch" | |
| fi | |
| fi | |
| fi | |
| elif [ -n "$found_sha1" ] && [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| echo "Found matching upstream commit: $found_sha1" | |
| if [ -n "$author_mismatch" ]; then | |
| echo | |
| echo "WARNING: Author mismatch between patch and found commit:" | |
| echo "$author_mismatch" | |
| fi | |
| else | |
| echo "No upstream commit was identified. Using temporary commit for testing." | |
| fi | |
| echo | |
| # Add newer kernel check results if available | |
| if [ -n "$newer_kernel_results" ] && [ "${#newer_kernel_results[@]}" -gt 0 ]; then | |
| echo "Status in newer kernel trees:" | |
| printf '%s\n' "${newer_kernel_results[@]}" | |
| echo | |
| fi | |
| # Add fixes and revert information if available | |
| if [ -n "$fixes_results" ]; then | |
| echo "$fixes_results" | |
| echo | |
| fi | |
| if [ -n "$revert_results" ]; then | |
| echo "$revert_results" | |
| echo | |
| fi | |
| # Add diff if there are differences and we have a valid SHA1 | |
| if [ -n "$diff_output" ] && [[ "$found_sha1" =~ ^[0-9a-f]{40}$ ]] && \ | |
| [ "$found_sha1" != "0000000000000000000000000000000000000000" ]; then | |
| # Check if this is just a failure message rather than actual diff | |
| if [[ "$diff_output" == *"Couldn't generate comparison"* ]]; then | |
| echo "Note: Could not generate a diff with upstream commit:" | |
| else | |
| echo "Note: The patch differs from the upstream commit:" | |
| fi | |
| echo "---" | |
| echo "$diff_output" | |
| echo "---" | |
| echo | |
| fi | |
| # Print results table | |
| if [ $is_series_part -eq 1 ] && [ "$current_part" -gt 1 ] && [ $has_issues -eq 1 ]; then | |
| echo "NOTE: These results are for this patch alone. Full series testing will be" | |
| echo "performed when all parts are received." | |
| echo | |
| fi | |
| echo "Results of testing on various branches:" | |
| echo | |
| printf "| %-25s | %-11s | %-10s |\n" "Branch" "Patch Apply" "Build Test" | |
| echo "|---------------------------|-------------|------------|" | |
| while IFS='|' read -r branch status build; do | |
| if [ -n "$branch" ]; then | |
| printf "| %-25s | %-11s | %-10s |\n" "$branch" "$status" "$build" | |
| fi | |
| done <<< "$results" | |
| # Add build errors if any | |
| if [ -n "$build_errors" ]; then | |
| echo | |
| echo "Build Errors:" | |
| echo "$build_errors" | |
| fi | |
| } > "$response_file" | |
| echo "Response written to $response_file" | |
| } | |
| # Cleanup function to remove any remaining worktrees | |
| cleanup() { | |
| if [ -d "$LINUX_DIR" ]; then | |
| cd "$LINUX_DIR" | |
| # Remove any worktrees we created | |
| if [ -d "$WORKTREE_DIR" ]; then | |
| for worktree in "$WORKTREE_DIR"/*; do | |
| if [ -d "$worktree" ]; then | |
| # Try to cleanup any git operations first | |
| if [ -d "$worktree/.git" ]; then | |
| cd "$worktree" 2>/dev/null | |
| if [ $? -eq 0 ]; then | |
| # Abort any in-progress git operations | |
| git am --abort >/dev/null 2>&1 | |
| git rebase --abort >/dev/null 2>&1 | |
| git reset --hard >/dev/null 2>&1 | |
| cd "$LINUX_DIR" | |
| fi | |
| fi | |
| # Now try to remove via git | |
| if ! git worktree remove --force "$worktree" >/dev/null 2>&1; then | |
| # If that fails, use rm -rf | |
| rm -rf "$worktree" | |
| fi | |
| fi | |
| done | |
| # Try to remove the directory | |
| rm -rf "$WORKTREE_DIR" 2>/dev/null || true | |
| # Prune any worktree references | |
| git worktree prune >/dev/null 2>&1 | |
| fi | |
| # Clean up any temp patch file | |
| rm -f "$TEMP_PATCH" 2>/dev/null || true | |
| fi | |
| } | |
| # Main script | |
| main() { | |
| if [ $# -ne 1 ]; then | |
| echo "Usage: $0 <mbox_file>" | |
| exit 1 | |
| fi | |
| MBOX_FILE="$1" | |
| local diff_output="" | |
| local failed=0 | |
| echo "Looking at $MBOX_FILE" | |
| # Validate inputs | |
| if [ ! -f "$MBOX_FILE" ]; then | |
| echo "Error: File '$MBOX_FILE' not found" | |
| exit 1 | |
| fi | |
| if [ ! -d "$LINUX_DIR" ]; then | |
| echo "Error: Linux git tree not found at $LINUX_DIR" | |
| exit 1 | |
| fi | |
| if [ ! -f "$HOME/stable-queue/active_kernel_versions" ]; then | |
| echo "Error: Active kernel versions file not found at ~/stable-queue/active_kernel_versions" | |
| exit 1 | |
| fi | |
| # Check if we should ignore this mail | |
| if should_ignore_mail "$MBOX_FILE"; then | |
| echo "Skipping mail from ignored author" | |
| exit 0 | |
| fi | |
| # Check if mail contains a git patch | |
| if ! is_git_patch "$MBOX_FILE"; then | |
| echo "Skipping mail: not a git patch" | |
| exit 0 | |
| fi | |
| # Extract subject and series information | |
| subject=$(formail -xSubject: < "$MBOX_FILE") | |
| if [ -z "$subject" ]; then | |
| echo "Error: Could not extract subject from mbox" | |
| exit 1 | |
| fi | |
| # Get kernel versions to process | |
| kernel_versions=$(extract_kernel_versions "$subject") | |
| local has_specific_versions=0 | |
| if [ "$kernel_versions" != "$(cat $HOME/stable-queue/active_kernel_versions)" ]; then | |
| has_specific_versions=1 | |
| fi | |
| # Extract email body | |
| email_body=$(formail -I "" < "$MBOX_FILE") | |
| # Try to find SHA1 in the email body | |
| claimed_sha1=$(extract_commit_sha1 "$email_body" || true) | |
| found_sha1="" | |
| # If we didn't find SHA1 in the body, try to find it by subject | |
| if [ -z "$claimed_sha1" ]; then | |
| found_sha1=$(find_commit_by_subject "$subject" "$LINUX_DIR") | |
| else | |
| if validate_commit "$claimed_sha1" "$LINUX_DIR"; then | |
| found_sha1="$claimed_sha1" | |
| else | |
| found_sha1=$(find_commit_by_subject "$subject" "$LINUX_DIR") | |
| fi | |
| fi | |
| # Skip if we have no SHA1 and no specific kernel versions | |
| if [ -z "$found_sha1" ] && [ $has_specific_versions -eq 0 ]; then | |
| echo "No commit SHA1 found and no specific kernel versions in subject. Skipping patch." | |
| exit 0 | |
| fi | |
| # Check if this is part of a series | |
| series_info=$(extract_series_info "$subject") | |
| if [ -n "$series_info" ]; then | |
| read current_part total_parts <<< "$series_info" | |
| # Skip 0/N patches | |
| if [ "$current_part" -eq 0 ]; then | |
| echo "Skipping 0/$total_parts patch" | |
| exit 0 | |
| fi | |
| # Get message IDs and determine series directory | |
| message_id=$(get_message_id "$MBOX_FILE") | |
| in_reply_to=$(get_in_reply_to "$MBOX_FILE") | |
| series_dir="$PENDING_DIR/$(get_series_dir "$message_id" "$in_reply_to")" | |
| # Store this patch | |
| store_patch "$MBOX_FILE" "$series_dir" "$current_part" | |
| echo "Processing part $current_part of $total_parts" | |
| # Check if series is now complete | |
| if is_series_complete "$series_dir" "$total_parts"; then | |
| echo "Series complete, testing all patches..." | |
| if ! test_series "$series_dir" "$total_parts" "$LINUX_DIR"; then | |
| echo "Series testing failed. See logs for details." | |
| failed=1 | |
| else | |
| echo "Series testing completed successfully." | |
| fi | |
| # Clean up series directory regardless of result | |
| rm -rf "$series_dir" | |
| exit $failed | |
| else | |
| echo "Series incomplete, waiting for remaining patches..." | |
| echo "Processed part $current_part of $total_parts" | |
| exit 0 | |
| fi | |
| else | |
| # Single patch processing | |
| declare -a patch_results=() | |
| declare -a patch_errors=() | |
| if ! process_patch "$MBOX_FILE" "" "1" patch_results patch_errors; then | |
| failed=1 | |
| fi | |
| fi | |
| exit $failed | |
| } | |
| # Set up trap for cleanup | |
| trap cleanup EXIT ERR | |
| # Run main script | |
| main "$@" |