Skip to content

Instantly share code, notes, and snippets.

@AliSoftware
Last active August 30, 2018 21:37
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AliSoftware/2b3a60118fef2826e127de38b5d2016e to your computer and use it in GitHub Desktop.
Save AliSoftware/2b3a60118fef2826e127de38b5d2016e to your computer and use it in GitHub Desktop.
A "git squash" command to squash multiple commits into one automatically
#!/bin/bash
#####
#
# git-gerrit v1.1
# O. Halligon, 2017.08.18
#
#####
#
# Usage:
#
# $ git gerrit status [--prune]
# For each branch, display if each commit on that branch is already cherry-picked on master
# (by searching a commit on master with the same Change-Id).
# Adding '--prune' will automatically delete the local branch if all commits of that branch
# have been cherry-picked on master already.
#
# $ git gerrit push
# An alias to 'git push gerrit HEAD:refs/for/master'
#
#####
#
if [ $(tput colors) -gt 8 ]; then
CLR_RED="$(tput setaf 1)"
CLR_GREEN=$(tput setaf 2)
CLR_CYAN=$(tput setaf 6)
CLR_RESET="$(tput sgr0)"
fi
case $1 in
status) # Gerrit status
branches=$(git branch --list {dev,bugfix}-*-branch | sed 's/^. //')
git checkout master &>/dev/null
for branch in $branches; do
echo "== Branch ${branch} =="
ids=$(git log --first-parent $branch master.. | sed -n 's/Change-Id: \(.*\)$/\1/p')
ready_to_delete=1
for id in $ids; do
commit_in_master=$(git log --grep $id --format=%H)
if [ "$commit_in_master" ]; then
echo -e "${CLR_GREEN} - $id : cherry-picked by $commit_in_master on master${CLR_RESET}"
else
echo -e "${CLR_RED} - $id : not yet in master${CLR_RESET}"
ready_to_delete=0
fi
done
if [[ "$2" == "--prune" && $ready_to_delete == 1 ]]; then
echo -e "${CLR_CYAN} --> Deleting local branch $branch...${CLR_RESET}"
git branch -D $branch
fi
done
git checkout - &>/dev/null
;;
push) # git push on gerrit
git push gerrit HEAD:refs/for/master
;;
*) # Usage
echo "Usage:"
echo " git gerrit status [--prune]"
echo " git gerrit push"
;;
esac
#!/bin/sh
#####
#
# git-gsquash v1.4.1
# O. Halligon, 2017.07.17
#
#####
#
# Usage:
#
# $ git squash <tree-ref>
# squash all commits on top of the <tree-ref> commit
# e.g.: git squash master
#
# $ git squash --continue
# $ git squash --abort
# To continue or abort a squash which has been interrupted by a conflict
#
#####
#
case "$1" in
--edit-todo) # Editor mode to alter the git-rebase-todo file
if [[ $(basename "$2") != "git-rebase-todo" ]]; then exit 0; fi
# change "pick" to "squash" for all commits except first
sed -i '' -e '2,$s/^pick/squash/' "$2"
# end the list of actions with a reword of last commit
# also let the commit hook be replayed, to add the final Change-Id after the commit message rewording
echo exec EDITOR=\"$0 --reword-squashed-commit\" git commit --amend >>"$2"
;;
--reword-squashed-commit) # Reword the last commit message nicely
# Replace intermediate Change-Ids with a separator line
# And remove "[DEV] (FF4) … : " prefixes from intermediate commits history
sed -e 's/^Change-Id: .*$/----------/' -e 's/^\[DEV\] (FF4) .* : //' -e 's/^\[DEV\] (FF4) //' "$2" > "$2.tmp"
# Add a "[DEV] (FF4) $DESC" header at the beginning of the squashed commit message
# Where $DESC is generated from the name of the branch, replacing dashes with spaces
BRANCH=$(git symbolic-ref HEAD 2>/dev/null || cat `git rev-parse --git-dir`/rebase-merge/head-name)
DESC=$(echo $BRANCH | sed "s#^refs/heads/dev-\(.*\)-branch\$#\1#" | tr "-" " ")
echo "[DEV] (FF4) ${DESC}\n\n=== Squashed History ===\n" | cat - "$2.tmp" > "$2"
rm "$2.tmp"
;;
--continue) # Helper to continue a squash which has been interrupted by a rebase conflict
EDITOR=true git rebase --continue
;;
--abort) # Helper to abort a squash which has been interrupted by a rebase conflict
git rebase --abort
;;
*) # Main invocation. That's what to execute when you will call `git squash <REF>` from the terminal
EDITOR="$0 --edit-todo" git rebase -i $@
;;
esac
#!/bin/sh
#####
#
# git-squash v1.4.1
# O. Halligon, 2017.07.17
#
#####
#
# Usage:
#
# $ git squash <tree-ref>
# squash all commits on top of the <tree-ref> commit
# e.g.: git squash master
#
# $ git squash --continue
# $ git squash --abort
# To continue or abort a squash which has been interrupted by a conflict
#
#####
#
case "$1" in
--edit-todo) # Editor mode to alter the git-rebase-todo file
if [[ $(basename "$2") != "git-rebase-todo" ]]; then exit 0; fi
# change "pick" to "squash" for all commits except first
sed -i '' -e '2,$s/^pick/squash/' "$2"
;;
--continue) # Helper to continue a squash which has been interrupted by a rebase conflict
EDITOR=true git rebase --continue
;;
--abort) # Helper to abort a squash which has been interrupted by a rebase conflict
git rebase --abort
;;
*) # Main invocation. That's what to execute when you will call `git squash <REF>` from the terminal
EDITOR="$0 --edit-todo" git rebase -i $@
;;
esac
@AliSoftware
Copy link
Author

