diff --git a/.github/workflows/sunnypilot-master-dev-c3-prep.yaml b/.github/workflows/sunnypilot-master-dev-c3-prep.yaml index 0a6a76099..f6b749742 100644 --- a/.github/workflows/sunnypilot-master-dev-c3-prep.yaml +++ b/.github/workflows/sunnypilot-master-dev-c3-prep.yaml @@ -97,6 +97,17 @@ jobs: headRefName title createdAt + labels(last:10) { + nodes { + name + } + } + headRepository { + name + nameWithOwner + url + isFork + } commits(last: 1) { nodes { commit { diff --git a/opendbc_repo b/opendbc_repo index f3378b8a0..5d609a919 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit f3378b8a04b809fe342bfe1ccde844058b229dbb +Subproject commit 5d609a91993d96fc47f169b41b8b31b604b9bddf diff --git a/release/ci/squash_and_merge_prs.py b/release/ci/squash_and_merge_prs.py index 5cd1b9574..6d8f70f34 100755 --- a/release/ci/squash_and_merge_prs.py +++ b/release/ci/squash_and_merge_prs.py @@ -7,6 +7,7 @@ import argparse import json from datetime import datetime +TRUST_FORK_LABEL = "trust-fork-pr" def setup_argument_parser(): parser = argparse.ArgumentParser(description='Process and squash GitHub PRs') @@ -37,43 +38,25 @@ def sort_prs_by_creation(pr_data): ) -def add_pr_comment(pr_number, comment): +def add_pr_comments(pr_number, comments: list[str]): + """Adds or updates a comment with multiple comments to a PR using gh cli""" + comment = "\n___\n".join(comments) + _add_pr_comment(pr_number, comment) + + +def _add_pr_comment(pr_number, comment): """Add or update a comment to a PR using gh cli""" title = "## Squash and Merge" try: - result = subprocess.run( - ['gh', 'pr', 'view', str(pr_number), '--json', 'comments'], + full_comment = f"{title}\n\n{comment}" + subprocess.run( + ['gh', 'pr', 'comment', '--edit-last', '--create-if-none', f"#{pr_number}", '--body', full_comment], check=True, capture_output=True, text=True ) - comments_data = json.loads(result.stdout) - has_existing_comment = False - - for pr_comment in comments_data['comments']: - if pr_comment['body'].startswith(title): - has_existing_comment = True - break - - full_comment = f"{title}\n\n{comment}" - - if has_existing_comment: - subprocess.run( - ['gh', 'pr', 'comment', '--edit-last', f"#{pr_number}", '--body', full_comment], - check=True, - capture_output=True, - text=True - ) - else: - subprocess.run( - ['gh', 'pr', 'comment', f"#{pr_number}", '--body', full_comment], - check=True, - capture_output=True, - text=True - ) - except subprocess.CalledProcessError as e: print(f"Failed to add/update comment on PR #{pr_number}: {e.stderr}") except json.JSONDecodeError: @@ -104,8 +87,8 @@ def validate_pr(pr): if not merge_data.get('mergeable'): return False, "merge conflicts detected" - if (mergeStateStatus := merge_data.get('mergeStateStatus')) == "BEHIND": - return False, f"branch is `{mergeStateStatus}`" + # if (mergeStateStatus := merge_data.get('mergeStateStatus')) == "BEHIND": + # return False, f"branch is `{mergeStateStatus}`" return True, None @@ -122,24 +105,43 @@ def process_pr(pr_data, source_branch, target_branch, squash_script_path): subprocess.run(['git', 'branch', target_branch, f'origin/{source_branch}'], check=True) success_count = 0 for pr in nodes: - pr_number = pr.get('number', 'UNKNOWN') - branch = pr.get('headRefName', '') - title = pr.get('title', '') - is_valid, skip_reason = validate_pr(pr) - - if not is_valid: - print(f"Warning: {skip_reason} for PR #{pr_number}, skipping") - add_pr_comment(pr_number, - f"⚠️ This PR was skipped in the automated `{target_branch}` squash because **{skip_reason}**.") - continue - + pr_comments = [] try: + pr_number = pr.get('number', 'UNKNOWN') + branch = pr.get('headRefName', '') + title = pr.get('title', '') + head_repository = pr.get('headRepository', {}) + pr_labels = pr.get('labels', {}).get('nodes', []) + is_fork = head_repository.get('isFork', False) + trust_fork = any(label.get('name') == TRUST_FORK_LABEL for label in pr_labels) + is_valid, skip_reason = validate_pr(pr) + origin = "origin" if not head_repository.get('isFork', False) else head_repository.get('nameWithOwner', 'origin') + + if is_fork and trust_fork: + print(f"Removing label `{TRUST_FORK_LABEL}` from PR #{pr_number} as it is being processed") + subprocess.run(['gh', 'pr', 'edit', str(pr_number), '--remove-label', TRUST_FORK_LABEL], check=True) + pr_comments.append(f"ℹ️️ This PR is from a fork. The `{TRUST_FORK_LABEL}` label was removed as it is being processed right now.") + print(f"Adding remote {origin} for PR #{pr_number}") + subprocess.run(['git', 'remote', 'add', origin, head_repository.get('url')], check=False) + + if is_fork and not trust_fork: + pr_comments.append( + f"⚠️ This PR is from a fork. Please add the `{TRUST_FORK_LABEL}` label to include it in the squash." + + "\n**Note**: The label is removed after the squash is done and must be added again for the next execution for security reasons." + ) + continue + + if not is_valid: + print(f"Warning: {skip_reason} for PR #{pr_number}, skipping") + pr_comments.append(f"⚠️ This PR was skipped in the automated `{target_branch}` squash because **{skip_reason}**.") + continue + # Fetch PR branch - subprocess.run(['git', 'fetch', 'origin', branch], check=True) + subprocess.run(['git', 'fetch', origin, branch], check=True) # Delete branch if it exists (ignore errors if it doesn't) subprocess.run(['git', 'branch', '-D', branch], check=False) # Create new branch pointing to origin's branch - subprocess.run(['git', 'branch', branch, f'origin/{branch}'], check=True) + subprocess.run(['git', 'branch', branch, f'{origin}/{branch}'], check=True) # Run squash script result = subprocess.run([ @@ -160,13 +162,17 @@ def process_pr(pr_data, source_branch, target_branch, squash_script_path): print(f"Command failed with exit code {result.returncode}") output = result.stdout print(f"Error output: {output}") - add_pr_comment(pr_number, f"⚠️ Error during automated `{target_branch}` squash:\n```\n{output}\n```") + pr_comments.append(f"⚠️ Error during automated `{target_branch}` squash:\n```\n{output}\n```") subprocess.run(['git', 'reset', '--hard'], check=True) continue except Exception as e: print(f"Unexpected error processing PR #{pr_number}: {str(e)}") + pr_comments.append(f"⚠️ Unexpected error during automated `{target_branch}` squash:\n```\n{str(e)}\n```") subprocess.run(['git', 'reset', '--hard'], check=True) continue + finally: + if pr_comments: + add_pr_comments(pr_number, pr_comments) # This "commits" all the comments generated on this run before leaving loop on continue. return success_count diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index f137b406b..d0d28ebac 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -10,7 +10,8 @@ def dmonitoringd_thread(): params = Params() pm = messaging.PubMaster(['driverMonitoringState']) - sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2') + sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2', + 'selfdriveStateSP'], poll='driverStateV2') DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 337be3a2a..76a07b079 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -403,13 +403,13 @@ class DriverMonitoring: driver_state=sm['driverStateV2'], cal_rpy=sm['liveCalibration'].rpyCalib, car_speed=sm['carState'].vEgo, - op_engaged=sm['selfdriveState'].enabled + op_engaged=sm['selfdriveState'].enabled or sm['selfdriveStateSP'].mads.enabled ) # Update distraction events self._update_events( driver_engaged=sm['carState'].steeringPressed or sm['carState'].gasPressed, - op_engaged=sm['selfdriveState'].enabled, + op_engaged=sm['selfdriveState'].enabled or sm['selfdriveStateSP'].mads.enabled, standstill=sm['carState'].standstill, wrong_gear=sm['carState'].gearShifter in [car.CarState.GearShifter.reverse, car.CarState.GearShifter.park], car_speed=sm['carState'].vEgo diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index d9aa54aa8..3e3e47990 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -122,7 +122,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://docs.sunnypilot.ai/driving_models.json" + MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v2.json" def __init__(self, params: Params): self.params = params diff --git a/sunnypilot/sunnylink/backups/manager.py b/sunnypilot/sunnylink/backups/manager.py index 7f3eddeaa..d6408d7ba 100644 --- a/sunnypilot/sunnylink/backups/manager.py +++ b/sunnypilot/sunnylink/backups/manager.py @@ -210,12 +210,25 @@ class BackupManagerSP: def _get_current_version(self) -> custom.BackupManagerSP.Version: """Gets current sunnypilot version information.""" version_obj = custom.BackupManagerSP.Version() - version_parts = get_version().split('.') - version_obj.major = int(version_parts[0]) if len(version_parts) > 0 else 0 - version_obj.minor = int(version_parts[1]) if len(version_parts) > 1 else 0 - version_obj.patch = int(version_parts[2]) if len(version_parts) > 2 else 0 - version_obj.build = int(version_parts[3]) if len(version_parts) > 3 else 0 + version_str = get_version() + + version_parts = version_str.split('-') # For when version is like "1.2.3-456" + version_nums = version_parts[0].split('.') + + # Extract build number from hyphen format or as 4th version component + build = 0 + if len(version_parts) > 1 and version_parts[1].isdigit(): + build = int(version_parts[1]) + elif len(version_nums) > 3 and version_nums[3].isdigit(): + build = int(version_nums[3]) + + # Set version components with safer defaults + version_obj.major = int(version_nums[0]) if len(version_nums) > 0 and version_nums[0].isdigit() else 0 + version_obj.minor = int(version_nums[1]) if len(version_nums) > 1 and version_nums[1].isdigit() else 0 + version_obj.patch = int(version_nums[2]) if len(version_nums) > 2 and version_nums[2].isdigit() else 0 + version_obj.build = build version_obj.branch = get_branch() + return version_obj async def main_thread(self) -> None: