Boost your Git Squash-Rebase Workflow with these Commands! (auto-squash, auto-commit, auto-rebase, auto-push)

Search for a command to run...

No comments yet. Be the first to comment.
Transferring files between your local machine and a virtual machine (VM) is one of the most common tasks in tech. We often use scp because it comes natively installed and has similar utilization to ssh, but it is the clunkiest tool for the job. In th...

Alias Alchemy is a Deno-powered server that fetches aliases from a GitHub repository based on your query. Think of it as your instant alias spellbook, perfect for setting up new environments in seconds. ⚡ Usage: $ alias-alchemy.ra101.dev/?q=py,shell,...

Reader discretion is advised!A good Sus-Saturday to all. In this article, I drag and drown you with my paranoia about AI. Let the mass psychosis begin! What are AI hallucinations?AI hallucinations are incorrect or misleading results that AI models ge...

Aim: To create an iterable blockchain, which could work with an extendable transaction class. Here the link GitHub repo! Blockchain in Brief: Blockchain is fundamentally a ledger maintaining records of transaction, This ledger is publicly distribute...

I recently joined a company, that follows squash-rebase flow here, and as my Git KT was going on, I figured I should build these commands before actually starting development, now that I have started development, these turned out to be quite handy!
Git Squash Rebase is a workflow in which:
multiple commits are squashed into a single cohesive commit,
then rebased from the up-to-date main branch.
This results in a cleaner commit history, grouping of changes, easier code reviews, and streamlined collaboration. Of-course there are some drawbacks of this workflow, but that is beyond the scope of this post.
gsq):gsq(){
# Usage:
## gsq -b "base_branch_opt" -n "squash_count_opt" -m "msg" -d "desc" -D
##
## -b: Base Branch Name (default: default origin branch)
## -n: Squash Count (default: all until base branch)
## -m: Commit Message, (default: latest commit message)
## - Uses Auto Commit Command, if present.
## -d: Commit Description, (default: null)
## -D: if passed, the description is populated with all the previous commit msg and desc until sqaush_count
# Get Base Branch -> Get Fork Point -> Get Squashable Count -> Squash!
## Even though this command seems big, but the flow is pretty simple.
## I use a very lite version of this code, there is alot of validation and
## edges cases here, which is not particularly I care about in my personal use.
# Declare Local Var
local base_branch_opt; local squash_count_opt; local commit_msg_opt;
local commit_desc_opt; local add_full_desc_opt=false;
# Get Optional Arguments!
## Reset the getopts state
OPTIND=1
## Parse Through new opts.
while getopts b:n:m:d:D flag
do
case "${flag}" in
b) base_branch_opt=${OPTARG};;
n) squash_count_opt=${OPTARG};;
m) commit_msg_opt=${OPTARG};;
d) commit_desc_opt=${OPTARG};;
D) add_full_desc_opt=true;;
esac
done
# Get Base Branch
# ----------------
## Update base_branch_opt with default origin branch, if not passed!
local current_branch=$(git rev-parse --abbrev-ref HEAD)
if [[ $base_branch_opt == "" ]]; then
base_branch_opt=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')
fi
# Get Fork Point Commit
# ----------------
# Check if given base branch is actually an ancestor or not.
local fork_point_commit=""
if [[ $( git branch --merged HEAD 2> /dev/null | sed -e 's/* \(.*\)/ \1/' -e 's/^ *//g' | grep -w "$base_branch_opt" ) != "" ]]; then
### Get the Last commit by `base_branch_opt`
fork_point_commit=$(git merge-base --fork-point "$base_branch_opt")
else
### Throw Error if Base Branch is not found as an ancestor.
echo "Invalid Base Branch! " && return 1
fi
# Get Squashable Count
# ----------------
## Exit, if Squashable Count is 0!
## We are okay with squashing 1 commit, the reason for that is, this allows us
## to stage the changes and use `gsq` to commit those changes into squashed commit
## squashable_count: Number of commits that can be to be squashed
local current_commit=$(git rev-parse HEAD)
local squashable_count=$(git rev-list --count $fork_point_commit..$current_commit)
## Exit, if passed squash_count_opt is `0` or not a `number`!
if [[ $squash_count_opt != "" && ( ! $squash_count_opt =~ ^[0-9]+$ || $squash_count_opt == 0 )]]; then
echo "Invalid Squash Count $squash_count_opt! " && return 1
elif [[ $squash_count_opt == "" ]]; then
squash_count_opt=$squashable_count
fi
## Exit, if found `squashable_count` turns about to be `0` !
if [[ $squashable_count == "0" ]]; then
echo "Nothing to Squash! " && return 1
fi
## Squash Count Passed cannot be more than Squashable Count found!
if [[ $squashable_count < $squash_count_opts ]]; then
echo "Cannot Squash more than $squashable_count! " && return 1
fi
# Squash!: Get Commit Message and Description.
# ----------------
## Get Commit Message, (Default: title from latest commit)
if [[ $commit_msg_opt != "" ]]; then
local commit_message=$commit_msg_opt
else
local commit_message=$(git log --format="%s" -n1)
fi
## Get Full Commit Description, (Default: raw body from last n commit)
if [[ $add_full_desc_opt == true ]]; then
if [[ $commit_desc_opt != "" ]]; then
commit_desc_opt="$(echo $commit_desc_opt) $(git log --format='%B' -n$squash_count_opt)"
else
commit_desc_opt="$(git log --format='%B' -n$squash_count_opt)"
fi
fi
## create commit_desc_if_there if commit_desc_opt is populated
local commit_desc_if_there=""
if [[ $commit_desc_opt != "" ]]; then
commit_desc_if_there=$(echo "-m '$commit_desc_opt'")
fi
# Squash!: Reset Commits Softly before commiting back.
# ----------------
## Normally Squashing is done using rebase,
## but I could not automate its interactive shell.
git reset --soft HEAD~$squash_count_opt
# Squash!: Commit to complete squashing!
# ----------------
## Use Auto Commit Command if There, Else use Default `git commit`
if [[ $commit_msg_opt == "" && $(command -v gac) ]]; then
gac $commit_desc_if_there
else
git commit -m "$commit_message" $commit_desc_if_there
fi
}
gac):gac() {
## The Idea is to create a commit message from branch name
## For Example:
## if branch name is `feature/jira-123-this-is-issue-desc`
## then commit would be `[Enhancement JIRA-123] This Is Issue Desc`
# Exit, if there is nothing to commit!
if [[ $(git diff --staged) == "" ]]; then
echo "Nothing to Commit! " && return 1
fi
# Fetch Details
# ----------------
## `feature/jira-123-this-is-issue-desc`, all in lower case
local current_branch=$(git rev-parse --abbrev-ref HEAD | tr '[:upper:]' '[:lower:]')
## `feature`
local issue_type=$(echo $current_branch | cut -d '/' -f 1)
## `jira-123`
local issue_no=$(echo $current_branch | cut -d '/' -f 2- | cut -d '-' -f -2)
## `this-is-issue-desc`
local issue_desc=$(echo $current_branch | cut -d '/' -f 2- | cut -d '-' -f 3- )
# Format Details
# ----------------
## We will use this map to get issue_type out of branch name
declare -A branch_map; branch_map["feature"]="Enhancement";
branch_map["bugfix"]="Patch"; branch_map["version"]="Upgrade";
## `feature` -> `Enhancement`; `random` -> `Random`
issue_type=${branch_map[$issue_type]-${issue_type^}}
## `jira-123` -> `JIRA-123`
issue_no=$(echo $issue_no | tr '[:lower:]' '[:upper:]')
## `this-is-issue-desc` -> `This Is Issue Desc`
issue_desc=$(echo $issue_desc | sed 's/-/ /g' | awk '{for (i=1; i<=NF; i++) $i=toupper(substr($i,1,1)) tolower(substr($i,2))}1')
## `[Enhancement JIRA-123] This Is Issue Desc`
local commit_msg="[$issue_type $issue_no] $issue_desc"
# Commit!
# ----------------
## Along with this message, I have added $@, So that all the
## flags and arguments of `git commit` can be passed in this command.
git commit -m "$commit_msg" $@
}
gfrb):gfrb() {
## If $1 is not passed, then it will fetch the default branch
local base_branch=${1:-$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')}
git fetch origin "$base_branch":"$base_branch"
## Rebase requires stashing, but any file that has both
## staged and unstaged changes will lose all its
## unstaged changes! So uncomment at your own risk.
# git stash --all
git rebase "$base_branch"
# git stash pop
}
gup):gup() {
## Although `--force` is frequently used in this workflow.
## It still feels destructive to add here.
## Instead I have added $@, So that all the `git push`
## flags and arguments can be passed in this command.
local current_branch=$(git rev-parse --abbrev-ref HEAD)
git push --set-upstream origin "$current_branch" $@
}
Update your ~/.[ba|z]shrc with these productive commands and save those few minutes. Happy Commiting!
You also might like these:
Check out my gist (alias.sh) for a big list of aliases for git, python, Django, and docker.
Made a meme into reality - New Bash Alias: Git Out
And Finally, a fun video - Bash Script: Enjoy the Little Things!
PS: Feel free to share your productivity Hack!