AliSoftware commented Jul 19, 2017

git squash

Installation:

  • Copy this git-squash file somewhere in your $PATH (e.g. /usr/local/bin/git-squash)
  • Add executable rights to it: chmod +x /usr/local/bin/git-squash

Usage

To squash the last 5 commits:

git squash HEAD^5

To squash all commits from master to current commit (HEAD) as a single commit on top of master:

git squash master

git gerrit

Installation

  • Copy this git-gerrit file somewhere in your $PATH (e.g. /usr/local/bin/git-gerrit)
  • Add executable rights to it: chmod +x /usr/local/bin/git-gerrit

Usage

  • git gerrit status allows you to check all the commits from your local branches and show if they (or rather their Change-Id) have already been cherry-picked on master. If a branch have all its Change-Id commits already cherry-picked to master, that mean it's safe to force-delete that local branch.
  • git gerrit push is just an alias for git push gerrit HEAD:refs/for/master

Notes

Script git-squash can be used outside Gerrit.
Scripts git-gerrit and git-gsquash has been written for a project which uses Gerrit.

  • git-gsquash does the same as git-squash but additionally replaces intermediate Change-Id: … lines in intermediate commits with a ---------- separator line, then let the Gerrit git hook be replayed on the squashed commit to add its own Change-Id to it
  • Branches are expected to be named dev-DESC-branch where DESC is a one or multiple dash-separated words describing the change, like update-settings or add-awesome-feature. That short description DESC will be used to generate the first line of the squashed commit message, replacing dashes with spaces in the DESC text.
  • Note that if some intermediate commit messages are prefixed with "[DEV] (FF4) … : ", that prefix is removed.
    • That's because on the project for which this script was created, we have a prepare-commit hook which formats all our commits (so including intermediate commits being squashed later) that way, but when squashing we don't need to keep those prefixes in the generated commit history message.
    • You could adjust that prefix in the script for your own commit message format conventions if needed by adjusting the 2nd regex on line 34.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment