Skip to content

Instantly share code, notes, and snippets.

@vermiculus
Last active September 25, 2022 12:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vermiculus/c869aa83811de734440918ec062c43d3 to your computer and use it in GitHub Desktop.
Save vermiculus/c869aa83811de734440918ec062c43d3 to your computer and use it in GitHub Desktop.
Squash a Git repository to just include specific snapshots
#!/bin/bash
# usage: ./squash-no-error-handling.sh <new-branch-name>
#
# Expects a list of refs on standard input. One commit will be made
# per ref. The last line provided will be the tip of the new branch.
#
# This version is provided to illustrate the concept without any
# error-handling. It is unforgiving against any mistakes -- I strongly
# recommend you use the 'squash.sh' script instead!
while read ref; do
head=$(git commit-tree $(git rev-parse "$ref^{tree}") $parent -m "$ref")
parent="-p $head"
done
git update-ref refs/heads/$1 $head
#!/bin/bash
# usage: ./squash.sh <new-branch-name>
#
# Expects a list of refs on standard input. One commit will be made
# per ref. The last line provided will be the tip of the new branch.
#
# For example, if you run
#
# git tag --list >tags.txt
#
# you'll get a file called tags.txt that looks something like this:
#
# v0.1.0
# v0.1.1
# v0.1.2
# v0.2.0
# v0.2.1
# v0.3.0
# v0.3.1
# v0.3.2
# v1.0.0
#
# Say you want to provide a branch that only includes more important
# snapshots. If you trim tags.txt to just
#
# v0.1.0
# v0.2.0
# v0.3.0
# v1.0.0
#
# and then run the script,
#
# ./squash.sh new-main <tags.txt
#
# the 'new-main' branch will contain just the snapshots from v0.1.0 to
# v1.0.0 and will be pointing to the same snapshot as v1.0.0. After
# that, you should rebase/reword the entire range to write useful
# commit messages.
#
# ----
#
# The basic idea of this script is the following loop:
#
# git commit-tree $tag^{tree} -p $parent
#
# where $parent is null the first go around. This low-level command
# will output the commit hash; this becomes the next $parent.
#
# When all the commits are made, we finally update the branch name
# given to point at the last commit using `git update-ref`.
#
# The rest of the code you see is error-handling and pretty output.
# See file 'squash-no-error-handling.sh' for a stripped-down version
# of this functionality that might make it easier to understand.
if ! git branch "$1"; then
echo "failed to create branch: $1" >&2
exit 1
fi
while read ref; do
tree=$(git rev-parse --verify --quiet "$ref^{tree}")
if [[ $? != 0 ]]; then
echo "does not point to a tree (not a commit or a ref): $ref" >&2
exit 1
fi
head=$(git commit-tree $tree $parent -m "auto-generated commit for snapshot '$ref'")
parent="-p $head"
echo "$ref -> ${head:0:8}"
done
if git update-ref refs/heads/$1 $head; then
echo "Branch '$1' created successfully:"
git log --oneline $1 | sed 's/^/ /'
echo "Run 'git rebase -i --root' to reword your commits."
else
echo "couldn't create branch; head should be $head" >&2
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment