Question

Our team is doing several projects in PHP. We have wrongly committed a folder of One Project to another. Now, we want to remove that particular commit from the Project. If we remove the particular folder/commit from the project then there is no issue for our project.

If we just remove the folder and issue the new commit at the current position then the folder is removed but it will remain in the history of Git. So, we want to remove it completely from refs, history and other things from Git.

We can also create a separate branch but the commit refs of authors would be lost. We want to only remove that particular commit. We have no issue in rewriting history or re-basing it but don't know how to do it.

In the project we have done 136 commits and want to remove commit no.76th. The required information about SHA is as under

5d39775b          //136th commit
a4df5ee9          //135th commit
6971cf35          //134th commit
.....
....
162f833c          //76th commit
32603274          //75th commit
.....
....
9770059          //1st commit
Was it helpful?

Solution 2

I have tried all the provided methods i.e. rebase and cherry-pick. I here to provide full information about both the methods tried and things done by me, to help to understand things with better view

Preparations

To check which is best I have done these things:

  1. Fork the remote repo so that the original would be intact and things can be understood easily. So, don't do any original repo until you are sure things done are correct or not.

  2. first taken the clean copy of Git Repo. Generally, whenever we stash or do other things that are also stored in Git as local Git database. So, that Git database doesn't have any local or temporary things.

  3. I have calculated total number of bytes taken by folder .git. As the repo is clean the minimum bytes would be correct here. To go further deep, I have noted down both bytes taken and bites taken on disk. As both are different things and they are:

bytes - 7,963,769 and size on disk - 8,028,160
  1. If you are using Windows and have installed any Anti virus then you have to disable the Active/Real time mode. As doing things Git need fast access of i/o files checking. What happens when file is created by Git, the active mode locks the new Created for checking virus and when Git tries to re-access that it fails due to locking by Antivirus. In Case of failure you have start your process again from 1.

Method I - Rebase

In re-basing method can be done by two ways. One is through --onto and other is with -i.

-i Method

I used the following command:

 git rebase -i 162f833c^

and it had opened the vim Editor and I see the list of commits starting from 162f833c not from master. If the end the following lines are provided.

# Rebase 3260327..5d39775 onto 3260327
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

I removed a line so that commit will be lost and saved the file and quit the Editor and when I quit it started re-basing as showing:

Rebasing ( 1/64) ..(2/64)

Successfully rebased and updated refs/heads/master.

And then tried to check the log to see that commit is lost or not and in the log I couldn't find the commit I want to delete. Means the successfully completed the command. The I tried to check the status as:

# On branch master
# Your branch and 'origin/master' have diverged,
# and have 64 and 65 different commit(s) each, respectively.

Since the commits is deleted but on the remote from which it is derived required to be pushed. So, pushed the commit with the force to override earlier code.

git push -f

After that I removed the local repo and re-fetched again the repo and the size it shows:

bytes 6,831,765 bytes
size on disk 6,897,664 bytes

--onto Method

After going through Many blogs and Manual, I found 1 blog here which really let me know how to use it. So the command I used is:

git rebase --onto 3260327 79504a5~1

and the out put is:

First, rewinding head to replay your work on top of it...
Applying: 77th Message
Applying: 78th Message
Applying: 79th Message
....
Last commit

And then tried to check the log to see that commit is lost or not and in the log I couldn't find the commit I want to delete. Means the successfully completed the command. The I tried to check the status as:

# On branch master
# Your branch and 'origin/master' have diverged,
# and have 64 and 65 different commit(s) each, respectively.

So, with single command all the things are done and then I done a force push to check the bytes etc as earlier

git push -f

After that I removed the local repo and re-fetched again the repo and the size it shows:

bytes - 6,831,389
size on disk - 6,893,568    

Rebase -i Vs --onto

Thus, after doing rebase method. I really approve the --onto method for deleting the commit in between as it is single command and the bytes are also saved marginally higher then -i method. The real advantage is that we don't have to do additional things in --onto method.

Method II - Cherry-pick

Cherry-pick really nice method but run through many commands and you have to take little cautions running the commands. First, I have to create a separate log file with

git log --all --decorate --oneline --graph > 1

Which showed me the log of the repo as we need to view it again and again. From this repo identify the commit you want to delete and copy the SHA Key the last good commit meaning thereby identify the commit up to which we didn't want to change any thing. So, the next command would be

git checkout 3260327 -b repair

generated output:

Switched to a new branch 'repair'

So, up to last Good commit we have created a new branch. So, things up to last Good commit doesn't change. Again I run the following command to view all the good commits:

git log --decorate --oneline --graph > 2

I have removed the world --all as, I want to view the commits of branch repair only. As it showed me Good Commits upto marked correctly. Now, next command is to be used with caution as it is includes the bad commit as starting point and end point the last commit done and it would be as:

git cherry-pick 162f833..5d39775

Output:

[repair 9ed3f18] xxxxxx
x files changed, xxx insertions(+), xx deletions(-)
[repair 7f06d73] xxxxx
xx files changed, xxx insertions(+), xx deletions(-)
.....
...

What this command does that it recommits all the commits leaving first commit provided above i.e (162f833) to last commit (5d39775) provided. So, the sha commits value would be changed accordingly as it is recommitting the commits one by one. Now it is the time to view the log as:

git log --all --decorate --oneline --graph > 3

output as:

