git-cliff supports generating changelogs for monorepo projects by filtering commits based on file paths. This allows you to create package-specific changelogs in repositories containing multiple projects.
Basic Path Filtering
Generate a changelog for a specific directory by switching to that directory:
cd packages/some_library
git cliff
This generates a changelog including only commits that affect files in the current directory and its subdirectories.
Include and Exclude Paths
Use --include-path and --exclude-path for fine-grained control over which commits to include:
cd packages/some_library
git cliff --include-path "packages/some_library/**/*" --exclude-path ".github/*"
Path patterns:
- Must be relative to the repository root
- Must be valid glob patterns
- Can be specified multiple times
Path Pattern Examples
Include Specific Package
# Include only commits affecting the API package
git cliff --include-path "packages/api/**/*"
Include Multiple Paths
# Include commits from multiple packages
git cliff --include-path "packages/api/**/*" \
--include-path "packages/core/**/*"
Exclude Specific Files
# Exclude documentation and test files
git cliff --include-path "packages/api/**/*" \
--exclude-path "**/*.md" \
--exclude-path "**/tests/**/*"
Complex Filtering
# Include source files, exclude generated and config files
git cliff --include-path "packages/api/src/**/*" \
--exclude-path "**/*.config.js" \
--exclude-path "**/dist/**/*" \
--exclude-path "**/node_modules/**/*"
Monorepo Workflows
Generate Changelog for Each Package
Create separate changelogs for each package in your monorepo:
#!/bin/bash
# Define packages
PACKAGES=("api" "core" "utils" "cli")
for pkg in "${PACKAGES[@]}"; do
echo "Generating changelog for $pkg..."
git cliff \
--include-path "packages/$pkg/**/*" \
--tag "$pkg-v$(cat packages/$pkg/package.json | jq -r .version)" \
--output "packages/$pkg/CHANGELOG.md"
done
Release Specific Package
Release a single package from a monorepo:
# Calculate next version for the package
VERSION=$(git cliff --bumped-version --include-path "packages/api/**/*")
# Generate changelog
git cliff --bump \
--include-path "packages/api/**/*" \
--tag "api-v$VERSION" \
--unreleased \
--output packages/api/CHANGELOG.md
# Update package version and commit
cd packages/api
npm version $VERSION
git add .
git commit -m "chore(api): release v$VERSION"
git tag "api-v$VERSION"
Root Changelog with All Packages
Generate a root-level changelog that includes all packages:
git cliff --include-path "packages/**/*" -o CHANGELOG.md
Repository Context
When using the --repository flag with multiple repositories, you can use the {{ repository }} variable in your template to identify which changes belong to which repository:
# cliff.toml
[changelog]
body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {{ commit.message }} ({{ commit.repository }})
{% endfor %}
{% endfor %}
"""
See template context for more information.
Practical Examples
Workspace with Scoped Packages
# Project structure:
# packages/
# @company/api/
# @company/core/
# @company/utils/
# Generate changelog for @company/api
git cliff --include-path "packages/@company/api/**/*" \
--tag "@company/[email protected]" \
-o packages/@company/api/CHANGELOG.md
Frontend and Backend Monorepo
# Project structure:
# apps/
# frontend/
# backend/
# mobile/
# Generate frontend changelog
git cliff --include-path "apps/frontend/**/*" \
--exclude-path "apps/frontend/public/**/*" \
--tag "frontend-v2.0.0" \
-o apps/frontend/CHANGELOG.md
# Generate backend changelog
git cliff --include-path "apps/backend/**/*" \
--exclude-path "apps/backend/tests/**/*" \
--tag "backend-v1.5.0" \
-o apps/backend/CHANGELOG.md
Shared Libraries
# Project structure:
# libs/
# shared/
# components/
# apps/
# web/
# mobile/
# Generate changelog including shared code
git cliff --include-path "apps/web/**/*" \
--include-path "libs/shared/**/*" \
--include-path "libs/components/**/*" \
-o apps/web/CHANGELOG.md
CI/CD Integration
Detect Changed Packages
#!/bin/bash
# detect-changes.sh
# Get changed files since last release
CHANGED_FILES=$(git diff --name-only HEAD~1 HEAD)
# Determine which packages changed
if echo "$CHANGED_FILES" | grep -q "^packages/api/"; then
echo "api" >> changed-packages.txt
fi
if echo "$CHANGED_FILES" | grep -q "^packages/core/"; then
echo "core" >> changed-packages.txt
fi
# Generate changelogs only for changed packages
while read package; do
git cliff --include-path "packages/$package/**/*" \
--bump --unreleased \
-o "packages/$package/CHANGELOG.md"
done < changed-packages.txt
GitHub Actions Workflow
# .github/workflows/release-package.yml
name: Release Package
on:
push:
branches: [main]
paths:
- 'packages/api/**'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install git-cliff
run: |
wget https://github.com/orhun/git-cliff/releases/latest/download/git-cliff-x86_64-unknown-linux-gnu.tar.gz
tar -xzf git-cliff-*.tar.gz
sudo mv git-cliff /usr/local/bin/
- name: Get package version
id: version
run: |
VERSION=$(git cliff --bumped-version --include-path "packages/api/**/*")
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Generate changelog
run: |
git cliff --bump \
--include-path "packages/api/**/*" \
--tag "api-v${{ steps.version.outputs.version }}" \
--unreleased \
-o packages/api/CHANGELOG.md
- name: Create Release
uses: actions/create-release@v1
with:
tag_name: api-v${{ steps.version.outputs.version }}
release_name: API v${{ steps.version.outputs.version }}
Environment Variables
Set path filters via environment variables:
# Set include path
export GIT_CLIFF_INCLUDE_PATH="packages/api/**/*"
# Set exclude path
export GIT_CLIFF_EXCLUDE_PATH="**/*.md"
# Generate changelog
git cliff
Multiple paths with environment variables:
export GIT_CLIFF_INCLUDE_PATH="packages/api/**/* packages/core/**/*"
git cliff
Configuration File
Define path filters in your cliff.toml:
[git]
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
]
filter_commits = true
# This can be overridden with --include-path
# include_path = "packages/api/**/*"
Command-line arguments override configuration file settings.
Advanced Patterns
Glob Pattern Reference
# Match all files in a directory
"packages/api/*"
# Match all files recursively
"packages/api/**/*"
# Match specific file types
"packages/api/**/*.js"
"packages/api/**/*.{js,ts}"
# Match multiple levels
"packages/*/src/**/*"
# Exclude pattern
"!packages/api/dist/**/*"
Negative Patterns
# Include everything except tests and docs
git cliff --include-path "packages/api/**/*" \
--exclude-path "**/test/**/*" \
--exclude-path "**/tests/**/*" \
--exclude-path "**/__tests__/**/*" \
--exclude-path "**/*.test.js" \
--exclude-path "**/*.spec.js" \
--exclude-path "**/*.md"
Troubleshooting
No Commits Found
If git-cliff doesn’t find any commits:
# Verify pattern matches files
git log --oneline --name-only | grep -E "packages/api/"
# Check pattern syntax
ls packages/api/**/*
# Use absolute path from repo root
git cliff --include-path "packages/api/**/*"
Path Patterns Not Working
Ensure:
- Paths are relative to repository root, not current directory
- Patterns use forward slashes (
/) even on Windows
- Patterns are quoted to prevent shell expansion
- Glob patterns are valid
# Wrong - relative to current directory
cd packages/api
git cliff --include-path "src/**/*" # Won't work
# Correct - relative to repo root
cd packages/api
git cliff --include-path "packages/api/src/**/*" # Works
Commits Excluded Unexpectedly
If commits are being excluded:
# Test your patterns
git log --all --name-only --pretty=format: | \
grep -E "^packages/api/" | \
sort -u
# Use verbose mode to debug
git cliff -vv --include-path "packages/api/**/*"
See Also