Last active
January 27, 2025 14:43
-
-
Save imaurer/28d3de5392cc5f4231973ce006a3ff30 to your computer and use it in GitHub Desktop.
Script to sync git repo and push with LLM-drafted git message
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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}" |
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
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:
For Ollama change 117