From cc977ce7c2e7890268c00a397d2445f7325165d2 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:06:35 -0700 Subject: [PATCH 01/15] ci: add org-wide defaults and reusable conventional-pr workflow - pull_request_template.md + PULL_REQUEST_TEMPLATE/ (internal/external): canonical PR templates inherited by every org repo without local copies - ISSUE_TEMPLATE/config.yml: disables blank issues and routes the new-issue chooser to the server repository - .github/workflows/conventional-pr.yml: reusable workflow validating PR titles against Conventional Commits and labeling the PR with its type TRI-1100 --- .github/workflows/conventional-pr.yml | 86 +++++++++++++++++++ ISSUE_TEMPLATE/config.yml | 5 ++ .../pull_request_template_external_contrib.md | 36 ++++++++ .../pull_request_template_internal_contrib.md | 36 ++++++++ pull_request_template.md | 13 +++ 5 files changed, 176 insertions(+) create mode 100644 .github/workflows/conventional-pr.yml create mode 100644 ISSUE_TEMPLATE/config.yml create mode 100644 PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md create mode 100644 PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md create mode 100644 pull_request_template.md diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml new file mode 100644 index 0000000..00eaa0b --- /dev/null +++ b/.github/workflows/conventional-pr.yml @@ -0,0 +1,86 @@ +# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Reusable workflow: validates the PR title against the Conventional Commits +# format (: ) and labels the PR with its commit type. +# Call from a repo with: +# +# jobs: +# conventional-pr: +# uses: triton-inference-server/.github/.github/workflows/conventional-pr.yml@v1.0.0 +# +# The caller must grant `permissions: pull-requests: write`. + +name: conventional-pr + +on: + workflow_call: + +jobs: + validate-and-label: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Validate PR title and apply type label + uses: actions/github-script@v7 + with: + script: | + const types = ['build', 'chore', 'ci', 'docs', 'feat', 'fix', + 'perf', 'refactor', 'revert', 'style', 'test']; + const title = context.payload.pull_request.title; + const match = title.match(/^(\w+)(\([^)]*\))?!?: .+/); + + if (!match || !types.includes(match[1])) { + core.setFailed( + `PR title "${title}" does not follow the Conventional Commits ` + + `format "<type>: <Title>" with type one of: ${types.join(', ')}. ` + + 'See https://www.conventionalcommits.org/'); + return; + } + const type = match[1]; + + // Ensure the label exists, then apply it (idempotent). Remove any + // stale type label left from a previous title. + const { owner, repo } = context.repo; + const number = context.payload.pull_request.number; + try { + await github.rest.issues.createLabel( + { owner, repo, name: type, color: 'ededed' }); + } catch (e) { + if (e.status !== 422) throw e; // 422 = already exists + } + const { data: current } = await github.rest.issues.listLabelsOnIssue( + { owner, repo, issue_number: number }); + for (const l of current) { + if (types.includes(l.name) && l.name !== type) { + await github.rest.issues.removeLabel( + { owner, repo, issue_number: number, name: l.name }); + } + } + await github.rest.issues.addLabels( + { owner, repo, issue_number: number, labels: [type] }); + core.info(`PR title OK; labeled as "${type}".`); diff --git a/ISSUE_TEMPLATE/config.yml b/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..1fb3f37 --- /dev/null +++ b/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Bug report or feature request + url: https://github.com/triton-inference-server/server/issues/new/choose + about: Please report issues for all Triton Inference Server components in the server repository. diff --git a/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md new file mode 100644 index 0000000..4a9e6b6 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md @@ -0,0 +1,36 @@ +#### What does the PR do? +<!-- Describe your pull request here. Please read the text below the line, and make sure you follow the checklist.--> + +#### Checklist +- [ ] I have read the [Contribution guidelines](#../../CONTRIBUTING.md) and signed the [Contributor License +Agreement](https://github.com/NVIDIA/triton-inference-server/blob/master/Triton-CCLA-v1.pdf) +- [ ] PR title reflects the change and is of format `<commit_type>: <Title>` +- [ ] Changes are described in the pull request. +- [ ] Related issues are referenced. +- [ ] Populated [github labels](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) field +- [ ] Added [test plan](#test-plan) and verified test passes. +- [ ] Verified that the PR passes existing CI. +- [ ] I ran pre-commit locally (`pre-commit install, pre-commit run --all`) +- [ ] Verified copyright is correct on all changed files. +- [ ] Added _succinct_ git squash message before merging [ref](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +- [ ] All template sections are filled out. +- [ ] Optional: Additional screenshots for behavior/output changes with before/after. + +#### Related PRs: +<!-- Related PRs from other Repositories --> + +#### Where should the reviewer start? +<!-- call out specific files that should be looked at closely --> + +#### Test plan: +<!-- list steps to verify feature works --> +<!-- were e2e tests added?--> + +#### Caveats: +<!-- any limitations or possible things missing from this PR --> + +#### Background +<!-- e.g. what led to this change being made. this is optional extra information to help the reviewer --> + +#### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to) +- closes GitHub issue: #xxx diff --git a/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md b/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md new file mode 100644 index 0000000..42b1bb9 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md @@ -0,0 +1,36 @@ +#### What does the PR do? +<!-- Describe your pull request here. Please read the text below the line, and make sure you follow the checklist.--> + +#### Checklist +- [ ] PR title reflects the change and is of format `<commit_type>: <Title>` +- [ ] Changes are described in the pull request. +- [ ] Related issues are referenced. +- [ ] Populated [github labels](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels) field +- [ ] Added [test plan](#test-plan) and verified test passes. +- [ ] Verified that the PR passes existing CI. +- [ ] Verified copyright is correct on all changed files. +- [ ] Added _succinct_ git squash message before merging [ref](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +- [ ] All template sections are filled out. +- [ ] Optional: Additional screenshots for behavior/output changes with before/after. + +#### Related PRs: +<!-- Related PRs from other Repositories --> + +#### Where should the reviewer start? +<!-- call out specific files that should be looked at closely --> + +#### Test plan: +<!-- list steps to verify --> +<!-- were e2e tests added?--> + +- CI Pipeline ID: +<!-- Only Pipeline ID and no direct link here --> + +#### Caveats: +<!-- any limitations or possible things missing from this PR --> + +#### Background +<!-- e.g. what led to this change being made. this is optional extra information to help the reviewer --> + +#### Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to) +- closes GitHub issue: #xxx diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 0000000..0787dcb --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,13 @@ +Thanks for submitting a PR to Triton! +Please go the the `Preview` tab above this description box and select the appropriate sub-template: + +* [PR description template for Triton Engineers](?expand=1&template=pull_request_template_internal_contrib.md) +* [PR description template for External Contributors](?expand=1&template=pull_request_template_external_contrib.md) + +If you already created the PR, please replace this message with one of +* [External contribution template](https://raw.githubusercontent.com/triton-inference-server/server/main/.github/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md) +* [Internal contribution template](https://raw.githubusercontent.com/triton-inference-server/server/main/.github/PULL_REQUEST_TEMPLATE/pull_request_template_internal_contrib.md) + +and fill it out. + + From de133b05c5ef6b00bc734b4135387c8dbabf3a9e Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:41:14 -0700 Subject: [PATCH 02/15] ci: consolidate centralized hooks into this repository Migrate the add-license pre-commit hook (hardened: .github/ excluded, LICENSE files never rewritten) from developer_tools into this org-wide defaults repository, so hooks, PR/issue templates, and reusable workflows live under one roof with a single tag line. Add this repo's own pre-commit baseline + CI and a self-check caller for the reusable conventional-pr workflow. Consumers reference: - repo: https://github.com/triton-inference-server/.github rev: v1.0.0 hooks: - id: add-license TRI-1100 --- .github/workflows/conventional-pr-check.yml | 40 ++ .github/workflows/pre-commit.yml | 45 +++ .pre-commit-config.yaml | 88 +++++ .pre-commit-hooks.yaml | 36 ++ pyproject.toml | 48 +++ tools/add_copyright.py | 407 ++++++++++++++++++++ 6 files changed, 664 insertions(+) create mode 100644 .github/workflows/conventional-pr-check.yml create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .pre-commit-config.yaml create mode 100644 .pre-commit-hooks.yaml create mode 100644 pyproject.toml create mode 100755 tools/add_copyright.py diff --git a/.github/workflows/conventional-pr-check.yml b/.github/workflows/conventional-pr-check.yml new file mode 100644 index 0000000..2509787 --- /dev/null +++ b/.github/workflows/conventional-pr-check.yml @@ -0,0 +1,40 @@ +# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Self-check: run this repository's own reusable conventional-pr workflow +# on its own pull requests (local path reference uses the PR's version). + +name: conventional-pr-check + +on: + pull_request: + types: [opened, edited, synchronize, reopened] + +jobs: + conventional-pr: + permissions: + pull-requests: write + uses: ./.github/workflows/conventional-pr.yml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..15d0b68 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,45 @@ +# Copyright 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +name: pre-commit + +on: + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5.0.0 + with: + fetch-depth: 2 + - name: Get modified files + id: modified-files + run: echo "modified_files=$(git diff --name-only -r HEAD^1 HEAD | xargs)" >> $GITHUB_OUTPUT + - uses: actions/setup-python@v6.0.0 + - uses: pre-commit/action@v3.0.1 + with: + extra_args: --files ${{ steps.modified-files.outputs.modified_files }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0b767fc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,88 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +default_install_hook_types: [pre-commit, commit-msg] + +repos: +- repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + additional_dependencies: [toml] +- repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + types_or: [python, cython] +- repo: https://github.com/PyCQA/flake8 + rev: 7.3.0 + hooks: + - id: flake8 + args: [--max-line-length=88, --select=C,E,F,W,B,B950, --extend-ignore = E203,E501] + types_or: [python, cython] +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v16.0.5 + hooks: + - id: clang-format + types_or: [c, c++, cuda, proto, textproto, java] + args: ["-fallback-style=none", "-style=file", "-i"] +- repo: https://github.com/codespell-project/codespell + rev: v2.2.4 + hooks: + - id: codespell + additional_dependencies: [tomli] + args: ["--toml", "pyproject.toml"] + exclude: (?x)^(.*stemmer.*|.*stop_words.*|^CHANGELOG.md$) +# Validates commit messages against the Conventional Commits format +# (<commit_type>: <title>). Replaces the manual commit-type checklist that +# used to live in the PR template. +- repo: https://github.com/compilerla/conventional-pre-commit + rev: v4.4.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg] + args: [build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test] +# More details about these pre-commit hooks here: +# https://pre-commit.com/hooks.html +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-json + - id: check-toml + - id: check-yaml + - id: check-shebang-scripts-are-executable + - id: end-of-file-fixer + types_or: [c, c++, cuda, proto, textproto, java, python] + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: trailing-whitespace + +# NOTE: the add-license hook defined by this repository (.pre-commit-hooks.yaml) +# is exported for consumer repositories; it is not enabled here because this +# repository has no LICENSE file. diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..fef4d8a --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,36 @@ +# Copyright 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +- id: add-license + name: Add License + entry: tools/add_copyright.py + language: script + stages: [pre-commit] + verbose: true + require_serial: true + # GitHub issue/PR templates must start with YAML frontmatter; keep + # license headers out of them. + exclude: ^\.github/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e4c246b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,48 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +[tool.codespell] +# note: pre-commit passes explicit lists of files here, which this skip file list doesn't override - +# this is only to allow you to run codespell interactively +skip = "./.git,./.github" +# ignore short words, and typename parameters like OffsetT +ignore-regex = "\\b(.{1,4}|[A-Z]\\w*T)\\b" +# use the 'clear' dictionary for unambiguous spelling mistakes +builtin = "clear" +# disable warnings about binary files and wrong encoding +quiet-level = 3 + +[tool.isort] +profile = "black" +use_parentheses = true +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +ensure_newline_before_comments = true +line_length = 88 +balanced_wrapping = true +indent = " " +skip = ["build"] diff --git a/tools/add_copyright.py b/tools/add_copyright.py new file mode 100755 index 0000000..c58fd81 --- /dev/null +++ b/tools/add_copyright.py @@ -0,0 +1,407 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import argparse +import os +import re +import subprocess +import sys +from datetime import datetime +from typing import Callable, Dict, Optional, Sequence + +current_year = str(datetime.now().year) + +COPYRIGHT_YEAR_PAT = re.compile( + r"Copyright( \(c\))? (\d{4})?-?(\d{4}), NVIDIA CORPORATION" +) + +LICENSE_TEXT = "" + + +def get_repo_root() -> str: + try: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + except (FileNotFoundError, subprocess.CalledProcessError): + return os.getcwd() + + +def get_license_path() -> str: + return os.path.join(get_repo_root(), "LICENSE") + + +def has_copyright(content: str) -> bool: + return COPYRIGHT_YEAR_PAT.search(content) + + +def update_copyright_year( + path: str, content: Optional[str] = None, disallow_range: bool = False +) -> str: + """ + Updates the copyright year in the provided file. + If the copyright is not present in the file, this function has no effect. + """ + if content is None: + with open(path, "r") as f: + content = f.read() + + match = COPYRIGHT_YEAR_PAT.search(content) + min_year = match.groups()[1] or match.groups()[2] + + new_copyright = f"Copyright{match.groups()[0] or ''} " + if min_year < current_year and not disallow_range: + new_copyright += f"{min_year}-{current_year}" + else: + new_copyright += f"{current_year}" + new_copyright += ", NVIDIA CORPORATION" + + updated_content = COPYRIGHT_YEAR_PAT.sub(new_copyright, content) + + if content != updated_content: + with open(path, "w") as f: + f.write(updated_content) + + +def get_license(license_path: Optional[str] = None) -> str: + """ + Returns the contents of the LICENSE file. + + Note: The LICENSE file itself is never modified by this hook; its + copyright year is maintained manually by each repository. + """ + license_path = license_path or get_license_path() + + with open(license_path, "r") as license_file: + return license_file.read() + + +def load_license_text() -> None: + global LICENSE_TEXT + LICENSE_TEXT = get_license() + + +# +# Header manipulation helpers +# + + +def prefix_lines(content: str, prefix: str) -> str: + # NOTE: This could have been done via `textwrap.indent`, but we're not actually indenting, + # so it seems semantically wrong to do that. + return prefix + f"\n{prefix}".join(content.splitlines()) + + +def insert_after(regex: str) -> Callable[[str, str], str]: + """ + Builds a callback that will insert a provided header after + the specified regular expression. If the expression is not + found in the file contents, the header will be inserted at the + beginning of the file. + + Args: + regex: The regular expression to match. + + Returns: + A callable that can be used as the `add_header` argument to `update_or_add_header`. + """ + + def add_header(header: str, content: str) -> str: + match = re.match(regex, content) + + if match is None: + return header + "\n" + content + + insertion_point = match.span()[-1] + + return content[:insertion_point] + f"{header}\n" + content[insertion_point:] + + return add_header + + +def update_or_add_header( + path: str, header: str, add_header: Optional[Callable[[str, str], str]] = None +): + """ + Updates in place or adds a new copyright header to the specified file. + + Args: + path: The path of the file. + header: The contents of the copyright header. + add_header: A callback that receives the copyright header and file contents and + controls how the contents of the file are updated. By default, the copyright + header is prepended to the file. + """ + with open(path, "r") as f: + content = f.read() + + if has_copyright(content): + update_copyright_year(path, content) + return + + add_header = add_header or (lambda header, content: header + "\n" + content) + + content = add_header(header, content) + + # As a sanity check, make sure we didn't accidentally add the copyright header + # twice, or add a new header when one was already present. + if content.count("Copyright (c)") != 1: + print( + f"WARNING: Something went wrong while processing: {path}!\n" + "Please check if the copyright header was included twice or wasn't added at all. " + ) + + with open(path, "w") as f: + f.write(content) + + +# Each file type requires slightly different handling when inserting the copyright +# header. For example, for C++ files, the header must be prefixed with `//` and for +# shell scripts, it must be prefixed with `#` and must be inserted *after* the shebang. +# +# This mapping stores callables that return whether a handler wants to process a specified +# file based on the path along with callables that will accept the file path and update +# it with the copyright header. +FILE_TYPE_HANDLERS: Dict[Callable[[str], bool], Callable[[str], None]] = {} + + +# +# Path matching callables +# These allow registered functions to more easily specify what kinds of +# paths they should be applied to. +# +def has_ext(exts: Sequence[str]): + def has_ext_impl(path: str): + _, ext = os.path.splitext(path) + return ext in exts + + return has_ext_impl + + +def basename_is(expected_path: str): + return lambda path: os.path.basename(path) == expected_path + + +def path_contains(expected: str): + return lambda path: expected in path + + +def any_of(*funcs: Sequence[Callable[[str], bool]]): + return lambda path: any(func(path) for func in funcs) + + +# +# File handlers for different types of files. +# Many types of files require very similar handling - those are combined where possible. +# + + +def register(match: Callable[[str], bool]): + def register_impl(func): + FILE_TYPE_HANDLERS[match] = func + return func + + return register_impl + + +@register( + any_of( + has_ext([".py", ".pyi", ".sh", ".bash", ".yaml", ".pbtxt"]), + basename_is("CMakeLists.txt"), + path_contains("Dockerfile"), + ) +) +def py_or_shell_like(path): + update_or_add_header( + path, + prefix_lines(LICENSE_TEXT, "# "), + # Insert the header *after* the shebang. + # NOTE: This could break if there is a shebang-like pattern elsewhere in the file. + # In that case, this could be edited to check only the first line of the file (after removing whitespace). + insert_after(r"#!(.*)\n"), + ) + + +@register(has_ext([".cc", ".h"])) +def cpp(path): + update_or_add_header(path, prefix_lines(LICENSE_TEXT, "// ")) + + +@register(has_ext([".tpl"])) +def tpl(path): + update_or_add_header(path, "{{/*\n" + prefix_lines(LICENSE_TEXT, "# ") + "\n*/}}") + + +@register(has_ext([".html", ".md"])) +def html_md(path): + update_or_add_header(path, "<!--\n" + prefix_lines(LICENSE_TEXT, "# ") + "\n-->") + + +@register(has_ext([".rst"])) +def rst(path): + update_or_add_header(path, prefix_lines(LICENSE_TEXT, ".. ")) + + +def add_copyrights(paths): + load_license_text() + + for path in paths: + # Special case: LICENSE file only needs year update + if os.path.basename(path) == "LICENSE": + update_copyright_year(path) + continue + + for match, handler in FILE_TYPE_HANDLERS.items(): + if match(path): + handler(path) + break + else: + print( + f"WARNING: No handler registered for file: {path}. Please add a new handler to {__file__}!" + ) + + # Don't automatically 'git add' changes for now, make it more clear which + # files were changed and have ability to see 'git diff' on them. + # Note that this means the hook will modify files and then cancel the commit, which you will then + # have to manually make again. + # subprocess.run(["git", "add"] + paths) + + print(f"Processed copyright headers for {len(paths)} file(s).") + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Adds copyright headers to source files" + ) + parser.add_argument("files", nargs="*") + + args, _ = parser.parse_known_args() + + license_path = get_license_path() + if not os.path.isfile(license_path): + print(f"ERROR: LICENSE file not found at {license_path}", file=sys.stderr) + return 1 + + add_copyrights(args.files) + return 0 + + +if __name__ == "__main__": + # sys.exit is important here to avoid the test-related imports below during normal execution. + sys.exit(main()) + + +# +# Integration Tests +# +import tempfile + +import pytest + + +# Processes provided text through the copyright hook by writing it to a temporary file. +def process_text(content, extension): + with tempfile.NamedTemporaryFile("w+", suffix=extension) as f: + f.write(content) + f.flush() + + add_copyrights([f.name]) + + f.seek(0) + return f.read() + + +# We use this slightly weird hack to make sure the copyright hook does not do a text replacement +# of the parameters in the test, since they look exactly like copyright headers. +def make_copyright_text(text): + return f"Copyright {text}" + + +@pytest.mark.parametrize( + "content, expected", + [ + # Convert to range if the year that's already present is older than the current year. + ( + make_copyright_text("(c) 2018, NVIDIA CORPORATION"), + make_copyright_text(f"(c) 2018-{current_year}, NVIDIA CORPORATION"), + ), + ( + make_copyright_text("2018, NVIDIA CORPORATION"), + make_copyright_text(f"2018-{current_year}, NVIDIA CORPORATION"), + ), + # No effect if the year is current: + ( + make_copyright_text(f"(c) {current_year}, NVIDIA CORPORATION"), + make_copyright_text(f"(c) {current_year}, NVIDIA CORPORATION"), + ), + ( + make_copyright_text(f"{current_year}, NVIDIA CORPORATION"), + make_copyright_text(f"{current_year}, NVIDIA CORPORATION"), + ), + # If there is already a range, update the upper bound of the range: + ( + make_copyright_text("(c) 2018-2023, NVIDIA CORPORATION"), + make_copyright_text(f"(c) 2018-{current_year}, NVIDIA CORPORATION"), + ), + ], +) +def test_copyright_update(content, expected): + # We don't really care about the extension here - just needs to be something the hook will recognize. + assert process_text(content, ".py") == expected + + +@pytest.mark.parametrize( + "content, extension, expected", + [ + ("", ".cc", f"// {make_copyright_text(f'(c) {current_year}')}"), + ("", ".h", f"// {make_copyright_text(f'(c) {current_year}')}"), + ("", ".py", f"# {make_copyright_text(f'(c) {current_year}')}"), + ("", ".sh", f"# {make_copyright_text(f'(c) {current_year}')}"), + # Make sure copyright comes after shebangs + ( + "#!/bin/python\n", + ".py", + f"#!/bin/python\n# {make_copyright_text(f'(c) {current_year}')}", + ), + ( + "#!/bin/bash\n", + ".sh", + f"#!/bin/bash\n# {make_copyright_text(f'(c) {current_year}')}", + ), + ], +) +def test_adding_new_copyrights(content, extension, expected): + assert process_text(content, extension).startswith(expected) + + +def test_license_has_current_year(): + load_license_text() + # LICENSE file should have the current year (either as single year or end of range) + assert f"{current_year}, NVIDIA CORPORATION" in LICENSE_TEXT From 85ab521db17e14b5b1441380a3882c7e7142a0c1 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:54:37 -0700 Subject: [PATCH 03/15] ci: enforce org-wide label colors and detect cherry-pick PRs The conventional-pr reusable workflow now creates type labels with a fixed org-wide color scheme (and corrects drifted colors), and applies a cherry-pick label when a PR contains the git cherry-pick -x trailer or a cherry-pick hint in its title or branch name. Ships as v1.1.0. TRI-1100 --- .github/workflows/conventional-pr.yml | 73 +++++++++++++++++++++------ 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 00eaa0b..135c1ac 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -25,12 +25,14 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # Reusable workflow: validates the PR title against the Conventional Commits -# format (<type>: <Title>) and labels the PR with its commit type. +# format (<type>: <Title>), labels the PR with its commit type (org-wide +# color scheme enforced), and labels cherry-pick PRs (detected via the +# `git cherry-pick -x` commit trailer or a title/branch hint). # Call from a repo with: # # jobs: # conventional-pr: -# uses: triton-inference-server/.github/.github/workflows/conventional-pr.yml@v1.0.0 +# uses: triton-inference-server/.github/.github/workflows/conventional-pr.yml@v1.1.0 # # The caller must grant `permissions: pull-requests: write`. @@ -49,11 +51,42 @@ jobs: uses: actions/github-script@v7 with: script: | - const types = ['build', 'chore', 'ci', 'docs', 'feat', 'fix', - 'perf', 'refactor', 'revert', 'style', 'test']; + // One color per conventional-commit type, enforced org-wide. + const LABELS = { + build: 'c5def5', + chore: 'fef2c0', + ci: 'bfd4f2', + docs: '0075ca', + feat: '0e8a16', + fix: 'd73a4a', + perf: 'fbca04', + refactor:'5319e7', + revert: 'b60205', + style: 'f9d0c4', + test: '1d76db', + 'cherry-pick': '006b75', + }; + const types = Object.keys(LABELS).filter((t) => t !== 'cherry-pick'); + const { owner, repo } = context.repo; + const number = context.payload.pull_request.number; + + // Create the label if missing, otherwise enforce the org color. + async function ensureLabel(name) { + const color = LABELS[name]; + try { + await github.rest.issues.createLabel({ owner, repo, name, color }); + } catch (e) { + if (e.status !== 422) throw e; // 422 = already exists + const { data } = await github.rest.issues.getLabel({ owner, repo, name }); + if (data.color.toLowerCase() !== color) { + await github.rest.issues.updateLabel({ owner, repo, name, color }); + } + } + } + + // 1. Validate the PR title and apply the type label. const title = context.payload.pull_request.title; const match = title.match(/^(\w+)(\([^)]*\))?!?: .+/); - if (!match || !types.includes(match[1])) { core.setFailed( `PR title "${title}" does not follow the Conventional Commits ` + @@ -62,17 +95,7 @@ jobs: return; } const type = match[1]; - - // Ensure the label exists, then apply it (idempotent). Remove any - // stale type label left from a previous title. - const { owner, repo } = context.repo; - const number = context.payload.pull_request.number; - try { - await github.rest.issues.createLabel( - { owner, repo, name: type, color: 'ededed' }); - } catch (e) { - if (e.status !== 422) throw e; // 422 = already exists - } + await ensureLabel(type); const { data: current } = await github.rest.issues.listLabelsOnIssue( { owner, repo, issue_number: number }); for (const l of current) { @@ -84,3 +107,21 @@ jobs: await github.rest.issues.addLabels( { owner, repo, issue_number: number, labels: [type] }); core.info(`PR title OK; labeled as "${type}".`); + + // 2. Cherry-pick detection: the standard `git cherry-pick -x` + // trailer in any commit, or an explicit hint in the title/branch. + // Additive only - a manually applied label is never removed. + const commits = await github.paginate(github.rest.pulls.listCommits, + { owner, repo, pull_number: number, per_page: 100 }); + const trailer = /cherry[- ]?picked from commit [0-9a-f]{7,40}/i; + const hint = /cherry[- ]?pick/i; + const isCherryPick = + commits.some((c) => trailer.test(c.commit.message)) || + hint.test(title) || + hint.test(context.payload.pull_request.head.ref); + if (isCherryPick) { + await ensureLabel('cherry-pick'); + await github.rest.issues.addLabels( + { owner, repo, issue_number: number, labels: ['cherry-pick'] }); + core.info('Cherry-pick detected; labeled as "cherry-pick".'); + } From 355dd17375c4df4fc516c5c6ce573275e6b42577 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:26:47 -0700 Subject: [PATCH 04/15] ci: apply human-readable type labels with descriptions Titles are still validated against the strict conventional-commit type tokens; the label applied is a friendlier name (feat->enhancement, fix->bug, docs->documentation, ...) reusing GitHub's well-known default labels where they exist, each with an explanatory description that records the type mapping. Colors and descriptions are enforced org-wide. Ships as v1.2.0. TRI-1100 --- .github/workflows/conventional-pr.yml | 74 +++++++++++++++++---------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 135c1ac..fa7437a 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -51,35 +51,53 @@ jobs: uses: actions/github-script@v7 with: script: | - // One color per conventional-commit type, enforced org-wide. - const LABELS = { - build: 'c5def5', - chore: 'fef2c0', - ci: 'bfd4f2', - docs: '0075ca', - feat: '0e8a16', - fix: 'd73a4a', - perf: 'fbca04', - refactor:'5319e7', - revert: 'b60205', - style: 'f9d0c4', - test: '1d76db', - 'cherry-pick': '006b75', + // Conventional-commit type -> human-readable label, org-wide color, + // and description. Titles are validated against the type tokens; + // the friendlier label is what gets applied. + const TYPES = { + build: { label: 'build', color: 'c5def5', + description: 'Build system or external dependencies (build: PRs)' }, + chore: { label: 'chore', color: 'fef2c0', + description: 'Maintenance work, no production code change (chore: PRs)' }, + ci: { label: 'CI/CD', color: 'bfd4f2', + description: 'Continuous integration and workflow changes (ci: PRs)' }, + docs: { label: 'documentation', color: '0075ca', + description: 'Improvements or additions to documentation (docs: PRs)' }, + feat: { label: 'enhancement', color: 'a2eeef', + description: 'New feature or capability (feat: PRs)' }, + fix: { label: 'bug', color: 'd73a4a', + description: 'Bug fix (fix: PRs)' }, + perf: { label: 'performance', color: 'fbca04', + description: 'Performance improvement (perf: PRs)' }, + refactor: { label: 'refactor', color: '5319e7', + description: 'Code change that neither fixes a bug nor adds a feature (refactor: PRs)' }, + revert: { label: 'revert', color: 'b60205', + description: 'Reverts a previous change (revert: PRs)' }, + style: { label: 'code style', color: 'f9d0c4', + description: 'Formatting and style-only changes (style: PRs)' }, + test: { label: 'testing', color: '1d76db', + description: 'Adding or correcting tests (test: PRs)' }, }; - const types = Object.keys(LABELS).filter((t) => t !== 'cherry-pick'); + const CHERRY = { label: 'cherry-pick', color: '006b75', + description: 'Cherry-picked from another branch' }; + const types = Object.keys(TYPES); + const typeLabels = Object.values(TYPES).map((t) => t.label); const { owner, repo } = context.repo; const number = context.payload.pull_request.number; - // Create the label if missing, otherwise enforce the org color. - async function ensureLabel(name) { - const color = LABELS[name]; + // Create the label if missing, otherwise enforce the org color and + // description. + async function ensureLabel({ label: name, color, description }) { try { - await github.rest.issues.createLabel({ owner, repo, name, color }); + await github.rest.issues.createLabel( + { owner, repo, name, color, description }); } catch (e) { if (e.status !== 422) throw e; // 422 = already exists const { data } = await github.rest.issues.getLabel({ owner, repo, name }); - if (data.color.toLowerCase() !== color) { - await github.rest.issues.updateLabel({ owner, repo, name, color }); + if (data.color.toLowerCase() !== color || + (data.description || '') !== description) { + await github.rest.issues.updateLabel( + { owner, repo, name, color, description }); } } } @@ -94,19 +112,19 @@ jobs: 'See https://www.conventionalcommits.org/'); return; } - const type = match[1]; - await ensureLabel(type); + const entry = TYPES[match[1]]; + await ensureLabel(entry); const { data: current } = await github.rest.issues.listLabelsOnIssue( { owner, repo, issue_number: number }); for (const l of current) { - if (types.includes(l.name) && l.name !== type) { + if (typeLabels.includes(l.name) && l.name !== entry.label) { await github.rest.issues.removeLabel( { owner, repo, issue_number: number, name: l.name }); } } await github.rest.issues.addLabels( - { owner, repo, issue_number: number, labels: [type] }); - core.info(`PR title OK; labeled as "${type}".`); + { owner, repo, issue_number: number, labels: [entry.label] }); + core.info(`PR title OK; labeled as "${entry.label}".`); // 2. Cherry-pick detection: the standard `git cherry-pick -x` // trailer in any commit, or an explicit hint in the title/branch. @@ -120,8 +138,8 @@ jobs: hint.test(title) || hint.test(context.payload.pull_request.head.ref); if (isCherryPick) { - await ensureLabel('cherry-pick'); + await ensureLabel(CHERRY); await github.rest.issues.addLabels( - { owner, repo, issue_number: number, labels: ['cherry-pick'] }); + { owner, repo, issue_number: number, labels: [CHERRY.label] }); core.info('Cherry-pick detected; labeled as "cherry-pick".'); } From 054fc998caca46756afb1a139f8119c8f2e7d5ad Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:27:44 -0700 Subject: [PATCH 05/15] ci: keep the fix type label literal and clean up legacy type labels fix: PRs are labeled 'fix' (the stock 'bug' label remains reserved for issue triage), and raw-token labels left by earlier workflow versions are removed when the friendly label is applied. Ships as v1.2.1. TRI-1100 --- .github/workflows/conventional-pr.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index fa7437a..00a0a78 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -65,7 +65,7 @@ jobs: description: 'Improvements or additions to documentation (docs: PRs)' }, feat: { label: 'enhancement', color: 'a2eeef', description: 'New feature or capability (feat: PRs)' }, - fix: { label: 'bug', color: 'd73a4a', + fix: { label: 'fix', color: 'd73a4a', description: 'Bug fix (fix: PRs)' }, perf: { label: 'performance', color: 'fbca04', description: 'Performance improvement (perf: PRs)' }, @@ -117,7 +117,10 @@ jobs: const { data: current } = await github.rest.issues.listLabelsOnIssue( { owner, repo, issue_number: number }); for (const l of current) { - if (typeLabels.includes(l.name) && l.name !== entry.label) { + // Also removes legacy raw-token labels from earlier workflow + // versions (e.g. "ci" before it became "CI/CD"). + if ((typeLabels.includes(l.name) || types.includes(l.name)) && + l.name !== entry.label) { await github.rest.issues.removeLabel( { owner, repo, issue_number: number, name: l.name }); } From 6cc271faf75d4e055b950356844993f3fe770220 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:33:55 -0700 Subject: [PATCH 06/15] ci: label feat PRs as 'feature' TRI-1100 --- .github/workflows/conventional-pr.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 00a0a78..0d6ec72 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -63,7 +63,7 @@ jobs: description: 'Continuous integration and workflow changes (ci: PRs)' }, docs: { label: 'documentation', color: '0075ca', description: 'Improvements or additions to documentation (docs: PRs)' }, - feat: { label: 'enhancement', color: 'a2eeef', + feat: { label: 'feature', color: 'a2eeef', description: 'New feature or capability (feat: PRs)' }, fix: { label: 'fix', color: 'd73a4a', description: 'Bug fix (fix: PRs)' }, From ea73a0fab0fcce1270ef4bda7c4a7df77ab9f037 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:38:16 -0700 Subject: [PATCH 07/15] docs: keep the Commit Type checklist in the external PR template Review feedback on triton-inference-server/server#8890: external contributors keep the visual commit-type checklist for now; internal contributors rely on the enforced conventional-pr workflow and commit-msg hook. TRI-1100 --- .../pull_request_template_external_contrib.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md index 4a9e6b6..4f7afde 100644 --- a/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md +++ b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md @@ -16,6 +16,20 @@ Agreement](https://github.com/NVIDIA/triton-inference-server/blob/master/Triton- - [ ] All template sections are filled out. - [ ] Optional: Additional screenshots for behavior/output changes with before/after. +#### Commit Type: +Check the [conventional commit type](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type) +box here and add the label to the github PR. +- [ ] build +- [ ] ci +- [ ] docs +- [ ] feat +- [ ] fix +- [ ] perf +- [ ] refactor +- [ ] revert +- [ ] style +- [ ] test + #### Related PRs: <!-- Related PRs from other Repositories --> From 80887c1e324a3c9383db9d6f5432534d3c8da15c Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:40:16 -0700 Subject: [PATCH 08/15] ci: label PRs from all conforming commit types Derive the label set from the PR title plus every conforming commit subject (one label per distinct type). Non-conventional commit subjects produce warnings; if no type can be derived at all and no type label is manually assigned, the check fails. Ships as v1.3.0. TRI-1100 --- .github/workflows/conventional-pr.yml | 44 ++++++++++++++++++++------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 0d6ec72..6620521 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -102,7 +102,7 @@ jobs: } } - // 1. Validate the PR title and apply the type label. + // 1. Validate the PR title (the squash-merge commit title). const title = context.payload.pull_request.title; const match = title.match(/^(\w+)(\([^)]*\))?!?: .+/); if (!match || !types.includes(match[1])) { @@ -112,28 +112,50 @@ jobs: 'See https://www.conventionalcommits.org/'); return; } - const entry = TYPES[match[1]]; - await ensureLabel(entry); + + // 2. Derive the full type set: the title plus every conforming + // commit subject in the PR (merge commits are ignored). + const commits = await github.paginate(github.rest.pulls.listCommits, + { owner, repo, pull_number: number, per_page: 100 }); + const derived = new Set([match[1]]); + for (const c of commits) { + const subject = c.commit.message.split('\n', 1)[0]; + if (/^Merge /.test(subject)) continue; + const m = subject.match(/^(\w+)(\([^)]*\))?!?: .+/); + if (m && types.includes(m[1])) derived.add(m[1]); + else core.warning(`Commit ${c.sha.slice(0, 9)} subject is not ` + + `a conventional commit: "${subject}"`); + } + const { data: current } = await github.rest.issues.listLabelsOnIssue( { owner, repo, issue_number: number }); + // Fail-safe: no derivable type and no manually assigned type label. + if (derived.size === 0 && + !current.some((l) => typeLabels.includes(l.name))) { + core.setFailed( + 'No conventional commit type could be determined from the PR ' + + 'title or its commits, and no type label is assigned.'); + return; + } + + // 3. Apply one label per derived type; drop stale auto-managed + // labels (incl. legacy raw-token names from earlier versions). + const wanted = [...derived].map((t) => TYPES[t].label); + for (const t of derived) await ensureLabel(TYPES[t]); for (const l of current) { - // Also removes legacy raw-token labels from earlier workflow - // versions (e.g. "ci" before it became "CI/CD"). if ((typeLabels.includes(l.name) || types.includes(l.name)) && - l.name !== entry.label) { + !wanted.includes(l.name)) { await github.rest.issues.removeLabel( { owner, repo, issue_number: number, name: l.name }); } } await github.rest.issues.addLabels( - { owner, repo, issue_number: number, labels: [entry.label] }); - core.info(`PR title OK; labeled as "${entry.label}".`); + { owner, repo, issue_number: number, labels: wanted }); + core.info(`Labeled: ${wanted.join(', ')}.`); - // 2. Cherry-pick detection: the standard `git cherry-pick -x` + // 4. Cherry-pick detection: the standard `git cherry-pick -x` // trailer in any commit, or an explicit hint in the title/branch. // Additive only - a manually applied label is never removed. - const commits = await github.paginate(github.rest.pulls.listCommits, - { owner, repo, pull_number: number, per_page: 100 }); const trailer = /cherry[- ]?picked from commit [0-9a-f]{7,40}/i; const hint = /cherry[- ]?pick/i; const isCherryPick = From e08e34763ac370e2414130464dd1623da45220a7 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:54:28 -0700 Subject: [PATCH 09/15] ci: detect cherry-picks of squash-merged commits A trailing "(#N)" squash-merge reference to a different PR in the title or any commit subject now also triggers the cherry-pick label. Ships as v1.3.1. TRI-1100 --- .github/workflows/conventional-pr.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 6620521..0eb4133 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -154,14 +154,23 @@ jobs: core.info(`Labeled: ${wanted.join(', ')}.`); // 4. Cherry-pick detection: the standard `git cherry-pick -x` - // trailer in any commit, or an explicit hint in the title/branch. + // trailer in any commit, an explicit hint in the title/branch, or + // a trailing "(#N)" squash-merge reference to a DIFFERENT PR (the + // signature of cherry-picking an already squash-merged commit). // Additive only - a manually applied label is never removed. const trailer = /cherry[- ]?picked from commit [0-9a-f]{7,40}/i; const hint = /cherry[- ]?pick/i; + const prRef = /\(#(\d+)\)$/; + const refersToOtherPr = (subject) => { + const m = subject.trim().match(prRef); + return m !== null && Number(m[1]) !== number; + }; const isCherryPick = commits.some((c) => trailer.test(c.commit.message)) || hint.test(title) || - hint.test(context.payload.pull_request.head.ref); + hint.test(context.payload.pull_request.head.ref) || + refersToOtherPr(title) || + commits.some((c) => refersToOtherPr(c.commit.message.split('\n', 1)[0])); if (isCherryPick) { await ensureLabel(CHERRY); await github.rest.issues.addLabels( From 4489ed7b012da5884f29c0807c7360d37784413a Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:21:13 -0700 Subject: [PATCH 10/15] chore: add BSD-3-Clause LICENSE and enable the add-license hook on this repo The repository ships code (the centralized copyright tool and the reusable conventional-pr workflow) whose file headers assert BSD terms; add the license text itself and run the in-tree add-license hook in this repo's own pre-commit config (templates stay excluded). TRI-1100 --- .pre-commit-config.yaml | 18 +++++++++++++++--- LICENSE | 25 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 LICENSE diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0b767fc..f77f1dd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -83,6 +83,18 @@ repos: - id: requirements-txt-fixer - id: trailing-whitespace -# NOTE: the add-license hook defined by this repository (.pre-commit-hooks.yaml) -# is exported for consumer repositories; it is not enabled here because this -# repository has no LICENSE file. +# This repository defines the centralized add-license hook; run the in-tree +# version directly so CI validates the hook code being merged. Consumer +# repositories reference it via `repo: .../.github` + a pinned tag. +- repo: local + hooks: + - id: add-license + name: Add License + entry: tools/add_copyright.py + language: script + stages: [pre-commit] + verbose: true + require_serial: true + # GitHub issue/PR templates must start with YAML frontmatter; keep + # license headers out of them. + exclude: ^(\.github/|ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE/|pull_request_template) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8464654 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of NVIDIA CORPORATION nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 898e8ccac0961a2c3da91db33c1a18c540581eaf Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Mon, 20 Jul 2026 17:44:59 -0700 Subject: [PATCH 11/15] ci: add CodeRabbit pilot configuration Per-repository config-as-code pilot, this repository only: chill profile, auto-review incl. drafts (pilot), path instructions for the org-wide workflows, centralized hook tooling, and inherited templates. Fleet rollout is decided after evaluation; requires the CodeRabbit GitHub App on the organization. No tag changes - this file is not part of any hook or workflow export. TRI-1100 --- .coderabbit.yaml | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..91bbeaf --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,65 @@ +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of NVIDIA CORPORATION nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY +# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json + +# Pilot configuration (verification on this repository only; fleet rollout +# is decided after evaluation). Requires the CodeRabbit GitHub App to be +# installed on the organization. +language: "en-US" + +reviews: + profile: "chill" + request_changes_workflow: false + high_level_summary: true + poem: false + review_status: true + auto_review: + enabled: true + # Drafts included during the pilot so the open TRI-1100 PR gets + # reviewed; revisit (likely false) before any fleet rollout. + drafts: true + path_instructions: + - path: ".github/workflows/*.yml" + instructions: | + These workflows are consumed org-wide (reusable workflows pinned by + tag). Review github-script blocks for correctness and injection + safety, and flag any behavior change that would require a new tag + and consumer rev bumps. + - path: "tools/*.py" + instructions: | + This tooling runs as a centralized pre-commit hook in consumer + repositories with cwd set to the consumer repo root. It must never + modify LICENSE files and must keep license headers out of .github/ + templates. + - path: "ISSUE_TEMPLATE/**" + instructions: | + Org-wide inherited defaults. Issue reporting must keep routing to + the server repository; frontmatter must stay the first line of any + template file. + +chat: + auto_reply: true From 1a8388b31d40537f7b113bc0afe078044cc5a894 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Tue, 21 Jul 2026 07:53:44 -0700 Subject: [PATCH 12/15] ci: fail add-license when the LICENSE copyright year is stale The hook never modifies LICENSE files; instead it now verifies that an NVIDIA copyright line's highest year is the current year and fails with guidance to update it in a deliberate commit. Repos without an NVIDIA copyright line in LICENSE (Apache text, third-party attribution) are not affected. Ships as v1.4.0. TRI-1100 --- tools/add_copyright.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tools/add_copyright.py b/tools/add_copyright.py index c58fd81..20b6186 100755 --- a/tools/add_copyright.py +++ b/tools/add_copyright.py @@ -95,12 +95,24 @@ def get_license(license_path: Optional[str] = None) -> str: Returns the contents of the LICENSE file. Note: The LICENSE file itself is never modified by this hook; its - copyright year is maintained manually by each repository. + copyright year is maintained manually by each repository. If the file + carries an NVIDIA copyright whose highest year is not the current year, + the hook fails so the year gets updated in a deliberate commit. """ license_path = license_path or get_license_path() with open(license_path, "r") as license_file: - return license_file.read() + text = license_file.read() + + match = COPYRIGHT_YEAR_PAT.search(text) + if match and match.groups()[2] != current_year: + raise SystemExit( + f"ERROR: the LICENSE copyright year is stale " + f"({match.group(0).strip()!r}); its highest year must be " + f"{current_year}. This hook never modifies LICENSE files - " + "update the year in a deliberate commit." + ) + return text def load_license_text() -> None: From 6698584b3eaa0a81c2e53cef67c8c4e13b367035 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Tue, 21 Jul 2026 08:26:56 -0700 Subject: [PATCH 13/15] ci: license-process .github/workflows, exclude only templates The add-license exclude was too broad: ^\.github/ also skipped workflow files, leaving their copyright headers unmaintained and unchecked. Narrow it to the template paths (frontmatter must stay first) and bump the stale header this repo's own workflow carried. Ships as v1.4.1. TRI-1100 --- .github/workflows/pre-commit.yml | 2 +- .pre-commit-config.yaml | 2 +- .pre-commit-hooks.yaml | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 15d0b68..4dbce6b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,4 +1,4 @@ -# Copyright 2023-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# Copyright 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f77f1dd..36622eb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -97,4 +97,4 @@ repos: require_serial: true # GitHub issue/PR templates must start with YAML frontmatter; keep # license headers out of them. - exclude: ^(\.github/|ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE/|pull_request_template) + exclude: ^(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE/|pull_request_template) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index fef4d8a..1fa5cd6 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -32,5 +32,6 @@ verbose: true require_serial: true # GitHub issue/PR templates must start with YAML frontmatter; keep - # license headers out of them. - exclude: ^\.github/ + # license headers out of them. Workflows and other .github/ files ARE + # processed. + exclude: ^\.github/(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE/|pull_request_template) From cd67f38797f1d5d86d130ca87273987f3d1de540 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Tue, 21 Jul 2026 17:17:32 -0700 Subject: [PATCH 14/15] ci: grant issues:write to the conventional-pr labeling job Review feedback (Greptile P1 on backend#127): label creation and mutation use the issues API surface; labeling worked empirically under pull-requests:write alone, but the explicit grant is correct belt-and-braces. Ships as v1.4.2. TRI-1100 --- .github/workflows/conventional-pr-check.yml | 1 + .github/workflows/conventional-pr.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/conventional-pr-check.yml b/.github/workflows/conventional-pr-check.yml index 2509787..1ce6550 100644 --- a/.github/workflows/conventional-pr-check.yml +++ b/.github/workflows/conventional-pr-check.yml @@ -37,4 +37,5 @@ jobs: conventional-pr: permissions: pull-requests: write + issues: write uses: ./.github/workflows/conventional-pr.yml diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 0eb4133..c277d60 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -46,6 +46,7 @@ jobs: runs-on: ubuntu-latest permissions: pull-requests: write + issues: write steps: - name: Validate PR title and apply type label uses: actions/github-script@v7 From 76ff555ceaa335dd322c64e3ff5dfcad622e2dc1 Mon Sep 17 00:00:00 2001 From: Misha Chornyi <99709299+mc-nv@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:26:01 -0700 Subject: [PATCH 15/15] ci: address fleet-wide review findings - add_copyright.py: the double-header sanity check now uses COPYRIGHT_YEAR_PAT so LICENSE forms without '(c)' are covered; the test-section imports carry explicit noqa markers now that flake8's select list is actually applied. - pre-commit workflow: robust modified-files runner - null-delimited paths (spaces survive), --no-run-if-empty (deletion-only PRs), --diff-filter=d (no deleted paths), no undocumented -r flag, explicit contents:read, pre-commit cache keyed on the config hash. - conventional-pr reusable + self-check: contents:read granted explicitly. - add-license exclude also covers the single-file .github/PULL_REQUEST_TEMPLATE.md form. - external PR template: absolute CONTRIBUTING link (relative anchors break in org-inherited templates) and the missing 'chore' type. - flake8 args quoted correctly - the previous flow-scalar form split at commas and silently reduced the select list. Ships as v1.4.3. TRI-1100 --- .github/workflows/conventional-pr-check.yml | 1 + .github/workflows/conventional-pr.yml | 1 + .github/workflows/pre-commit.yml | 19 +++++++++++++----- .pre-commit-config.yaml | 4 ++-- .pre-commit-hooks.yaml | 2 +- .../pull_request_template_external_contrib.md | 3 ++- ...add_copyright.cpython-312-pytest-9.1.1.pyc | Bin 0 -> 18429 bytes tools/add_copyright.py | 6 +++--- 8 files changed, 24 insertions(+), 12 deletions(-) create mode 100644 tools/__pycache__/add_copyright.cpython-312-pytest-9.1.1.pyc diff --git a/.github/workflows/conventional-pr-check.yml b/.github/workflows/conventional-pr-check.yml index 1ce6550..523a3a7 100644 --- a/.github/workflows/conventional-pr-check.yml +++ b/.github/workflows/conventional-pr-check.yml @@ -38,4 +38,5 @@ jobs: permissions: pull-requests: write issues: write + contents: read uses: ./.github/workflows/conventional-pr.yml diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index c277d60..f6b9e9a 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -47,6 +47,7 @@ jobs: permissions: pull-requests: write issues: write + contents: read steps: - name: Validate PR title and apply type label uses: actions/github-script@v7 diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 4dbce6b..cd8680b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -32,14 +32,23 @@ on: jobs: pre-commit: runs-on: ubuntu-latest + permissions: + contents: read steps: - uses: actions/checkout@v5.0.0 with: fetch-depth: 2 - - name: Get modified files - id: modified-files - run: echo "modified_files=$(git diff --name-only -r HEAD^1 HEAD | xargs)" >> $GITHUB_OUTPUT - uses: actions/setup-python@v6.0.0 - - uses: pre-commit/action@v3.0.1 + - uses: actions/cache@v4 with: - extra_args: --files ${{ steps.modified-files.outputs.modified_files }} + path: ~/.cache/pre-commit + key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }} + - name: Run pre-commit on the files modified by the PR + # Null-delimited so paths with spaces survive; --no-run-if-empty + # handles deletion-only PRs; deleted paths are filtered out before + # being handed to pre-commit. + run: | + python -m pip install --quiet pre-commit + git diff --name-only -z --diff-filter=d HEAD^1 HEAD \ + | xargs -0 --no-run-if-empty \ + pre-commit run --show-diff-on-failure --color=always --files diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 36622eb..3c4e9bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,7 +41,7 @@ repos: rev: 7.3.0 hooks: - id: flake8 - args: [--max-line-length=88, --select=C,E,F,W,B,B950, --extend-ignore = E203,E501] + args: ["--max-line-length=88", "--select=C,E,F,W,B,B950", "--extend-ignore=E203,E501"] types_or: [python, cython] - repo: https://github.com/pre-commit/mirrors-clang-format rev: v16.0.5 @@ -97,4 +97,4 @@ repos: require_serial: true # GitHub issue/PR templates must start with YAML frontmatter; keep # license headers out of them. - exclude: ^(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE/|pull_request_template) + exclude: ^(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE|pull_request_template) diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 1fa5cd6..55576fa 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -34,4 +34,4 @@ # GitHub issue/PR templates must start with YAML frontmatter; keep # license headers out of them. Workflows and other .github/ files ARE # processed. - exclude: ^\.github/(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE/|pull_request_template) + exclude: ^\.github/(ISSUE_TEMPLATE/|PULL_REQUEST_TEMPLATE|pull_request_template) diff --git a/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md index 4f7afde..c5a3bb8 100644 --- a/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md +++ b/PULL_REQUEST_TEMPLATE/pull_request_template_external_contrib.md @@ -2,7 +2,7 @@ <!-- Describe your pull request here. Please read the text below the line, and make sure you follow the checklist.--> #### Checklist -- [ ] I have read the [Contribution guidelines](#../../CONTRIBUTING.md) and signed the [Contributor License +- [ ] I have read the [Contribution guidelines](https://github.com/triton-inference-server/server/blob/main/CONTRIBUTING.md) and signed the [Contributor License Agreement](https://github.com/NVIDIA/triton-inference-server/blob/master/Triton-CCLA-v1.pdf) - [ ] PR title reflects the change and is of format `<commit_type>: <Title>` - [ ] Changes are described in the pull request. @@ -20,6 +20,7 @@ Agreement](https://github.com/NVIDIA/triton-inference-server/blob/master/Triton- Check the [conventional commit type](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type) box here and add the label to the github PR. - [ ] build +- [ ] chore - [ ] ci - [ ] docs - [ ] feat diff --git a/tools/__pycache__/add_copyright.cpython-312-pytest-9.1.1.pyc b/tools/__pycache__/add_copyright.cpython-312-pytest-9.1.1.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ce04903a9cec6943bdc890c9d492b66601cb5b2 GIT binary patch literal 18429 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 literal 0 HcmV?d00001 diff --git a/tools/add_copyright.py b/tools/add_copyright.py index 20b6186..ff17802 100755 --- a/tools/add_copyright.py +++ b/tools/add_copyright.py @@ -184,7 +184,7 @@ def update_or_add_header( # As a sanity check, make sure we didn't accidentally add the copyright header # twice, or add a new header when one was already present. - if content.count("Copyright (c)") != 1: + if len(COPYRIGHT_YEAR_PAT.findall(content)) != 1: print( f"WARNING: Something went wrong while processing: {path}!\n" "Please check if the copyright header was included twice or wasn't added at all. " @@ -333,9 +333,9 @@ def main() -> int: # # Integration Tests # -import tempfile +import tempfile # noqa: E402 (test-section import, deliberately mid-file) -import pytest +import pytest # noqa: E402 (test-section import, deliberately mid-file) # Processes provided text through the copyright hook by writing it to a temporary file.