9

I started messing around with git filter-branch. The --all option rewrites all branches and tags. Cool. git filter-branch creates a backup of all refs it overwrites in refs/original. Very Cool. Now I'd like to blow all my experimenting with filter-branch away.

Is there an easy way to completely undo the effects of git filter-branch <whatever filter> -- --all? I.e. to restore all of the rewritten branches to their original state all at once?

If there isn't a preexisting way, there should be. If there isn't a preexisting way, does anybody have a short script that will do it?

Obviously there's workarounds. I could restore it manually, a branch at a time, as in this question. Or I could just nuke and re-clone. Either would get tedious quickly in a repo with many branches/tags that is (say) being split up into smaller repos.

Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
stochastic
  • 3,155
  • 5
  • 27
  • 42
  • 1
    Possible duplicate of [Undo git filter-branch](https://stackoverflow.com/questions/14542326/undo-git-filter-branch) – Mr_and_Mrs_D Jan 08 '18 at 12:04

2 Answers2

11

Here's a simple and safe way to undo a filter-branch:

git fetch . +refs/original/*:*

If the currently checked out branch (HEAD) is one of the branches to be restored, this command will fail. You can run git checkout --detach beforehand to let git fetch update all branches. Remember to checkout a branch after running git fetch!

Once the refs have been successfully restored, you can delete the refs/original refs safely with this command:

git for-each-ref refs/original --format='delete %(refname) %(objectname)' | git update-ref --stdin

Old answer:

git for-each-ref refs/heads refs/tags \
        --format='git update-ref "%(refname)" "%(refname)@{1 hour ago}"'

For added safety you could test whether the filter-branch actually did update the ref:

git for-each-ref refs/heads refs/tags --format='
        { git reflog -1 "%(refname)" | sed -n "/filter-branch: rewrite$/Q1"; } \
                  || git update-ref -m "reset to before filter-branch" \
                             %(refname) %(refname)@{1}' | sh -x
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
jthill
  • 55,082
  • 5
  • 77
  • 137
  • This looks like a good script solution (and parsing it expanded my sed and git horizons). The use of git update-ref should make it safer than manual copying of files (according to the git docs). It mostly worked when I tried it, but I also got some errors like `fatal: Log for 'refs/remotes/origin/master' only has 1 entries.` Haven't yet gotten to the bottom of why. sh -x is also a nice touch, though the output is garbled for some reason. I suspect that the output from subprocesses are racing to the terminal output and getting printed all over each other. – stochastic May 06 '14 at 00:21
  • Yes, should probably make it `git for-each-ref refs/heads refs/tags --format=etc.`. Fixed. – jthill May 06 '14 at 00:26
  • Thing is, if git tried to implement a pushbutton for every sometimes-very-useful-thing you can achieve with a few lines of script, it'd take longer to find the pushbutton that does what you want than to just implement it. – jthill May 06 '14 at 00:53
  • on that last comment, I most strongly disagree. In my experience, the "not being able to find the button" problem is a deficiency in careful design of the user interface/experience (a front where git has been historically lacking), not necessarily a "too many features" problem. Not that I'm complaining too much: I use git for everything, and also understand implementation triage :-). – stochastic May 06 '14 at 17:37
2

Ehmm, move all refs from .git/refs/original to their place in .git/refs/heads? I mean literally move, these are plain text files. Also you may take a look into .git/packed-refs if it exists.

user3159253
  • 16,836
  • 3
  • 30
  • 56
  • Thanks for that answer. I knew that those files existed and what they did, but messing around manually in the .git folder seemed to be courting disaster. I did this: `cd myrepo/.git/refs/original` `cp -r refs/* ..` and that seemed to do the trick. That packed-refs file worries me, though. I know that packed-refs is used as a fallback if the file doesn't exist. I presume that `git filter-branch` doesn't touch packed-refs. Are there any other gotchas here? – stochastic May 05 '14 at 23:33
  • `packed-refs` is created during `git gc` and used as a fatser alternative to files in `.git/refs`. Usually, it's not a problem since entries in .git/refs override it, and `git filter-branch` is a quite rare operation so no need to "automate things", but just to be sure that everything is under your control :) – user3159253 May 06 '14 at 01:09