Search code examples
gitgithubrebasesquash

non-interactive git squash/rebase all into current branch heads


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.


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