This commit is contained in:
devin-ai-integration[bot] 2026-10-17 08:01:51 -07:00 • committed by GitHub
commit 511894f21b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 13 additions and 210 deletions

View file

@ -1,90 +0,0 @@
#!/usr/bin/env bash
#
# pre-push — enforce Conventional Branches
# https://conventional-branch.github.io/
#
# Branch format: <type>/<description>
# <type> must be one of: feature, bugfix, hotfix, release, chore
#
# Protected branches (always allowed):
# - main
# - dependabot/*
# - gh-readonly-queue/*
#
# Tag pushes and branch deletions are skipped.
# Bypass: git push --no-verify.
set -eu
ZERO_OID="0000000000000000000000000000000000000000"
ZERO_OID_SHA256="0000000000000000000000000000000000000000000000000000000000000000"
ALLOWED_TYPES="feature|bugfix|hotfix|release|chore"
BRANCH_PATTERN="^(${ALLOWED_TYPES})/.+"
PROTECTED_NAMES="main"
PROTECTED_PREFIXES="dependabot/ gh-readonly-queue/"
is_protected() {
branch="$1"
for name in $PROTECTED_NAMES; do
if [ "$branch" = "$name" ]; then
return 0
fi
done
for prefix in $PROTECTED_PREFIXES; do
case "$branch" in "$prefix"*) return 0 ;; esac
done
return 1
}
invalid=""
while read -r local_ref local_oid remote_ref remote_oid; do
# Branch deletion (no local commit being pushed).
if [ "$local_oid" = "$ZERO_OID" ] || [ "$local_oid" = "$ZERO_OID_SHA256" ]; then
continue
fi
# Only validate branch pushes; ignore tags and other ref namespaces.
case "$remote_ref" in
refs/heads/*) ;;
*) continue ;;
esac
branch="${remote_ref#refs/heads/}"
if is_protected "$branch"; then
continue
fi
if ! printf '%s' "$branch" | grep -Eq "$BRANCH_PATTERN"; then
invalid="$invalid $branch"
fi
done
if [ -n "$invalid" ]; then
cat >&2 <<EOF
✗ Branch name does not follow Conventional Branches.
Invalid:$invalid
Expected: <type>/<description>
Allowed types: feature, bugfix, hotfix, release, chore
Examples:
feature/weighted-round-robin
bugfix/streaming-empty-chunks
chore/bump-deps
hotfix/auth-bypass
Protected (always allowed): main, dependabot/*, gh-readonly-queue/*.
See https://conventional-branch.github.io/
Rename with: git branch -m <new-name>
To bypass (use sparingly): git push --no-verify
EOF
exit 1
fi
exit 0

View file

@ -39,12 +39,12 @@ git clone https://github.com/YOUR_USERNAME/litellm.git
cd litellm
# Create a new branch for your feature (see "Commit and Branch Conventions" below)
git checkout -b feature/your-feature
git checkout -b litellm_your_feature
# Install development dependencies
make install-dev
# Install git hooks that enforce commit + branch conventions (one-time, opt-in)
# Install git hooks that enforce commit conventions (one-time, opt-in)
make install-hooks
# Verify your setup works
@ -55,7 +55,7 @@ That's it! Your local development environment is ready.
## Commit and Branch Conventions
Commits follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) and branches follow [Conventional Branches](https://conventional-branch.github.io/). Run `make install-hooks` once per clone to enable the local git hooks that enforce these — see the [contributor docs](https://docs.litellm.ai/docs/extras/contributing_code#commit-and-branch-conventions) for the full type list, examples, the protected-branch bypass list, and how to opt out.
Commits follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). Branches use the `litellm_` prefix with no `/`. Run `make install-hooks` once per clone to enable the local hook that enforces the commit convention. See the [contributor docs](https://docs.litellm.ai/docs/extras/contributing_code#commit-and-branch-conventions) for details
### 2. Development Workflow
@ -78,8 +78,8 @@ uv run pytest tests/unit/<your_test_file>.py -v
git add .
git commit -m "feat(scope): your descriptive commit message"
# Push and create a PR (branch must follow Conventional Branches — see above)
git push origin feature/your-feature
# Push and create a PR
git push origin litellm_your_feature
```
## Adding Testing

View file

@ -120,7 +120,7 @@ install-helm-unittest:
helm plugin install https://github.com/helm-unittest/helm-unittest --version v0.8.2; \
}
# Install git hooks that enforce Conventional Commits and Conventional Branches.
# Install git hooks that enforce Conventional Commits.
# Opt-in: not chained into install-dev.
install-hooks:
./scripts/install_git_hooks.sh

View file

@ -31,7 +31,7 @@ cat <<EOF
core.hooksPath = .githooks
active hooks: $(ls "$hooks_dir" | tr '\n' ' ')
These hooks enforce Conventional Commits and Conventional Branches.
These hooks enforce Conventional Commits.
Bypass with --no-verify when you need to (e.g. for emergency hotfixes).
The CI-equivalent lint is deliberately not installed as an auto-firing hook

View file

@ -1,12 +1,11 @@
"""Tests for the repo's git hook scripts in ``.githooks/``.
The hooks enforce Conventional Commits 1.0.0 on the commit-msg path and
Conventional Branches on the pre-push path. Each hook is exercised here as a
The commit-msg hook enforces Conventional Commits. It is exercised as a
subprocess against representative valid / invalid inputs so that any future
regex change or accidental edit gets caught by ``make test-unit``.
The hooks are POSIX-ish bash scripts; the test is skipped on Windows where
``bash`` may not be on PATH.
The hook is a bash script; the test is skipped on Windows where ``bash`` may
not be on PATH.
"""
import os
@ -19,10 +18,6 @@ import pytest
_REPO_ROOT = Path(__file__).resolve().parents[2]
_HOOKS_DIR = _REPO_ROOT / ".githooks"
_COMMIT_MSG_HOOK = _HOOKS_DIR / "commit-msg"
_PRE_PUSH_HOOK = _HOOKS_DIR / "pre-push"
_ZERO_OID = "0" * 40
_NONZERO_OID = "abc123abc123abc123abc123abc123abc123abc1"
pytestmark = pytest.mark.skipif(
shutil.which("bash") is None,
@ -33,14 +28,12 @@ pytestmark = pytest.mark.skipif(
@pytest.fixture(autouse=True)
def _ensure_hooks_exist():
assert _COMMIT_MSG_HOOK.exists(), f"missing hook: {_COMMIT_MSG_HOOK}"
assert _PRE_PUSH_HOOK.exists(), f"missing hook: {_PRE_PUSH_HOOK}"
# Exec bit may be missing on a fresh clone on case-preserving filesystems;
# the installer normalizes this, but the test shouldn't depend on having
# run it.
for hook in (_COMMIT_MSG_HOOK, _PRE_PUSH_HOOK):
mode = hook.stat().st_mode
if not (mode & 0o100):
hook.chmod(mode | 0o755)
mode = _COMMIT_MSG_HOOK.stat().st_mode
if not (mode & 0o100):
_COMMIT_MSG_HOOK.chmod(mode | 0o755)
def _run_commit_msg(subject: str, tmp_path: Path) -> subprocess.CompletedProcess:
@ -54,21 +47,6 @@ def _run_commit_msg(subject: str, tmp_path: Path) -> subprocess.CompletedProcess
)
def _run_pre_push(stdin: str) -> subprocess.CompletedProcess:
return subprocess.run(
["bash", str(_PRE_PUSH_HOOK)],
input=stdin,
capture_output=True,
text=True,
check=False,
)
def _ref_line(branch: str, local_oid: str = _NONZERO_OID, remote_oid: str = _ZERO_OID) -> str:
ref = f"refs/heads/{branch}"
return f"{ref} {local_oid} {ref} {remote_oid}\n"
# ----- commit-msg -----------------------------------------------------------
@ -198,88 +176,3 @@ def test_commit_msg_uses_first_non_comment_line(tmp_path):
check=False,
)
assert result.returncode == 0, result.stderr
# ----- pre-push -------------------------------------------------------------
@pytest.mark.parametrize(
"branch",
[
"feature/weighted-round-robin",
"bugfix/streaming-empty-chunks",
"hotfix/auth-bypass",
"release/v1.45.0",
"chore/bump-deps",
"feature/nested/path/ok", # nested slashes after type are fine
],
)
def test_pre_push_accepts_conventional_branches(branch):
result = _run_pre_push(_ref_line(branch))
assert result.returncode == 0, (
f"hook rejected a valid branch:\n branch: {branch!r}\n"
f" stderr: {result.stderr}"
)
@pytest.mark.parametrize(
"branch",
[
"random-branch-name",
"litellm_fix/optimize-streaming", # legacy pattern is now rejected
"ui/navbar-notifications", # not in the allow list
"feature/", # empty description
"Feature/foo", # type is case-sensitive
"feat/foo", # angular commit type, not branch type
],
)
def test_pre_push_rejects_non_conventional_branches(branch):
result = _run_pre_push(_ref_line(branch))
assert result.returncode == 1, (
f"hook accepted an invalid branch:\n branch: {branch!r}\n"
f" stderr: {result.stderr}"
)
assert "Conventional Branches" in result.stderr
@pytest.mark.parametrize(
"branch",
[
"main",
"dependabot/github_actions/foo",
"gh-readonly-queue/main/abc123",
],
)
def test_pre_push_bypasses_protected_branches(branch):
result = _run_pre_push(_ref_line(branch))
assert result.returncode == 0, (
f"protected branch was rejected:\n branch: {branch!r}\n"
f" stderr: {result.stderr}"
)
def test_pre_push_skips_tag_pushes():
line = f"refs/tags/v1 {_NONZERO_OID} refs/tags/v1 {_ZERO_OID}\n"
result = _run_pre_push(line)
assert result.returncode == 0, result.stderr
def test_pre_push_skips_branch_deletions():
# local oid all zeros = deletion
line = f"refs/heads/whatever {_ZERO_OID} refs/heads/whatever {_NONZERO_OID}\n"
result = _run_pre_push(line)
assert result.returncode == 0, result.stderr
def test_pre_push_fails_if_any_ref_is_invalid():
# Mixed batch: one valid, one invalid — entire push should fail.
stdin = _ref_line("feature/ok") + _ref_line("random-bad")
result = _run_pre_push(stdin)
assert result.returncode == 1
assert "random-bad" in result.stderr
def test_pre_push_no_refs_passes():
# Empty stdin (no refs being pushed) should pass.
result = _run_pre_push("")
assert result.returncode == 0, result.stderr