-
-
Save chaozh/bf48cd3050ffc04626db to your computer and use it in GitHub Desktop.
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
#!/bin/sh | |
usage() | |
{ | |
local name=${0##*/} | |
cat >&2 <<-END | |
Rewrite history to squash all commits wiht message starts with 'fixup!' to | |
its first parent. By [email protected] | |
Usage: | |
> $name [-f] [-t] [-p <pattern>] <rev-list options>... | |
Options: | |
-f Force to remove backup ref from previous $name. | |
-p <pattern> Squash commits with mssage starts with <pattern>. | |
By default <pattern> is 'fixup!'. | |
-t Update ref covered by <rev-list>, when squashing | |
history with merges. | |
By default, with a repo like: | |
* a6910e2 (master) Merge commit '75275ed' | |
|\\ | |
| * 75275ed (branch-fix) fixup! ok | |
| * 2e85eb7 ok | |
|/ | |
* b66353d init | |
> $name master | |
Will only update "master", but leave "branch-fix" where it | |
was. It results in: | |
* 0982b05 (master) Merge commit '75275ed' | |
|\\ | |
| * 377a349 ok | |
|/ | |
* b66353d init | |
With "-t" it also update ref "branch-fix" after squashing: | |
> $name -t master | |
* 0982b05 (master) Merge commit '75275ed' | |
|\\ | |
| * 377a349 (branch-fix) ok | |
|/ | |
* b66353d init | |
It is same with specifying which ref to update manually: | |
> $name -t master branch-fix | |
Example: | |
> $name -f -p fixup b66353d..master | |
moves master from a6910e2 to 4d4a5e4: | |
* 4d4a5e4 (HEAD, master) ok | |
| * a6910e2 fixup! fixup! ok | |
| * 75275ed fixup! ok | |
| * 2e85eb7 ok | |
|/ | |
* b66353d init | |
Tips: | |
$name uses git-filter-branch to process history, which leaves backup refs | |
like: "refs/original/refs/heads/master". | |
I suggest to keep them but if you really find it annoying, just force | |
auto-squash to run again with empty-history to remove them: | |
> $name -f HEAD.. | |
END | |
} | |
die() | |
{ | |
echo "Failure $@" >&2 | |
exit 1 | |
} | |
# workdir=./.git-rewrite/t/ | |
# mappingdir=./.git-rewrite/map | |
squash_dir=.git-auto-squash | |
_orig_dir=$(pwd) | |
mkdir -p "$squash_dir/squash_to" "$squash_dir/tree_to_use" \ | |
&& squash_dir="$(cd "$squash_dir"; pwd)" \ | |
|| exit 1 | |
# Remove squash_dir on exit | |
trap 'cd "$_orig_dir"; rm -rf "$squash_dir"' 0 | |
find_refs() | |
{ | |
mkdir -p "$squash_dir/commit" || die "mkdir $squash_dir/commit" | |
for commit in $(git rev-list --reverse --topo-order --default HEAD --parents --simplify-merges "$@") | |
do | |
echo 1 >"$squash_dir/commit/$commit" | |
done | |
git show-ref --heads | | |
while read commit full_ref | |
do | |
if test -r "$squash_dir/commit/$commit" | |
then | |
echo "$full_ref" | |
fi | |
done | |
} | |
force= | |
update_refs= | |
pattern="fixup!" | |
while : | |
do | |
case $1 in | |
"-h"|"--help"|"") | |
usage | |
exit 0 | |
;; | |
-f) | |
shift | |
force=" -f " | |
;; | |
-t) | |
shift | |
update_refs=1 | |
;; | |
-p) | |
shift | |
pattern="$1" | |
shift | |
if test -z $pattern | |
then | |
usage | |
exit 1 | |
fi | |
;; | |
*) | |
break | |
;; | |
esac | |
done | |
if test "$update_refs" = "1" | |
then | |
update_refs=$(find_refs "$@") | |
echo "Also update ref: $update_refs" | |
fi | |
vars='squash_dir="'"$squash_dir"'"' | |
funs=$(cat << \END | |
record_squash() | |
{ | |
local commit=$1 | |
local tree=$2 | |
local parent=$3 | |
local target= | |
if test -r "$squash_dir/squash_to/$parent" | |
then | |
target=$(cat "$squash_dir/squash_to/$parent") | |
else | |
target=$parent | |
fi | |
echo $target >"$squash_dir/squash_to/$commit" | |
echo $tree >"$squash_dir/tree_to_use/$target" | |
} | |
END) | |
# The first pass collect commits need to be squashed | |
git filter-branch $force --commit-filter "$vars; $funs;"' | |
cat > "$workdir/../this_message" | |
mes="$(cat "$workdir/../this_message")" | |
# input arguments are "<tree-ish> -p <firstparent> [-p <parent>].." | |
tree=$1 | |
shift | |
firstparent=$2 | |
case $mes in | |
"'"$pattern"'"*) | |
record_squash $GIT_COMMIT $tree $firstparent | |
;; | |
*) | |
for p in "$@" | |
do | |
# if any of its parent is squashed | |
if test "-p" != "$p" && test "$firstparent" != "$p" && test -r "$squash_dir/squash_to/$p" | |
then | |
record_squash $GIT_COMMIT $tree $firstparent | |
break | |
fi | |
done | |
;; | |
esac | |
git commit-tree $tree "$@" < $workdir/../this_message | |
' "$@" | |
# without force, our responsibility to remove backup refs | |
git filter-branch $force --commit-filter "$vars; $funs;"' | |
if test -r "$squash_dir/squash_to/$GIT_COMMIT" | |
then | |
tree=$1 | |
shift; | |
# normal commit | |
if test "$#" = "2" | |
then | |
skip_commit $tree "$@" | |
else | |
# merge, do not pass squashed parent to children. | |
# this also removed non-squashed parent if there are more than 3 | |
# parents. | |
skip_commit $tree $1 $2 | |
fi | |
else | |
if test -r "$squash_dir/tree_to_use/$GIT_COMMIT" | |
then | |
tree=$(cat "$squash_dir/tree_to_use/$GIT_COMMIT") | |
else | |
tree=$1 | |
fi | |
shift # remove tree from args | |
git commit-tree $tree "$@" | |
fi | |
' "$@" $update_refs |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment