|
|
|
|
@@ -0,0 +1,602 @@
|
|
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
|
|
|
|
|
# Protected by the BSD3 license. Please read bellow for details.
|
|
|
|
|
#
|
|
|
|
|
# * Redistribution and use in source and binary forms,
|
|
|
|
|
# * with or without modification, are permitted provided
|
|
|
|
|
# * that the following conditions are met:
|
|
|
|
|
# *
|
|
|
|
|
# * Redistributions of source code must retain the above
|
|
|
|
|
# * copyright notice, this list of conditions and the
|
|
|
|
|
# * following disclaimer.
|
|
|
|
|
# *
|
|
|
|
|
# * Redistributions in binary form must reproduce the above
|
|
|
|
|
# * copyright notice, this list of conditions and the following
|
|
|
|
|
# * disclaimer in the documentation and/or other materials
|
|
|
|
|
# * provided with the distribution.
|
|
|
|
|
# *
|
|
|
|
|
# * Neither the name of the copyright holder nor the names
|
|
|
|
|
# * of any other contributors may be used to endorse or
|
|
|
|
|
# * promote products derived from this software without
|
|
|
|
|
# * specific prior written permission.
|
|
|
|
|
# *
|
|
|
|
|
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
|
|
|
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
|
|
|
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
|
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
|
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
|
|
|
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
|
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
|
|
|
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
|
|
|
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
|
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
|
|
|
# * OF SUCH DAMAGE.
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
# Built-in defaults (can be overridden from [git] section in profile.conf)
|
|
|
|
|
: "${GIT_MAIN_BRANCH:=main}"
|
|
|
|
|
: "${GIT_DEFAULT_REMOTE:=origin}"
|
|
|
|
|
: "${GIT_WIP_PREFIX:=wip}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Internal helper: ensure git is available and cwd is a git worktree
|
|
|
|
|
_git_require_repo()
|
|
|
|
|
{
|
|
|
|
|
if ! command -v git >/dev/null 2>&1; then
|
|
|
|
|
disp E "git command not found."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
|
|
|
disp E "Current directory is not inside a git repository."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Internal helper: return default branch from remote HEAD, fallback to config
|
|
|
|
|
_git_default_branch()
|
|
|
|
|
{
|
|
|
|
|
local remote="${1:-$GIT_DEFAULT_REMOTE}"
|
|
|
|
|
local head
|
|
|
|
|
|
|
|
|
|
head=$(git symbolic-ref --quiet --short "refs/remotes/${remote}/HEAD" 2>/dev/null) || true
|
|
|
|
|
if [[ -n $head ]]; then
|
|
|
|
|
printf "%s\n" "${head#${remote}/}"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
printf "%s\n" "$GIT_MAIN_BRANCH"
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Display compact git status + branch tracking information
|
|
|
|
|
# Usage: gst [path]
|
|
|
|
|
gst()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o h --long help -n 'gst' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"gst --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "gst: Display short git status and branch tracking info.\n"
|
|
|
|
|
printf "Usage: gst [path]\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"gst --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
local target="${1:-.}"
|
|
|
|
|
git -C "$target" status --short --branch
|
|
|
|
|
}
|
|
|
|
|
export -f gst
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Show a readable commit graph
|
|
|
|
|
# Usage: ggraph [-n limit]
|
|
|
|
|
ggraph()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o hn: --long help,limit: -n 'ggraph' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"ggraph --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
local limit=30
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "ggraph: Display decorated git history graph.\n"
|
|
|
|
|
printf "Usage: ggraph [-n limit]\n"
|
|
|
|
|
printf "Options:\n"
|
|
|
|
|
printf "\t-n, --limit\tNumber of commits to display (default: 30)\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
-n|--limit)
|
|
|
|
|
limit="$2"
|
|
|
|
|
shift 2
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"ggraph --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
[[ $limit =~ ^[0-9]+$ ]] || {
|
|
|
|
|
disp E "Invalid limit: must be a positive integer."
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
git log --graph --decorate --oneline --all --max-count="$limit"
|
|
|
|
|
}
|
|
|
|
|
export -f ggraph
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Sync current branch with remote (fetch + rebase)
|
|
|
|
|
# Usage: gsync [remote]
|
|
|
|
|
gsync()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o h --long help -n 'gsync' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"gsync --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "gsync: Fetch and rebase current branch onto its remote tracking branch.\n"
|
|
|
|
|
printf "Usage: gsync [remote]\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"gsync --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
|
|
|
|
|
local remote="${1:-$GIT_DEFAULT_REMOTE}"
|
|
|
|
|
local branch upstream
|
|
|
|
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
|
|
|
|
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
|
|
|
|
|
|
|
|
|
|
disp I "Fetching from $remote..."
|
|
|
|
|
git fetch --prune "$remote" || return 1
|
|
|
|
|
|
|
|
|
|
if [[ -z $upstream ]]; then
|
|
|
|
|
disp W "No upstream configured for $branch, skipping rebase."
|
|
|
|
|
disp I "Set one with: git branch --set-upstream-to ${remote}/${branch} ${branch}"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
disp I "Rebasing $branch onto $upstream..."
|
|
|
|
|
git rebase "$upstream"
|
|
|
|
|
}
|
|
|
|
|
export -f gsync
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Add, commit, and push changes with automatic pull/rebase if needed
|
|
|
|
|
# Usage: gacp -m "message" [file1 file2 ...]
|
|
|
|
|
gacp()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o hm: --long help,message: -n 'gacp' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"gacp --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
local msg=""
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "gacp: Run git add, git commit, and git push in one command.\n"
|
|
|
|
|
printf "Usage: gacp -m \"message\" [file1 file2 ...]\n"
|
|
|
|
|
printf "Options:\n"
|
|
|
|
|
printf "\t-m, --message\tCommit message (mandatory)\n"
|
|
|
|
|
printf "\n"
|
|
|
|
|
printf "If files are provided, only those paths are added.\n"
|
|
|
|
|
printf "If no file is provided, all changes are added with git add -A.\n"
|
|
|
|
|
printf "If the remote branch moved forward, gacp pulls with rebase before pushing.\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
-m|--message)
|
|
|
|
|
msg="$2"
|
|
|
|
|
shift 2
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"gacp --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
|
|
|
|
|
if [[ -z $msg ]]; then
|
|
|
|
|
disp E "Missing commit message. Use -m or --message."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
local branch upstream remote tracking_branch ahead behind counts
|
|
|
|
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
|
|
|
|
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
|
|
|
|
|
|
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
|
|
|
disp I "Adding selected paths..."
|
|
|
|
|
git add -- "$@" || return 1
|
|
|
|
|
else
|
|
|
|
|
disp I "Adding all changes..."
|
|
|
|
|
git add -A || return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if git diff --cached --quiet; then
|
|
|
|
|
disp W "No staged changes to commit."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
disp I "Creating commit..."
|
|
|
|
|
git commit -m "$msg" || return 1
|
|
|
|
|
|
|
|
|
|
if [[ -n $upstream ]]; then
|
|
|
|
|
remote="${upstream%%/*}"
|
|
|
|
|
tracking_branch="${upstream#*/}"
|
|
|
|
|
else
|
|
|
|
|
remote="$GIT_DEFAULT_REMOTE"
|
|
|
|
|
tracking_branch="$branch"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
disp I "Fetching from $remote..."
|
|
|
|
|
git fetch --prune "$remote" || return 1
|
|
|
|
|
|
|
|
|
|
if git rev-parse --verify --quiet "refs/remotes/${remote}/${tracking_branch}" >/dev/null; then
|
|
|
|
|
counts=$(git rev-list --left-right --count HEAD..."${remote}/${tracking_branch}" 2>/dev/null) || return 1
|
|
|
|
|
read -r ahead behind <<< "$counts"
|
|
|
|
|
if [[ ${behind:-0} -gt 0 ]]; then
|
|
|
|
|
disp I "Remote branch is ahead, rebasing before push..."
|
|
|
|
|
git pull --rebase "$remote" "$tracking_branch" || return 1
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ -n $upstream ]]; then
|
|
|
|
|
disp I "Pushing to $upstream..."
|
|
|
|
|
git push || return 1
|
|
|
|
|
else
|
|
|
|
|
disp I "Pushing and setting upstream to ${remote}/${branch}..."
|
|
|
|
|
git push -u "$remote" "$branch" || return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
disp I "gacp complete."
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
export -f gacp
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Reset local branch to exact upstream state (stash local changes first)
|
|
|
|
|
# Usage: greset [target]
|
|
|
|
|
greset()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o hx --long help,with-ignored -n 'greset' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"greset --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
local clean_ignored=0
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "greset: Reset current branch to upstream, stashing local changes first.\n"
|
|
|
|
|
printf "Usage: greset [target]\n"
|
|
|
|
|
printf "Options:\n"
|
|
|
|
|
printf "\t-x, --with-ignored\tAlso remove ignored files (git clean -fdx)\n"
|
|
|
|
|
printf "\n"
|
|
|
|
|
printf "Default target is current branch upstream (@{u}).\n"
|
|
|
|
|
printf "If no upstream exists, fallback target is <remote>/<branch>.\n"
|
|
|
|
|
printf "This command stashes local modifications (tracked + untracked),\n"
|
|
|
|
|
printf "drops local unpushed commits by hard-reset, and cleans untracked files.\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
-x|--with-ignored)
|
|
|
|
|
clean_ignored=1
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"greset --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
|
|
|
|
|
local branch upstream target remote old_head stash_msg stash_out stash_created=0 dropped=0
|
|
|
|
|
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
|
|
|
|
upstream=$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null) || true
|
|
|
|
|
target="$1"
|
|
|
|
|
|
|
|
|
|
if [[ -z $target ]]; then
|
|
|
|
|
if [[ -n $upstream ]]; then
|
|
|
|
|
target="$upstream"
|
|
|
|
|
else
|
|
|
|
|
remote="$GIT_DEFAULT_REMOTE"
|
|
|
|
|
target="${remote}/${branch}"
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ -z $remote ]]; then
|
|
|
|
|
remote="${target%%/*}"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
old_head=$(git rev-parse HEAD 2>/dev/null) || return 1
|
|
|
|
|
|
|
|
|
|
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
|
|
|
|
|
stash_msg="greset:${branch}:$(date +'%Y-%m-%d %H:%M:%S')"
|
|
|
|
|
disp I "Stashing local changes as '$stash_msg'..."
|
|
|
|
|
stash_out=$(git stash push -u -m "$stash_msg" 2>&1) || {
|
|
|
|
|
disp E "Failed to stash local changes."
|
|
|
|
|
printf "%s\n" "$stash_out"
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
[[ $stash_out != "No local changes to save"* ]] && stash_created=1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
disp I "Fetching from $remote..."
|
|
|
|
|
git fetch --prune "$remote" || return 1
|
|
|
|
|
|
|
|
|
|
if ! git rev-parse --verify --quiet "$target" >/dev/null; then
|
|
|
|
|
disp E "Target '$target' does not exist."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
dropped=$(git rev-list --count "${target}..${old_head}" 2>/dev/null || printf "0")
|
|
|
|
|
|
|
|
|
|
disp W "Hard-resetting $branch to $target..."
|
|
|
|
|
git reset --hard "$target" || return 1
|
|
|
|
|
|
|
|
|
|
if (( clean_ignored )); then
|
|
|
|
|
git clean -fdx || return 1
|
|
|
|
|
else
|
|
|
|
|
git clean -fd || return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if (( stash_created )); then
|
|
|
|
|
disp I "Local changes were stashed. Use 'git stash list' and 'git stash pop' when needed."
|
|
|
|
|
fi
|
|
|
|
|
disp I "greset complete. Dropped local-only commits: $dropped"
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
export -f greset
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Create a quick WIP commit for local checkpointing
|
|
|
|
|
# Usage: gwip [message]
|
|
|
|
|
gwip()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o h --long help -n 'gwip' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"gwip --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "gwip: Create a local checkpoint commit with all tracked/untracked changes.\n"
|
|
|
|
|
printf "Usage: gwip [message]\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"gwip --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
|
|
|
|
|
local msg
|
|
|
|
|
if [[ $# -gt 0 ]]; then
|
|
|
|
|
msg="$*"
|
|
|
|
|
else
|
|
|
|
|
msg="$GIT_WIP_PREFIX: $(date +'%Y-%m-%d %H:%M:%S')"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
git add -A || return 1
|
|
|
|
|
git commit -m "$msg"
|
|
|
|
|
}
|
|
|
|
|
export -f gwip
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Delete merged local branches (except protected branches)
|
|
|
|
|
# Usage: gprune [main-branch]
|
|
|
|
|
gprune()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o h --long help -n 'gprune' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"gprune --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "gprune: Delete local branches already merged into main branch.\n"
|
|
|
|
|
printf "Usage: gprune [main-branch]\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"gprune --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
|
|
|
|
|
local base="${1:-$(_git_default_branch "$GIT_DEFAULT_REMOTE")}" current deleted=0
|
|
|
|
|
current=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
|
|
|
|
|
|
|
|
|
disp I "Pruning branches merged into $base..."
|
|
|
|
|
|
|
|
|
|
while IFS= read -r b; do
|
|
|
|
|
[[ -z $b ]] && continue
|
|
|
|
|
[[ $b == "$current" ]] && continue
|
|
|
|
|
[[ $b == "$base" ]] && continue
|
|
|
|
|
[[ $b == "master" || $b == "main" || $b == "develop" || $b == "dev" ]] && continue
|
|
|
|
|
git branch -d "$b" >/dev/null 2>&1 && {
|
|
|
|
|
printf "Deleted: %s\n" "$b"
|
|
|
|
|
((deleted++))
|
|
|
|
|
}
|
|
|
|
|
done < <(git branch --merged "$base" | sed -E 's/^\*?\s*//')
|
|
|
|
|
|
|
|
|
|
(( deleted == 0 )) && disp I "No merged branches to delete."
|
|
|
|
|
}
|
|
|
|
|
export -f gprune
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
# Print repository root path
|
|
|
|
|
# Usage: groot
|
|
|
|
|
groot()
|
|
|
|
|
{
|
|
|
|
|
local PARSED
|
|
|
|
|
PARSED=$(getopt -o hg --long help,go -n 'groot' -- "$@")
|
|
|
|
|
# shellcheck disable=SC2181 # getopt return code is checked immediately after
|
|
|
|
|
if [[ $? -ne 0 ]]; then
|
|
|
|
|
disp E "Invalid options, use \"groot --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
eval set -- "$PARSED"
|
|
|
|
|
local do_go=0
|
|
|
|
|
while true; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
-h|--help)
|
|
|
|
|
printf "groot: Display the absolute path of the current repository root.\n"
|
|
|
|
|
printf "Usage: groot [-g|--go]\n"
|
|
|
|
|
printf "Options:\n"
|
|
|
|
|
printf "\t-g, --go\tChange current directory to repository root\n"
|
|
|
|
|
return 0
|
|
|
|
|
;;
|
|
|
|
|
-g|--go)
|
|
|
|
|
do_go=1
|
|
|
|
|
shift
|
|
|
|
|
;;
|
|
|
|
|
--)
|
|
|
|
|
shift
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
*)
|
|
|
|
|
disp E "Invalid options, use \"groot --help\" to display usage."
|
|
|
|
|
return 1
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
_git_require_repo || return 1
|
|
|
|
|
local root
|
|
|
|
|
root=$(git rev-parse --show-toplevel) || return 1
|
|
|
|
|
|
|
|
|
|
if (( do_go )); then
|
|
|
|
|
cd "$root" || {
|
|
|
|
|
disp E "Failed to move to repository root: $root"
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
printf "%s\n" "$root"
|
|
|
|
|
}
|
|
|
|
|
export -f groot
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
load_conf git
|
|
|
|
|
|
|
|
|
|
# EOF
|