Question

What is the easiest way to rebase a git repository such that only the heads of current branches remain in tact, and all else is squashed? Preferably a non-interactive solution that I can apply to various repositories.

Background: my repository is too large because it is tracking a lot of legacy code from previous versions. I deleted the branches I am no longer interested in. For the remaining branches, I would like to keep the final versions around for archiving purposes, but clean up all of the intermediate changes.

Was it helpful?

Solution

Since I found this question interesting, I wrote a little bash script that does (something like) what you want.

You can download it as a file (or check out its repo on Github) and find the source below. Here's a little discussion:

Usage:

./start_git_repo_fresh.sh [-r] [<path to git repo>]
where -r enables creation of a common root commit and the path let's you use the script from a different location; if the path is ommitted, the current directory (CWD) is used

This script's main task is to transplant currently existing branches, by simply using their latest commit (since Git commits are snapshots of the complete repository, that's easy).

If you specify the -r flag, a new empty root commit is created, and all branches are created as direct descendants of that root commit. If you do not specify the flag, every new branch will have its own root (they'll be orphan branches) and contain exactly one commit each.

This is done by getting a branch of existing local branches and walking over them; for each branch (call it BR) , the script will:

  • when -r is specified
    • (a) create a new branch from the new common root commit
    • (b) check out the files from BR
  • when -r is not specified
    • (a) check out BR
    • (b) create a new orphan branch from BR
  • (c) commit the files from BR using the commit message from BR
  • (d) delete the old branch BR
  • (e) rename the newly created branch to BR

If a branch was checked out when the script started (the repo was not in a detached HEAD state), the new version of that branch is checked out at the end; if the repo was on a detached HEAD, the last created branch is left checked out.


Script source:

#!/bin/bash

COMMON_ROOT=0
TEMP_ROOT_BRANCH="NEW_ROOT_COMMIT"

if [ "$1" == "-r" ]; then
    COMMON_ROOT=1
    shift 1
fi

if [ "$#" -eq 1 ]; then
    cd "$1"
fi

branches=$(git branch --color=never)
orig_branch=$(echo "$branches" | grep --color=never "^\* " | sed "s/^\* //")

if [ "$COMMON_ROOT" -eq 1 ]; then
    echo "Creating new (empty) common root commit"
    git checkout --orphan "$TEMP_ROOT_BRANCH" 2> /dev/null
    git rm -r --cached . >/dev/null
    git clean -dfx > /dev/null
    git commit --allow-empty -m "Initial commit" > /dev/null
fi

echo "$branches" | sed "s/^\* //" | while read branch; do
    echo "Transplanting branch $branch"
    newbranch="${branch}_new"
    if [ "$COMMON_ROOT" -eq 1 ]; then
        git checkout -b "$newbranch" "$TEMP_ROOT_BRANCH" > /dev/null 2>/dev/null
        git checkout "$branch" -- . > /dev/null 2>/dev/null
    else
        git checkout "$branch" > /dev/null 2>/dev/null
        git checkout --orphan "$newbranch" > /dev/null 2>/dev/null
    fi
    git commit -C "$branch" > /dev/null
    git branch -D "$branch" > /dev/null
    git branch -m "$newbranch" "$branch" > /dev/null
done

if [ "$COMMON_ROOT" -eq 1 ]; then
    git branch -D "$TEMP_ROOT_BRANCH" > /dev/null
fi

if [ -n "$orig_branch" ]; then
    git checkout "$orig_branch" 2>/dev/null
fi
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top