* f61a9a5 (HEAD, repair) xxxxxx
* 25be3b9 xxxxx
* 49be029 xxxxx
 .......
 .......
| * 5d39775 (origin/master, origin/HEAD, master)
| * a4df5ee xxxxx
| * 6971cf3 xxxxxx
| .......
| .......
| * 162f833 xxxx
|/
* 3260327 xxxxx
......
* 9770059 xxxxx

So, viewing graph let us know that it had re-commits all the commits except the bad commit. showing you all the old sha keys with new keys. If everything is all right we have to make repair branch as master and deleting the master branch as:

git checkout master

and output as:

Switched to branch 'master'

First checkout master branch so that we can over ride the changes on master branch. Then

git reset --hard 3260327

and output as:

HEAD is now at 3260327 xxxxx

It will discard the commits after the good commit and now we have to merge the repair branch with master:

git merge repair

and output as:

Updating 3260327..40d290d
Fast-forward

Now, if you view the log it will not show you the bad commit and every thing is done. I done a force push to check the bytes etc as earlier

git push -f

After that I removed the local repo and re-fetched again the repo and the size it shows:

bytes - 6,831,556
size on disk - 6,897,664

Ranking of commands

Rebase --onto [First]

bytes - 6,831,389
size on disk - 6,893,568    

Cherry Pick [second]

bytes - 6,831,556
size on disk - 6,897,664

Rebase -i [third]

bytes 6,831,765 bytes
size on disk 6,897,664 bytes

I would like to give preference to git rebase --onto command as things are done clean way with single command easily.

OTHER TIPS

In your master branch, you can interactively rebase:

git rebase -i 162f833c^

This will rebase on top on the commit before the offending commit. Now just remove the offending commit from the list, save, and exist the editor (default in *nix platforms is vi).

This will rebase your branch on top the commit before the problematic one, without it, which seems to be what you're trying to achieve.

You can use interactive rebase.

Since the commit you want to delete has the sha1 162f833c, then just do git rebase -i 162f833c^

A text editor will open with a list of commits. Just delete the line corresponding to the commit you want to delete, save, and close.

As a safety net, when I do this kind of stuff, I like to put a tag on my HEAD first, so that if something goes wrong, I can just checkout this tag and retrieve my initial state.

You can automatically remove a commit and rewrite history by (where ${ref_to_delete} is the commit you want to remove, and assumes master is the branch that it is on):

git rebase --onto ${ref_to_delete}~ ${ref_to_delete} master

If you use a remote repository, you'll have to push with -f:

git push -f

You can also use git cherry-pick for this:

$ g  # g is my alias for git log --all --decorate --oneline --graph --color
* 618f8e5 [2013-12-14 15:13] (HEAD, master) me: good 6
* e27d6d7 [2013-12-14 15:13] me: good 5
* 533f6c3 [2013-12-14 15:13] me: good 4
* 877585f [2013-12-14 15:13] me: bad
* 00c06f3 [2013-12-14 15:12] me: good 3
* e9f80a4 [2013-12-14 15:12] me: good 2
* 3122ba7 [2013-12-14 15:12] me: good 1
* 98da603 [2013-12-14 15:12] me: first
$ git checkout 00c06f3 -b repair
Switched to a new branch 'repair'
$ git cherry-pick 877585f..618f8e5
[repair b340e21] good 4
 1 file changed, 1 insertion(+)
[repair 1d2e0d0] good 5
 1 file changed, 1 insertion(+)
[repair 1ed1d19] good 6
 1 file changed, 1 insertion(+)
$ g
* 1ed1d19 [2013-12-14 15:13] (HEAD, repair) me: good 6
* 1d2e0d0 [2013-12-14 15:13] me: good 5
* b340e21 [2013-12-14 15:13] me: good 4
| * 618f8e5 [2013-12-14 15:13] (master) me: good 6
| * e27d6d7 [2013-12-14 15:13] me: good 5
| * 533f6c3 [2013-12-14 15:13] me: good 4
| * 877585f [2013-12-14 15:13] me: bad
|/  
* 00c06f3 [2013-12-14 15:12] me: good 3
* e9f80a4 [2013-12-14 15:12] me: good 2
* 3122ba7 [2013-12-14 15:12] me: good 1
* 98da603 [2013-12-14 15:12] me: first
$ git checkout master
Switched to branch 'master'
$ git reset --hard repair
HEAD is now at 1ed1d19 good 6
$ git branch -d repair
Deleted branch repair (was 1ed1d19).
$ g
* 1ed1d19 [2013-12-14 15:13] (HEAD, master) me: good 6
* 1d2e0d0 [2013-12-14 15:13] me: good 5
* b340e21 [2013-12-14 15:13] me: good 4
* 00c06f3 [2013-12-14 15:12] me: good 3
* e9f80a4 [2013-12-14 15:12] me: good 2
* 3122ba7 [2013-12-14 15:12] me: good 1
* 98da603 [2013-12-14 15:12] me: first
$ # Done :)

git rebase is maybe better, so at least I am showing that there are usually multiple ways in git. After all, rebase is so powerful that "all meaningful operations in git can be expressed in terms of the rebase command" (Linus Torvalds).

git rebase -i 32603274.. This will show a huge list of commits.

pick 162f833c .. pick ...

change the first commit as d 162f833c ..

and save it.

This will drop the 76th commit alone

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top