0

I have 5 files in my project. I've done 10 commits, and in each commit, I modified and staged each file. Is it possible to change each of those commits to include all but the first of those 5 files?

gkeenley
  • 6,088
  • 8
  • 54
  • 129
  • Take a look on this answer: https://stackoverflow.com/questions/34519665/how-can-i-move-head-back-to-a-previous-location-detached-head-undo-commits/34519716#34519716 – CodeWizard Jul 05 '21 at 08:40
  • gkeenly, I took the time to write a through answer for you. Please at least respond with fedback if my answer is not acceptable. – Inigo Jul 06 '21 at 23:40
  • @Inigo I'll reply as soon as I'm able to go through it, I hadn't checked back yet. Stay tuned :) – gkeenley Jul 07 '21 at 20:59

1 Answers1

1

Yes, it is more than possible. I'll give you four ways to rewrite history to remove a file from it...

⚠️ If you need to eliminate a file from git history for security reasons, you should instead follow the steps in Remove sensitive files and their commits from Git history.

interactive rebase

If you're not yet a git pro, I recommend that you use this method, even though there are more manual steps.

Since you want to rewrite 10 commits, you can do this:

git rebase -i HEAD~10

This will open up a "todo list" that will list commits starting from HEAD~10, which will be the last 10 commits. If you leave the file as is and close it, git rebase will replay those 10 commits in the order shown, and nothing will have changed. But by editing the todo list, you have total control over what happens. You can reorder any line and thus reorder the commits. You can delete a line or replace pick with skip to skip a commit. Or you can replace "pick" with another instruction. All this is explained in the todo file itself, so you don't have to memorize all the options.

In your case, you want to amend each commit to remove the one file. Replace each pick with edit or just e. Close the file. Git rebase will then replay those commits, but will stop after each one and allow you to amend it.

On the first one you will git rm the file you want removed, and then git commit --amend. The new first commit will no longer include the file. You can now move on to the next one.

ℹ️ During an interactive rebase, you can always type git status and it will show you the current state of your rebase, and give you hints about what you probably want to do next.

To move on to the next commit: git rebase --continue. Rebase will try to replay the next commit but will stop because of a conflict: On your new rewritten branch the file does not exist anymore, but on the second commit that is being replay, the file got modified.

You resolve the conflict in this case by just deleting the file again and then git add . to update the staging index with this chosen conflict resolution. Then git rebase --continue to tell rebase to retry committing the second commit and then move on to the third.

ℹ️ If at any time you think you've made a mistake, or change your mind, run git rebase --abort and everything will be restored to where you were before.

The same conflict will happen again for all the remaining commits, and you do the same thing for all of them:

  • delete the file
  • git add . to record your resolution
  • git rebase --continue to move on.

When the rebase is done, your current branch HEAD will now point to a new series of 10 commits without the file you wanted removed. The original commits will still exist, but if no refs are pointing to them they will eventually get pruned from the git database.

squash the 10 commits into one commit without the file

You can skip having to amend 10 commits if you squash them into 1. If you don't need the history to reflect 10 separate edits, if you don't care if the history just had one commit with all the combined changes, then this is the ticket for you:

git reset HEAD~10

This will move HEAD of the current branch back 10 commits. All the changes you made in those 10 commits will still be in the worktree, but no longer commited into git. You can now delete the file you don't want and git add . the rest, and commit. OR, if you don't want to delete the file just keep it out of git, just git add <the other 9 files> and commit.

git filter-branch

This is the advanced user way. It is more automated. But I recommend all non git-gurus to use git rebase above because it a great way to become a guru.

The git docs for filter-branch has instructions for removing a file from every commit, so just follow those.

git-filter-repo

I've never used git-filter-repo (yet), but it comes very recommended (apparently by the git maintainers). It is essentially a more user friendly version of git filter-branch. @LeGEC provided the specific command in a comment so I'm adding it here:

git filter-repo --invert-paths --path file/to/ignore

Good luck!

Inigo
  • 12,186
  • 5
  • 41
  • 70
  • 1
    To add to @Inigo's answer : using `git filter-repo`, try : `git filter-repo --invert-paths --path file/to/ignore` – LeGEC Jul 05 '21 at 07:34
  • @LeGEC, do you think I'm off-base recommending the more manual path, interactive rebase, for people learning git? – Inigo Jul 05 '21 at 07:50
  • 1
    no I don't, it's a very useful and versatile tool, and is definitely worth mentioning. For the specific needs of this question ("drop one file from the history"), however, there are a one liners that solve it (using either `git filter-repo` or `git filter-branch`). so they are also worth mentioning. – LeGEC Jul 05 '21 at 08:00
  • i added the `filter-repo` command you provided to the answer. Thanks! – Inigo Jul 05 '21 at 10:37
  • 1
    I upper-case-ized `HEAD` in your answer, as lowercase `head` only works in particular cases (e.g., on Windows and macOS in their default case-folding file systems). It's also worth mentioning that this doesn't change the *original* commits: it makes new-and-improved *replacement* commits that we now want everyone to use instead of the originals. – torek Jul 05 '21 at 12:40
  • thanks. But re: commits not replaced: Yes, but that’s always true for any form of history rewriting. It’s the nature of git and its use of cryptographic hashes. And the branch head after the rebase will point to the new head. But when i get back to my computer I’ll add a note as you suggest. – Inigo Jul 05 '21 at 17:54