diff --git a/profile.d/git.sh b/profile.d/git.sh index 9b857c8..d8de35e 100755 --- a/profile.d/git.sh +++ b/profile.d/git.sh @@ -225,6 +225,111 @@ 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] diff --git a/profile.d/help.sh b/profile.d/help.sh index d0237f3..3a3058b 100644 --- a/profile.d/help.sh +++ b/profile.d/help.sh @@ -52,6 +52,7 @@ help() printf "findbig\t\tFind the biggest files in the given or current directory\n" printf "finddead\tFind dead symbolic links in the given or current directory\n" printf "findzero\tFind empty files in the given or current directory\n" + printf "gacp\t\tAdd, commit and push changes (auto-pull if needed)\n" printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n" printf "ggraph\t\tDisplay decorated git history graph\n" printf "gprune\t\tDelete local branches already merged into main branch\n"