mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
Merge 9b2893f445 into 82ca10cc2d
This commit is contained in:
commit
511894f21b
5 changed files with 13 additions and 210 deletions
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue