1

I have 6 commits and each of them have multiple files commited. My "problem" is, that I commited a file which I should not commit, and I want it to dissapear from that old commit, so I can‘t see it:

last commit
  file8
  file7

commit 3
  file6
  file5

commit 2
  file4
  file3

first commit
  file2
  file1

How can I remove file4 from commit 2 to disappear?

...
commit 3
  file6
  file5

commit 2
  file3

...
SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34
rodriciru
  • 141
  • 2
  • 9

2 Answers2

0

You can't. No commit, once made, can ever be changed. So you can't remove the file from the old commit.

This does not mean you cannot solve your real problem, which is: you'd like to still have six commits, but you'd like the contents of some of those six commits to be different than they are now. You can achieve this by making several new commits, after which you stop using some of the old commits.

If commit #1 is OK, you can keep that one around as-is. Commit #2, which follows #1, however, is not OK. You must throw that one out. You can't actually discard or delete it directly, but you can stop using it. Once you do that, Git will eventually discard it.1 You will, however, need a new and improved version of commit #2, that you will use instead of the old commit-#2.

Commit #3 might be partly OK, if it doesn't contain the file you don't want any of the commits to contain.2 But even if it is OK this way, it's definitely not okay after all, because it refers to the bad commit #2. Every Git commit refers back to its immediate predecessor, so to replace commit #2, you must strip out commit #3 as well. This means you will need to make a replacement for #3.

Commit #4 might be partly OK, like #3, but like #3, it refers back to a bad commit: in this case, #3 itself! #3 is bad because it refers directly to #2, and that makes #4 bad too. So you're going to have to make a new-and-improved version of commit #4.

This repeats down the line with commits #5 and #6. If commit #2 is bad, you must replace it with a new-and-improved version, and that necessitates replacing every subsequent commit as well.

So: how you do you go about replacing these commits? This is where git rebase comes in. What git rebase does—what it's all about—is taking some existing commits, that are sort of mostly OK in some way or another, but not OK in some other way—and replacing them with new-and-improved commits.

The git rebase command does this with git cherry-pick internally. It's a good idea to understand this and know how it all works. This has been quite well covered in other StackOverflow postings, though, so see those.

You can also use git filter-branch (which is now formally deprecated) or its replacement, git filter-repo, to copy the existing commits to new-and-improved commits. See SwissCodeMen's answer for an example. Remember that this makes new and improved commits: the originals still exist for some time, perhaps forever on GitHub for instance.


1By default, disused commits stick around for at least 30 days, in case you change your mind and want them back. It's hard to find them once they're like this, and it's rarely worth trying to speed up the default-at-least-30-days aging thing that Git does with them, but if you have an odd situation, it is possible to accelerate the process.

2Every commit contains every file, not just changed files since the previous commit. So if commit #3 doesn't remove the file, as compared with commit #2, it's not OK after all. But that's a side point because of the way we'll build the new-and-improved commits.

torek
  • 448,244
  • 59
  • 642
  • 775
  • As far as I know, this is possible with the following command: `git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch path_to_file" HEAD` -> look at my answer. Or am I wrong? – SwissCodeMen Jul 14 '21 at 09:36
  • @SwissCodeMen: filter-branch also copies commits to new-and-improved commits. However, it's not a very user-usable command. – torek Jul 14 '21 at 09:59
  • Yes, I agree with you that `git filter branch` isn't a very user usable command. But I have tried it now, and the file was removed in commit X without creating/copying another commit ... So I think it is possible, but to be used/executed with caution. – SwissCodeMen Jul 14 '21 at 10:45
  • @SwissCodeMen: But it did (create a new commit, and then a bunch more new commits down the line). Compare the *hash IDs* before and after the filter-branch. Don't just count up the commits! Also, run `git log --all --decorate --graph` afterward to see the original commits under the `refs/original/refs/heads/` names. – torek Jul 14 '21 at 11:07
0

You can to this with following command. But first find out the path to the file in your repository where you want to remove (file4).

git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch <path_to_file>" HEAD

After this, execute git push -f and the file is removed from the commit2 and from the repository.

BUT BE CAREFUL WITH git filter-branch. The documentation you can see here

SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34
  • Note that this makes *new and different commits*, as I explain in [my answer](https://stackoverflow.com/a/68374435/1256452). It does not alter, nor remove, the old commit, and some hosting systems (such as GitHub) won't expire the old commit the way Git eventually will. – torek Nov 15 '22 at 06:14