14

I have a git branch I need to nuke all traces of. I think I need something like

git filter-branch --index-filter 'git -D <branch>' --tag-name-filter cat -- --all

or maybe

git filter-branch --index-filter 'git rm --cached *' --tag-name-filter cat -- <branch>

Then I would

git push -f origin :<branch>

I'm hesitant to try them without knowing for sure what they'll do or if they'll work.

drs
  • 5,679
  • 4
  • 42
  • 67
  • I've never used it, and it might not be at all relevant to your situation, but if you're just trying to get rid of sensitive data that shouldn't have been committed, there's [the BFG](https://rtyley.github.io/bfg-repo-cleaner/). – DaveyDaveDave Jan 07 '16 at 14:05
  • Possible duplicate of [Delete a Git branch both locally and remotely](http://stackoverflow.com/questions/2003505/delete-a-git-branch-both-locally-and-remotely) – Chris Maes Jan 07 '16 at 14:21
  • 1
    If you just want to remove locally and remotely the branch, see @codeWizard answer (http://stackoverflow.com/a/34657278/2531279). If you want to get rid of the commits that were done on this branch, it is more complicated and indeed needs to use `git filter-branch`. – Frodon Jan 07 '16 at 14:29
  • @drs maybe this answer will fit your needs: http://stackoverflow.com/a/15256575/2531279 – Frodon Jan 07 '16 at 14:37
  • When you say you want to remove a branch and all of its history, do you mean *all* of it, including the initial commit (which is by definition in the history of all branches) or only some of it? I can't really answer your question without knowing this. – PyRulez Jan 09 '16 at 20:34
  • The problem is, git does not store which commit goes with which branch necessarily. Namely, if you have two branches `master` and `evilness` the have a common commit `666fe`, it does not know if `master` split from `evilness` or `evilness` split from `master` at that point. [Reflog](https://git-scm.com/docs/git-reflog) may help though. – PyRulez Jan 09 '16 at 20:56
  • 1
    In your example, I would want to remove all commits in branch `evilness` that are decedents of `666fe`, but not `666fe`. – drs Jan 10 '16 at 15:42
  • @drs Okay, my answer was based on this assumption. – PyRulez Jan 10 '16 at 15:57

2 Answers2

10

Let's say you want to nuke evilbranch.

You say you want to delete all of a branch's history. This technically includes the initial commit, which is probably more commits then you want. So first, identify which commit you consider to be the first commit of that branch. Let's say it's 666bad. Now we need to find all references to it. Run

git branch -a --contains 666bad
git tag --contains 666bad

Now delete them all. You can either use git commands, or just go into .git/refs.

Do this on every computer that might have the file.

Make sure you are not in detached head.

Now we can kill all the commits, and therefore all the code, that is no longer referenceable (from this gitHub link):

git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now

Again, this should be done on each computer.

Finally, use this on each computer.

Note: 666bad is the first commit after evilbranch split from wherever it came from, i.e., the first commit that is only evilbranch.

Community
  • 1
  • 1
PyRulez
  • 10,513
  • 10
  • 42
  • 87
  • How can I use this to remove the history from remotes like github that I don't have a shell on? – drs Jan 10 '16 at 15:40
  • @drs Delete the tags and branches through the online interface. GitHub will probably not leak any commits when people clone (since that was their link), and I doubt that GitHub has any evil plots against you. – PyRulez Jan 10 '16 at 15:55
  • You can also use [this](https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches#Deleting-Remote-Branches) and [this](https://nathanhoad.net/how-to-delete-a-remote-git-tag) if you do not want to use the online interface. – PyRulez Jan 10 '16 at 15:56
  • @drs (Other remotes should have something similar. In the end though, any computer that has your code that is malicious *could* save it, if it wanted to. Most code hosting things probably aren't evil though.) – PyRulez Jan 10 '16 at 15:59
  • 1
    I presume that somebody that grabbed a copy with `evilness` could commit the bad code right back. Would this be simple enough that someone could do it 'unwittingly' or would it be complicated enough that it would require a determined 'bad actor'? – John Hascall Jan 16 '16 at 17:50
  • @JohnHascall Every computer with the branch needs to follow the above steps. If they do, the only way `evilness` could survive is if someone intentionally backed it up somewhere. (It *may* be easier to do this on one computer, then reclone from it.) – PyRulez Jan 16 '16 at 18:03
  • @JohnHascall (And of course, any bad actor that comes in contact with `evilness` could easily save `evilness` without anyone knowing.) – PyRulez Jan 16 '16 at 18:04
  • With a decentralized system like this, is there ever any hope of getting everyone to do this? Hence my question about whether 'recontamination' requires a bad actor. – John Hascall Jan 16 '16 at 18:25
  • @JohnHascall depends how many people have seen it. (If you accidentally put the nuclear launch codes in a commit message in the Linux kernel good luck). – PyRulez Jan 16 '16 at 18:28
  • @PyRulez I didn't try it because I wanted a way to push my deletions to an origin that I don't have shell-access to. I posted what I ended up doing as another answer. – drs Jan 18 '16 at 20:14
1

Investigate affected branches and tags

Find the first commit to the branch you want to nuke, perhaps using git log --all --decorate --graph --oneline or using git reflog. Let's say that first commit is 666bad.

Use the tips mentioned by PyRulez to help find any tags or branches that may be contaminated with this branch.

git branch -a --contains 666bad
git tag --contains 666bad

If any tags are associated with the branch, delete them locally and remotely.

git tag -d <tag name 1> <tag name 2> ...               # remove tags locally
git push --delete origin <tag name 1> <tag name 2> ... # remove tags remotely

Clean up your local and remote branches

Checkout the branch you want to delete and use git filter-branch to reset all commits on the branch to the parent of the branch's first commit:

git checkout evilbranch
git filter-branch --force --index-filter `git reset --hard 666bad^' \
                  --prune-empty 666bad..HEAD

This will delete the commits as it goes, because they are empty.

Force-push the results to your origin

git push origin --force

Clean up your reflog and prune dangling commits

git reflog expire --expire=now --all
git gc --prune=now

Clean up team member branches

Make sure anyone else that has checked out the branch delete the tags and the branch on their machine, and then that they expire their reflog and garbage collect.

git tag -d <tag name 1> <tag name 2> ...
git branch -D evilbranch
git checkout master
git reflog expire --expire=now
git gc --prune=now

A final note

If your origin is GitHub and your branch has any Pull Requests on it, you must contact GitHub support and request that they prune your Pull Requests. You should also contact them to remove an cached views.

drs
  • 5,679
  • 4
  • 42
  • 67