Skip to content

Instantly share code, notes, and snippets.

@EricCousineau-TRI
Last active October 14, 2020 23:03
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 EricCousineau-TRI/bc6cbb03bc37a1980ec1982c790b61d8 to your computer and use it in GitHub Desktop.
Save EricCousineau-TRI/bc6cbb03bc37a1980ec1982c790b61d8 to your computer and use it in GitHub Desktop.
No-Prune Branches in Git

If you're submitting PR's to a Git repo, then you generally may want to squash and/or rebase your commits to have a curated history. However, you may still have experiments that you run, where you may want a "permalink" to its history, and thus you would want to ensure that an unreachable commit (e.g. your commit before squashing or rebasing) does not get pruned when running some form of git gc.

I'm not sure if GitHub prevents pruning of explicit references to a given commit, but just in case, you may want to have a no_prune branch, and make sure you push to it (as --fast-forward only!).

To make one, just create the branch (off of whatever commit). Then, as you're making commits, merge using git merge --strategy=ours. For more information, see the Git documentation for the ours strategy.

Example:

# Clone your fork as remote `origin`.
$ git clone git@github.com:your-username/repo.git
$ cd repo
# Add upstream for PRs.
$ git remote add upstream https://github.com/original/repo
$ git config remote.upstream.pushurl no_push

# Make a `no_prune` branch if you haven't already.
$ git branch no_prune

# Make a PR, make several revisions, 
$ git fetch upstream
$ git checkout -b my-stuff upstream/master

# Make a whole bunch of commits.
# Push the branch for reviwe.
$ git push origin my-stuff

# Reviewers come in, request changes.
# You make your changes, and do history-destroying things like amend, rebase, and squash.
# BEFORE you force push your new changes, make sure to merge the old state of your branch!

$ git checkout no_prune
$ git merge --strategy=ours origin/my-stuff -m "Remember old revision"
$ git push origin no_prune

# Return to your previous branch.
$ git checkout -

# Now push your changes.
$ git push --force origin my-stuff

# Make more edits, and do the same thing.
# Oh shit! I forgot to do the no_prune stuff :(
# Well, I guess I'll look at my reflog to see where there those old commits are.
$ git reflog

Here's a bash function that could be placed in ~/.bash_aliases:

git-no-prune-merge () 
{ (
    set -eux;
    ref="$1";
    git checkout no_prune;
    git merge --no-ff --strategy=ours ${ref} -m "No-prune merge (with --strategy=ours) of ${ref}";
    git checkout -
) }

Example usage (this requires you creating this branch first):

git-no-prune-merge $(git rev-parse HEAD)
git push origin no_prune

TODO(eric): The git gc docs indicate that reflogs may prevent a commit from being pruned. If so, at least the gc won't happen locally, as long as you don't discard your checkout? (However, still unclear if it disappears...)

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