Last active
August 29, 2024 07:09
-
-
Save lucaspar/c6ed7e5654887beeb703b163d9b57712 to your computer and use it in GitHub Desktop.
"Work in Progress" | A git helper to save partial work
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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) | |
# if not a git repo, exit | |
if [[ -z "${git_dir}" ]]; then | |
echo -e "\e[33mNot a git repository. Exiting...\e[0m" | |
return 1 | |
fi | |
# 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=0 # do not amend by default: create a new commit | |
if [[ "${last_commit_message}" == "${default_commit_message}" ]]; then | |
last_commit_date=$(git log -1 --pretty=%ct) | |
seconds_in_day=86400 | |
if [[ $(date +%s) -gt $((last_commit_date + seconds_in_day)) ]]; then | |
# if commit date is older than 24h, don't amend. Create a new commit instead. | |
should_amend=0 | |
fi | |
# if last commit date is within 24h and its message | |
# is the wip default, amend the last commit. | |
should_amend=1 | |
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