Skip to content

Instantly share code, notes, and snippets.

@lucaspar
Last active June 22, 2024 02:29
Show Gist options
  • Save lucaspar/c6ed7e5654887beeb703b163d9b57712 to your computer and use it in GitHub Desktop.
Save lucaspar/c6ed7e5654887beeb703b163d9b57712 to your computer and use it in GitHub Desktop.
"Work in Progress" | A git helper to save partial work
# Source: https://gist.github.com/lucaspar/c6ed7e5654887beeb703b163d9b57712
# Add this function to your .bashrc, then source it: `source ~/.bashrc` or restart the terminal.
# Helper function for submitting working changes into version control and avoid loss of work.
# Work in Progress (`wip`) will create a temporary commit with your unstaged changes and a
# generic message. When run subsequently and within 24h of the last "wip" commit, it amends
# the last commit to prevent cluttering the commit history. Otherwise it will create a new "wip"
# commit. After a few hours of work, consider amending the "wip" commit with a proper message.
# Usage:
# wip [options] [file_a path_b file_c ...]
# Notes:
# If no files/paths are provided, `.` is passed to `git add` as default.
# Options:
# -h, --help Display usage information.
# Examples:
# wip # uses `.` as default
# wip my/file1.md my/directory/
# Optionally enable the `push_to_remote` flag below to send changes to the remote server. Note
# if branch protection rules are enabled, pushing might fail, as it does a forced-push on
# subsequent runs. Forced pushes will also fail if the local history is not up-to-date
# against the remote, which makes it a bit safer.
function wip() {
# Source: https://gist.github.com/lucaspar/c6ed7e5654887beeb703b163d9b57712
push_to_remote=0
# Set to 1 to push to remote after committing.
# This is disabled (=0) by default for:
# 1. Avoiding accidental pushes to the remote.
# 2. Avoiding potentially dangerous 'force' pushes to the remote when 'wip' runs subsequently.
# When push is enabled: if this function is just ammending the last commit, --force-if-includes and
# --force-with-lease will be used to push to the remote, which is safer than --force.
# displays usage
show_usage() {
echo -e "Creates a temporary 'Work in Progress' commit with unstaged changes."
echo -e "Usage: wip [options] [file1 file2 ...]"
echo -e "Options:"
echo -e "\t-h, --help Display this help message\n"
}
# check for help option
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
show_usage
return 0
fi
# do not run in the middle of a merge or rebase
git_dir=$(git rev-parse --git-dir 2>/dev/null)
# https://stackoverflow.com/a/67245016/2848528
if [[ -d "${git_dir}/rebase-merge" ]] || [[ -d "${git_dir}/rebase-apply" ]]; then
echo -e "\e[33mA rebase is ongoing. Please complete it before using wip.\e[0m"
return 1
fi
# https://stackoverflow.com/a/30781568/2848528
if ! git merge HEAD &>/dev/null; then
echo -e "\e[33mA merge is ongoing. Please complete it before using wip.\e[0m"
return 1
fi
# do not run if there are already one or more staged changes
if [[ -n $(git diff --cached --name-only) ]]; then
echo -e "\e[33mThere are already staged files. Please commit them manually or unstage them before using wip.\e[0m"
return 1
fi
# ensure at least one file or directory is provided, if none, use `.` as default
files=("$@")
if [[ ${#files[@]} -eq 0 ]]; then
files=(".")
fi
# add both tracked and untracked changes
git add -v "${files[@]}"
# if staging is empty, exit
if [[ -z $(git diff --cached --name-only) ]]; then
echo -e "\e[34mNo changes to commit. Exiting...\e[0m"
return 0
fi
default_commit_message="wip"
last_commit_message=$(git log -1 --pretty=%B | sed 's/[[:space:]]*$//')
remote=$(git remote | head -n 1)
should_amend=1
if [[ "${last_commit_message}" == "${default_commit_message}" ]]; then
# if commit date is older than 24h, don't amend. Create a new commit instead.
last_commit_date=$(git log -1 --pretty=%ct)
seconds_in_day=86400
if [[ $(date +%s) -gt $((last_commit_date + seconds_in_day)) ]]; then
should_amend=0
fi
fi
# check if the last commit was created by this function
if [[ "${should_amend}" -eq 1 ]]; then
echo -e "\e[32mWIP: Amending the last commit...\e[0m"
# just amend it instead of creating a new commit
git commit --amend --no-edit
if [[ ${push_to_remote} -eq 1 ]]; then
echo -e "\e[32mWIP: Pushing to remote...\e[0m"
git push --force-if-includes --force-with-lease "${remote}" HEAD
fi
else
echo -e "\e[32mWIP: Creating a new commit...\e[0m"
# create a new commit with the generic message
git commit -m "${default_commit_message}"
if [[ ${push_to_remote} -eq 1 ]]; then
echo -e "\e[32mWIP: Pushing to remote...\e[0m"
git push "${remote}" HEAD
fi
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment