Skip to content

Instantly share code, notes, and snippets.

@imaurer
Last active January 27, 2025 14:43
Show Gist options
  • Save imaurer/28d3de5392cc5f4231973ce006a3ff30 to your computer and use it in GitHub Desktop.
Save imaurer/28d3de5392cc5f4231973ce006a3ff30 to your computer and use it in GitHub Desktop.
Script to sync git repo and push with LLM-drafted git message
#!/bin/bash
#
# gpush: Lazy git add, commit, and push with comments written by LLM
#
#
# This script provides an enhanced git push workflow with the following features:
# - Automatically pulls latest changes from the current branch
# - Checks for uncommitted changes
# - Generates commit messages using AI (LLM/Gemini) if no message is provided
# - Allows interactive editing of auto-generated commit messages
# - Commits and pushes changes in a single command
# - Provides informative emojis and error handling
#
# Usage:
# gpush # Auto-generate commit message
# gpush "Commit message" # Use provided commit message
# gpush --no-edit # Auto-generate commit message and skip edit
#
# Dependencies:
# - git
# - uvx
# - llm/gemini CLI tool
# - Default text editor (vim/nano/etc.)
#
# I have an `llm` template called `commit_msg` with the following:
#
# $ llm templates show commit_msg
# model: gemini-1.5-flash-002
# name: commit_msg
# system: Create commit message for code git diff. Focus on essential changes, be laconic.
#
# License: Public domain. No warranty of any kind. Use at your own risk.
# Author: Ian Maurer (https://imaurer.com) and Claude Sonnet 3.5
#
# Exit on any error
set -euo pipefail
RED="\033[0;31m"
GREEN="\033[0;32m"
YELLOW="\033[0;33m"
CYAN="\033[0;36m"
NC="\033[0m"
usage() {
echo "Usage: $(basename "$0") [--no-edit] [commit message]"
echo " - If no commit message is provided, an AI-generated one is created."
exit 1
}
NO_EDIT=false
commit_message=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--no-edit)
NO_EDIT=true
shift
;;
-h|--help)
usage
;;
*)
commit_message="$1"
shift
;;
esac
done
#######################################
# Confirm we are in a Git repository.
#######################################
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
echo -e "${RED}Error: Not in a Git repository.${NC}"
exit 1
fi
#######################################
# Identify current branch.
#######################################
current_branch=$(git rev-parse --abbrev-ref HEAD)
if [ "$current_branch" = "HEAD" ]; then
echo -e "${RED}Error: Detached HEAD. Please checkout a branch.${NC}"
exit 1
fi
#######################################
# Ensure uvx/llm is installed
#######################################
if ! command -v uvx &>/dev/null; then
echo -e "${RED}Error: uvx is required but not installed or not in PATH.${NC}"
exit 1
fi
#######################################
# FUNCTION: check or set up a remote tracking branch
#######################################
ensure_tracking_branch() {
# If this succeeds, there's already an upstream set
if git rev-parse --abbrev-ref --symbolic-full-name "@{u}" >/dev/null 2>&1; then
return 0
fi
# If we're here, there's NO remote tracking branch set
echo -e "${YELLOW}No remote tracking branch for '${current_branch}'. Attempting to set upstream...${NC}"
# Try pushing with -u
if ! git push -u origin "$current_branch" 2>/dev/null; then
echo -e "${YELLOW}Push -u failed. The remote may have commits we don't have. Attempting to pull...${NC}"
# Attempt a normal pull from origin
if ! git pull origin "$current_branch"; then
echo -e "${RED}Automatic pull failed. Please resolve merges or conflicts manually and then push.${NC}"
exit 1
fi
# Now try again
if ! git push -u origin "$current_branch"; then
echo -e "${RED}Could not set remote tracking branch automatically. Please fix manually.${NC}"
exit 1
fi
fi
echo -e "${GREEN}Remote tracking branch successfully set for '${current_branch}'.${NC}"
}
#######################################
# Pull latest changes from remote to ensure we are up to date.
#######################################
update_from_remote() {
echo -e "${CYAN}Pulling latest changes from origin/${current_branch}...${NC}"
# If ff-only fails, we can optionally try a normal pull.
# But let's start with ff-only to avoid accidental merges.
if ! git pull --ff-only origin "$current_branch"; then
echo -e "${YELLOW}Fast-forward only pull failed. Attempting normal pull...${NC}"
if ! git pull origin "$current_branch"; then
echo -e "${RED}Pull failed due to merge conflicts or other issues. Please resolve manually.${NC}"
exit 1
fi
fi
}
#######################################
# MAIN LOGIC
#######################################
# 1) Ensure we have a remote tracking branch
ensure_tracking_branch
# 2) Pull from remote
update_from_remote
# 3) Check if there are changes
if [ -z "$(git status --porcelain)" ]; then
echo "❌ No changes to commit. Working tree clean."
exit 0
fi
echo "📝 Adding all changes..."
git add .
# 4) Double-check if anything actually changed
if [ -z "$(git diff HEAD)" ] && [ -z "$(git diff --staged)" ]; then
echo -e "${YELLOW}No changes to commit. Working tree is clean.${NC}"
exit 0
fi
echo -e "${CYAN}Adding all changes...${NC}"
git add .
# 5) Generate commit message if none is provided
if [ -z "$commit_message" ]; then
echo -e "${CYAN}Generating commit message via LLM...${NC}"
# Use your llm template "commit_msg" with uvx
commit_message="$(git diff HEAD -U15 | uvx --with llm-gemini --with llm llm -t commit_msg)"
if [ "$NO_EDIT" = false ]; then
temp_file="$(mktemp)"
echo "$commit_message" > "$temp_file"
echo -e "${CYAN}Opening editor for review...${NC}"
"${EDITOR:-vim}" "$temp_file"
commit_message="$(cat "$temp_file")"
rm "$temp_file"
if [ -z "$(echo "$commit_message" | tr -d '[:space:]')" ]; then
echo -e "${RED}Error: Empty commit message after editing.${NC}"
exit 1
fi
fi
fi
# 6) Commit
temp_file="$(mktemp)"
echo "$commit_message" > "$temp_file"
echo -e "${CYAN}Committing changes...${NC}"
git commit -F "$temp_file"
rm "$temp_file"
# 7) Push
echo -e "${CYAN}Pushing to origin/${current_branch}...${NC}"
if ! git push origin "$current_branch"; then
# If push fails, possibly the remote changed again or rejections.
# We'll do a final fallback attempt to pull & push.
echo -e "${YELLOW}Push failed, attempting to pull & push again...${NC}"
if ! git pull origin "$current_branch"; then
echo -e "${RED}Pull failed. Please fix conflicts and push manually.${NC}"
exit 1
fi
if ! git push origin "$current_branch"; then
echo -e "${RED}Push still failing. Please investigate manually.${NC}"
exit 1
fi
fi
echo -e "${GREEN}✅ All done!${NC}"
@bartkamphuis
Copy link

Thank you for this. For me this is an immediate time-saver, with better repo's to boot.

Steps I took to use local ollama server:

llm templates edit commit_msg
model: llama3.2
name: commit_msg
system: Create commit message for code git diff. Focus on essential changes, be laconic. Only output a single commit message, and nothing else.

For Ollama change 117

commit_message="$(git diff HEAD -U15 | uvx --with llm-ollama --with llm llm -t commit_msg)"

@imaurer
Copy link
Author

imaurer commented Jan 27, 2025

Thanks @bartkamphuis ! Line 117 is on revision 5 for anyone in the future.
Will be updating as I run into issues with branching and merging issues over time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment