-
Notifications
You must be signed in to change notification settings - Fork 947
Description
Steps to Reproduce
This reproduces the GitHub Actions scenario where actions/checkout@v4 uses --depth=1 by default.
Quick reproduction using test branch:
# 1. Create a shallow clone (simulates GitHub Actions checkout)
rm -rf /tmp/commitlint-bug-repro && git init /tmp/commitlint-bug-repro
cd /tmp/commitlint-bug-repro
git remote add origin https://github.com/CervEdin/commitlint.git
git fetch --no-tags --depth=1 origin test-commitlint-shallow-bug
git checkout FETCH_HEAD
# 2. Fetch master branch (shallow)
git fetch --depth=1 origin master
# 3. Verify incomplete history
git merge-base origin/master HEAD || echo "No merge-base found (shallow clone)"
git log origin/master..HEAD --oneline
# Shows only 1 commit: "fix: resolve issue with shallow clones"
# 4. Create simple config (to avoid dependency issues)
cat > /tmp/simple-config.js << 'EOF'
export default {
rules: {
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore']],
'type-empty': [2, 'never'],
'subject-empty': [2, 'never']
}
};
EOF
# 5. Run commitlint - it will PASS despite missing invalid commits
npx @commitlint/cli@latest --config /tmp/simple-config.js --from origin/master --to HEAD --verbose
# Result: ✔ found 0 problems, 0 warnings (FALSE POSITIVE!)Compare with full clone:
# Clone with full history
git clone https://github.com/CervEdin/commitlint.git /tmp/commitlint-full
cd /tmp/commitlint-full
git checkout test-commitlint-shallow-bug
# Check actual commits in range
git log origin/master..HEAD --oneline
# Shows 4 commits:
# - fix: resolve issue with shallow clones ✓
# - adapt thingyBeepBoop ✗ (INVALID)
# - update some documentation ✗ (INVALID)
# - feat: add initial feature for testing ✓
# Run commitlint with full history
npx @commitlint/cli@latest --config /tmp/simple-config.js --from origin/master --to HEAD --verbose
# Result: ✖ found 2 problems, 0 warnings (for each invalid commit)
# Correctly identifies both invalid commitsKey observation: In shallow clone, commitlint validates only 1 commit and passes. In full clone, it correctly identifies 2 invalid commits out of 4 total.
Current Behavior
When running commitlint --from origin/master --to HEAD in a shallow clone,
commitlint silently succeeds even when commits in the specified range don't
exist in the repository, potentially missing invalid commit messages.
Commitlint validates only the commits that exist in the shallow clone and
silently succeeds, giving a false positive that all commits in the range are
valid.
Real-world example from our CI pipeline:
Using GitHub Actions with actions/checkout@v4 (which uses --depth=1):
- Branch had 6 commits from
origin/masterto HEAD - One commit had invalid message: "adapt thingyBeepBoop" (no type prefix)
- Shallow clone contained only HEAD commit (which was valid)
- Commitlint validated only HEAD, reported success
- Invalid commit was never checked
Expected Behavior
Commitlint should verify that a merge-base exists between --from and --to, and fail with a clear error when the history is incomplete. Something like:
Error: Cannot find merge-base between origin/master and HEAD
This typically indicates incomplete git history (e.g., shallow clone).
This follows git's own pattern - git diff --three-dot fails with "no merge base" when history is incomplete.
Affected packages
- cli
- core
- prompt
- config-angular
Possible Solution
Check if a merge-base exists between --from and --to before validation:
git merge-base "$from" "$to" >/dev/null 2>&1 || {
echo "Error: Cannot find merge-base between $from and $to"
echo "This typically indicates incomplete git history (e.g., shallow clone)"
exit 1
}This is more precise than checking for shallow clones because:
- It detects the actual problem (incomplete history between specified refs)
- Git itself uses merge-base for three-dot diff (
git diff --three-dot) - It's reasonable for a validation tool to require a complete commit range
Workaround for users:
In GitHub Actions, fetch sufficient history and verify it's complete:
Recommended: Fetch depth and verify with merge-base:
- uses: actions/checkout@v4
with:
fetch-depth: 100 # Covers most PRs
- name: Commitlint
run: |
# Verify we have complete history for the range
if ! git merge-base origin/master HEAD >/dev/null 2>&1; then
echo "Insufficient history, fetching more..."
git fetch --unshallow origin
fi
npx commitlint --from origin/master --to HEADThis approach:
- Fetches 100 commits upfront (covers most PRs)
- Falls back to full history only when needed (PRs >100 commits)
- Uses
git merge-baseto detect incomplete history
Alternative: Fetch full history (simpler but slower):
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history (0 = unlimited depth)Context
This issue affects CI pipelines that use shallow clones (the default in GitHub
Actions). Developers expect commitlint to validate all commits in a PR, but it
silently passes when it can only see HEAD, creating a false sense of security.
This appears to be related to:
- Using same commit for --from and --to does nothing and returns exit-code=0 (success) #3376 - Using same commit for --from and --to does nothing and returns
exit-code=0 (success) - fix: from to feature does not detect violations properly #3924 - from to feature does not detect violations properly
Both show similar patterns where --from and --to silently succeed when they
should fail or warn.
This issue was identified and documented with assistance from
Claude Code.
commitlint --version
@commitlint/cli@20.1.0
git --version
git version 2.51.0
node --version
v24.7.0