#!/bin/bash # Log output: # # * 51c333e (12 days) add vim-eunuch # # The time massaging regexes start with ^[^<]* because that ensures that they # only operate before the first "<". That "<" will be the beginning of the # author name, ensuring that we don't destroy anything in the commit message # that looks like time. # # The log format uses } characters between each field, and `column` is later # used to split on them. A } in the commit subject or any other field will # break this. HASH="%C(yellow)%h%Creset" RELATIVE_TIME="%Cgreen(%ar)%Creset" AUTHOR="%C(bold blue)<%an>%Creset" REFS="%C(red)%d%Creset" SUBJECT="%s" DEFAULT_REMOTE="origin" ROOT_BRANCH="master" MAIN_BRANCH="main" DEVELOP_BRANCH="development" FORMAT="$HASH}$RELATIVE_TIME}$AUTHOR}$REFS $SUBJECT" RED="\033[0;31m" GREEN="\033[0;32m" YELLOW="\033[0;33m" CYAN="\033[0;36m" CRESET="\033[0m" pretty_git_log() { git log --graph --pretty="tformat:${FORMAT}" "$@" | # Replace (2 years ago) with (2 years) #sed -Ee 's/(^[^<]*) ago)/\1)/' | sed -e 's/ ago//' | # Replace (2 years, 5 months) with (2 years) #sed -Ee 's/(^[^<]*), [[:digit:]]+ .*months?)/\1)/' | sed -e 's/(^[^<]*), [[:digit:]]+ .*months?)/)/' | # Line columns up based on } delimiter column -s '}' -t | # Page only if we need to less -FXRS } sync_to_remote() { REMOTE="${1:-${DEFAULT_REMOTE}}" BRANCH=$(git rev-parse --abbrev-ref HEAD) echo "Synching with ${YELLOW}${REMOTE}/${BRANCH}${CRESET}" git fetch "${REMOTE}" && git rebase "${REMOTE}/${BRANCH}"; } update_branch() { BRANCH="${1:-${MAIN_BRANCH}}" REMOTE="${2:-${DEFAULT_REMOTE}}" echo "Fetching ${YELLOW}${REMOTE}/${BRANCH}${CRESET} and rebasing with autostash" # 1. Update the local ref for the target branch (worktree-safe) git fetch "$REMOTE" "$BRANCH" # 2. Rebase with integrated stash management # --autostash: hides dirty files, rebases, then pops them back if git rebase --autostash "$REMOTE/$BRANCH"; then echo "${GREEN}Successfully updated and reapplied changes${CRESET}" git status --short else echo "${RED}Rebase conflict detected!${CRESET}" echo "1. Fix conflicts -> git rebase --continue" echo "2. Or abort -> git rebase --abort" echo " If aborting, don't forget your stashed changes in the stash list" return 1 fi } get_pr() { PR=$1 REMOTE="${2:-${DEFAULT_REMOTE}}" if [ -z "$PR" ]; then echo "${RED}Please specify a PR to checkout${CRESET}" exit 1 fi echo Checking out PR "${CYAN}${PR}${CRESET}" from "${YELLOW}${REMOTE}${CRESET}" git fetch "${REMOTE}" "pull/${PR}/head:pr/${PR}" # Optimized count using grep -c WT_COUNT=$(git worktree list --porcelain | grep -c "^worktree") if [ "$WT_COUNT" -gt 1 ]; then WT_NAME="PR-$PR" echo "Worktrees detected. ${CYAN}Creating ${WT_NAME}${CRESET}" git worktree add "PR-$PR" "pr/${PR}" return fi # Fallback to normal checkout if no worktrees or user opts out git checkout "pr/$PR" } del_pr() { BRANCH=$(git rev-parse --abbrev-ref HEAD) if [[ ! ${BRANCH} =~ ^pr\/ ]]; then echo "${RED}Not a PR branch, aborting!${CRESET}" exit 1 fi if [ "$(git worktree list --porcelain | grep -c "^worktree")" -gt 1 ]; then WT_PATH=$(git rev-parse --show-toplevel) # echo "Worktrees detected. ${CYAN}Removing ${WT_PATH}${CRESET}" git worktree remove "$WT_PATH" --force && git branch -D "$BRANCH" echo "PR branch ${CYAN}${BRANCH}${CRESET} deleted and worktree ${CYAN}$(basename "$WT_PATH")${CRESET} removed" echo "** ${GREEN}'cd ..' to return to the main repo${CRESET} **" return fi # Fallback to normal branch deletion if no worktrees or user opts out git checkout - && git branch -D "${BRANCH}" } clean_branches() { BRANCHES=$(git branch | grep -v "${ROOT_BRANCH}\|${MAIN_BRANCH}\|${DEVELOP_BRANCH}\|\*") for i in ${BRANCHES}; do git branch -d "$i" done } backport_pr() { echo "THIS NEEDS WORK - DO NOT USE YET" return 1 # PR=$1 # BRANCH=$2 # [ -z "$BRANCH" ] && BRANCH=$(git rev-parse --abbrev-ref HEAD) # REMOTE="${3:-${DEFAULT_REMOTE}}" # THISBRANCH=$(git rev-parse --abbrev-ref HEAD) # URL="https://patch-diff.githubusercontent.com/raw/elastic/kibana/pull/${PR}.patch" # if [ -z "$PR" ]; then # echo "Please specify a PR to backport" # exit 1 # fi # echo "Backporting ${CYAN}${PR}${CRESET} to ${YELLOW}${BRANCH}${CRESET} from ${YELLOW}${REMOTE}${CRESET}" # # if the backport couldn't be cleanly applied, tell the user and exit # if ! git checkout "${BRANCH}" && git pull "${REMOTE}" "${BRANCH}" && curl -L -s "$URL" | git am # then # echo "FAILED - Backport could not be cleanly applied!" # echo "FAILED - Fix by hand or run 'git am --abort'" # exit 2 # fi # # switch back if we didn't start in the target branch # [ "$THISBRANCH" != "$BRANCH" ] && git checkout - } track_remote() { REMOTE="${1:-${DEFAULT_REMOTE}}" BRANCH=$2 [ -z "$BRANCH" ] && BRANCH=$(git rev-parse --abbrev-ref HEAD) echo "Setting ${CYAN}${BRANCH}${CRESET} to track ${YELLOW}${REMOTE}/${BRANCH}${CRESET}" git branch --set-upstream-to="${REMOTE}/${BRANCH}" "${BRANCH}" } SNAPSHOT_DIR="$HOME/.git-snapshots" create_snap() { NAME=$1 if [ -z "$NAME" ]; then echo "${RED}Error: Please specify a name for the snap.${CRESET}" return 1 fi mkdir -p "$SNAPSHOT_DIR" FILE="$SNAPSHOT_DIR/${NAME}.patch" if [ -f "$FILE" ]; then echo "${RED}Error: Snap '$NAME' already exists.${CRESET}" return 1 fi git diff --cached > "$FILE" && git reset } apply_snap() { NAME=$1 FILE="$SNAPSHOT_DIR/${NAME}.patch" if [ ! -f "$FILE" ]; then echo "${RED}Error: Snap '$NAME' not found.${CRESET}" return 1 fi echo "Applying snap ${CYAN}${NAME}${CRESET}" git apply "$FILE" } list_snaps() { echo "Snapshots in ${CYAN}$SNAPSHOT_DIR${CRESET}" find "$SNAPSHOT_DIR" -maxdepth 1 -mindepth 1 -print0 | while IFS= read -r -d '' file; do # $file is now safe to use, even with spaces or newlines echo " $(basename "$file" .patch)" done } revert_snap() { NAME=$1 FILE="$SNAPSHOT_DIR/${NAME}.patch" if [ ! -f "$FILE" ]; then echo "${RED}Error: Snap '$NAME' not found.${CRESET}" return 1 fi echo "Reverting snap ${CYAN}${NAME}${CRESET}" git apply --reverse "$FILE" } remove_snap() { NAME=$1 FILE="$SNAPSHOT_DIR/${NAME}.patch" if [ ! -f "$FILE" ]; then echo "${RED}Error: Snap '$NAME' not found.${CRESET}" return 1 fi echo "Remove snap ${CYAN}${NAME}${CRESET} ? (y/n)" read -r a if [[ "$a" == [yY]* ]]; then rm "$FILE" else echo "-- Aborted" fi } show_snap() { NAME=$1 FILE="$SNAPSHOT_DIR/${NAME}.patch" if [ ! -f "$FILE" ]; then echo "${RED}Error: Snap '$NAME' not found.${CRESET}" return 1 fi cat "$FILE" }