Skip to content

Instantly share code, notes, and snippets.

@chris-piekarski
Last active October 23, 2025 07:34
Show Gist options
  • Select an option

  • Save chris-piekarski/1b67d00259e53b7d478b931cc92f0017 to your computer and use it in GitHub Desktop.

Select an option

Save chris-piekarski/1b67d00259e53b7d478b931cc92f0017 to your computer and use it in GitHub Desktop.
Bash script to help sign PRs from GitHub copilot
#!/usr/bin/env bash
# Rebase and GPG/SSH sign the last N (or all unsigned) commits.
# Non-interactive, skips empty commits, with CLI flags for help and verbosity.
set -euo pipefail
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
RESET='\033[0m'
# --- Defaults ---
VERBOSE=false
DRYRUN=false
# --- Help message ---
show_help() {
cat <<EOF
${BOLD}Usage:${RESET} $(basename "$0") [N] [options]
Re-sign the last N commits (or automatically detect unsigned commits).
${BOLD}Options:${RESET}
-h, --help Show this help message and exit
-v, --verbose Print every git command executed
-n, --dry-run Show what would happen but do not modify anything
${BOLD}Examples:${RESET}
$(basename "$0") # Auto-detect unsigned commits
$(basename "$0") 5 # Re-sign last 5 commits
$(basename "$0") -v # Verbose mode
EOF
}
# --- Parse flags ---
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) show_help; exit 0 ;;
-v|--verbose) VERBOSE=true; shift ;;
-n|--dry-run) DRYRUN=true; shift ;;
[0-9]*) N="$1"; shift ;;
*) echo -e "${RED}Unknown argument:${RESET} $1"; show_help; exit 1 ;;
esac
done
# --- Git repo check ---
if ! REPO_PATH=$(git rev-parse --show-toplevel 2>/dev/null); then
echo -e "${RED}❌ Not inside a git repository.${RESET}"
exit 1
fi
cd "$REPO_PATH"
# --- Determine commit range ---
if [[ -z "${N:-}" ]]; then
echo -e "${CYAN}πŸ”Ž Searching for oldest unsigned commit...${RESET}"
LAST_UNSIGNED_LINE=$(git log --pretty="%H %G?" | grep -E " N$" | tail -n1 || true)
if [[ -z "$LAST_UNSIGNED_LINE" ]]; then
echo -e "${GREEN}βœ… All commits are signed β€” nothing to rebase.${RESET}"
exit 0
fi
LAST_UNSIGNED_HASH=$(echo "$LAST_UNSIGNED_LINE" | awk '{print $1}')
TOTAL=$(git rev-list --count "${LAST_UNSIGNED_HASH}..HEAD")
N=$((TOTAL + 1))
echo -e "${YELLOW}⚠️ Oldest unsigned commit detected:${RESET}"
git log --pretty=format:"%h %G? %s" -1 "$LAST_UNSIGNED_HASH"
echo -e "πŸ‘‰ Will rebase last ${BOLD}$N${RESET} commits."
else
echo -e "${CYAN}ℹ️ Using manual commit count: ${BOLD}$N${RESET}"
fi
# --- Verbose mode ---
if $VERBOSE; then
set -x
fi
# --- Dry-run mode ---
if $DRYRUN; then
echo -e "${YELLOW}[DRY RUN]${RESET} Would execute:"
echo "git rebase HEAD~$N --exec 'git commit --amend --no-edit -S --allow-empty'"
exit 0
fi
# --- Safety tag ---
RESTORE_TAG="pre-rebase-$(date +%Y%m%d-%H%M%S)"
git tag "$RESTORE_TAG" || true
echo -e "${CYAN}πŸ’Ύ Created restore tag:${RESET} ${BOLD}$RESTORE_TAG${RESET}"
# --- Show commits to be re-signed ---
echo -e "${CYAN}πŸ“œ Commits to be re-signed:${RESET}"
git log --pretty=format:"%h %G? %s" -$N || true
echo
read -p "$(echo -e ${YELLOW}"Proceed to re-sign last $N commits? (y/N) "${RESET})" confirm
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
echo -e "${RED}❎ Cancelled.${RESET}"
exit 0
fi
# --- Rebase operation ---
echo -e "${CYAN}πŸ” Rebasing and signing commits non-interactively...${RESET}"
git rebase HEAD~$N --exec 'git commit --amend --no-edit -S --allow-empty' || {
echo -e "${RED}❌ Rebase failed.${RESET}"
echo "To roll back: git rebase --abort || git reset --hard $RESTORE_TAG"
exit 1
}
# --- Verify ---
echo -e "${GREEN}βœ… Rebase complete. Verifying signatures:${RESET}"
git log --pretty=format:"%h %G? %s" -$N
echo
read -p "$(echo -e ${YELLOW}"Push changes with --force-with-lease? (y/N) "${RESET})" pushconfirm
if [[ "$pushconfirm" =~ ^[Yy]$ ]]; then
git push --force-with-lease
echo -e "${GREEN}πŸŽ‰ Push complete.${RESET}"
else
echo -e "${YELLOW}πŸ•’ Push skipped.${RESET}"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment