name: 'Smart Commit with Trailer Detection'
description: 'Creates or replaces commits based on git trailer detection'
inputs:
commit_message:
description: 'Commit message (can be multiline)'
required: true
trailer_id:
description: 'Unique identifier for the trailer (e.g., changelog-update)'
required: true
branch:
description: 'Target branch'
required: true
files:
description: 'Files to commit (newline-separated). If empty, commits all changes'
required: false
default: ''
github_token:
description: 'GitHub token for API operations'
required: false
default: ${{ github.token }}
runs:
using: 'composite'
steps:
- name: Check for changes
id: check-changes
shell: bash
env:
FILES: ${{ inputs.files }}
run: |
# Check if files input is empty - if so, check all changes
if [ -z "$FILES" ] || [ "$FILES" = "" ]; then
echo "No files specified, checking all changes"
# Check if there are any changes at all
if git diff --quiet HEAD && git diff --cached --quiet HEAD; then
echo "No changes detected"
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "Changes detected"
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
else
# Convert newline-separated files to space-separated for git diff
FILES_LIST=$(echo "$FILES" | tr '\n' ' ' | sed 's/[[:space:]]*$//')
# Check if files list is empty after processing
if [ -z "$FILES_LIST" ]; then
echo "No valid files specified after processing, checking all changes"
if git diff --quiet HEAD && git diff --cached --quiet HEAD; then
echo "No changes detected"
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "Changes detected"
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
else
# Check if there are any changes in the specified files
if git diff --quiet HEAD -- $FILES_LIST && git diff --cached --quiet HEAD -- $FILES_LIST; then
echo "No changes detected in specified files"
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "Changes detected in specified files"
echo "has_changes=true" >> $GITHUB_OUTPUT
fi
fi
fi
- name: Check if HEAD has matching trailer
if: steps.check-changes.outputs.has_changes == 'true'
id: check-trailer
shell: bash
env:
TRAILER_ID: ${{ inputs.trailer_id }}
run: |
# Check if HEAD commit has our trailer using git interpret-trailers
COMMIT_MSG=$(git log -1 --format=%B)
TRAILER_VALUE=$(echo "$COMMIT_MSG" | git interpret-trailers --parse | grep "^X-Smart-Commit-ID:" | cut -d' ' -f2- || true)
if [ "$TRAILER_VALUE" = "$TRAILER_ID" ]; then
echo "HEAD has matching trailer (X-Smart-Commit-ID: $TRAILER_ID)"
echo "should_reset=true" >> $GITHUB_OUTPUT
# Store parent SHA for later use
PARENT_SHA=$(git rev-parse HEAD~1)
echo "parent_sha=$PARENT_SHA" >> $GITHUB_OUTPUT
else
echo "HEAD does not have matching trailer"
echo "should_reset=false" >> $GITHUB_OUTPUT
fi
- name: Reset to parent if needed
if: steps.check-changes.outputs.has_changes == 'true' && steps.check-trailer.outputs.should_reset == 'true'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github_token }}
BRANCH: ${{ inputs.branch }}
PARENT_SHA: ${{ steps.check-trailer.outputs.parent_sha }}
run: |
echo "Resetting branch to parent commit: $PARENT_SHA"
# Use GitHub API to update the branch reference to parent
curl -X PATCH \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${{ github.repository }}/git/refs/heads/$BRANCH" \
-d "{\"sha\":\"$PARENT_SHA\",\"force\":true}"
- name: Create commit with trailer
if: steps.check-changes.outputs.has_changes == 'true'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.github_token }}
BRANCH: ${{ inputs.branch }}
FILES: ${{ inputs.files }}
COMMIT_MESSAGE: ${{ inputs.commit_message }}
TRAILER_ID: ${{ inputs.trailer_id }}
run: |
# Create commit message with trailer
FULL_MESSAGE="${COMMIT_MESSAGE}
X-Smart-Commit-ID: ${TRAILER_ID}"
# Get the script path relative to the repository root
SCRIPT_PATH="${GITHUB_WORKSPACE}/scripts/run-ghcp.sh"
# Check if files input is empty - if so, commit all changes
if [ -z "$FILES" ] || [ "$FILES" = "" ]; then
echo "Committing all changes"
# Get all modified and new files
ALL_FILES=$(git diff --name-only HEAD; git diff --cached --name-only HEAD; git ls-files --others --exclude-standard)
ALL_FILES=$(echo "$ALL_FILES" | sort -u | tr '\n' ' ')
if [ -z "$ALL_FILES" ]; then
echo "Error: No files to commit"
exit 1
fi
# Commit using ghcp with all changed files
"$SCRIPT_PATH" -- commit \
-r "${{ github.repository }}" \
-b "$BRANCH" \
-m "$FULL_MESSAGE" \
$ALL_FILES
else
# Convert newline-separated files to space-separated
FILES_LIST=$(echo "$FILES" | tr '\n' ' ' | sed 's/[[:space:]]*$//')
if [ -z "$FILES_LIST" ]; then
echo "Error: Files list is empty after processing"
exit 1
fi
# Commit using ghcp with specified files
"$SCRIPT_PATH" -- commit \
-r "${{ github.repository }}" \
-b "$BRANCH" \
-m "$FULL_MESSAGE" \
$FILES_LIST
fi
- name: Sync local git state with remote
if: steps.check-changes.outputs.has_changes == 'true'
shell: bash
env:
BRANCH: ${{ inputs.branch }}
run: |
echo "Syncing local git state with remote branch: $BRANCH"
# Fetch the latest state of the branch
git fetch origin "$BRANCH"
# Reset the current branch to match remote
# This updates HEAD and the index without touching the working tree
git reset "origin/$BRANCH"
# Clean up the index to match HEAD (discard staged changes)
git reset --mixed HEAD