Is it possible to drop commits like #9701282
in the Git
history?
I don't mean repo above #b372fa5
should change,
just graph should be simplified in this place.
Commit in purple branch looks like redundant.
It shouldn't be very hard to apply it's changes inside of #b372fa5
merge point.
What techniques exists for implement this action?

- 14,461
- 4
- 37
- 66
3 Answers
Git is like the classical time machines of old sci-fi movies. You can go back in time, but if you alter the past you impact the present.
You can use one of git's most powerful features rebase to do just about anything you want, however, the object names after and including the modified commits will be changed.
If you are collaborating with others it is advisable not to do this as they will have to recover from the operation.
Here is an example of a before and after I just ran as an example:
git log --oneline --graph
* d3959f3 Something
* 9e58b42 Modify new
* ad1dea6 Merge branch 'mybranch'
|\
| * e75658b somechange
* | 1076214 New File
* | 49fe418 Updated on master
|/
* b94640d Updated
* da15c9c Initial commit
git log --oneline --graph
* 89fe296 Something
* d9620cc Modify new
* 6301614 New File - somechange
* 49fe418 Updated on master
* b94640d Updated
* da15c9c Initial commit
To perform the operation I used:
git rebase -i ad1dea6~2
# a more common form is to modify n number of commits starting at HEAD
git rebase -i HEAD~n
This will open the configured editor with changes you specified in a "todo list" allowing you to interactively pick, squash, reword, drop, reorder, and even run arbitrary shell commands with exec.
Here is what it looks like in vi:
pick 1076214 New File
pick e75658b somechange
pick 9e58b42 Modify new
pick d3959f3 Something
# Rebase 49fe418..d3959f3 onto 49fe418
#
# 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
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
I chose the simplest option to squash the change which didn't even require a conflict resolution, but does prompt you to confirm the commit message (which as you can see I did alter).
Once I saved the commit message change git automatically rewrote the rest of history without prompting me any further.
I was even able to try out a few different permutations of this by falling back on git's handy reflog
command.
git reflog
In git even orphaned changes can be recovered, so long as they have not been collected in a gc
run. You can invoke the garbage collector at any time with gc
but some commands do this periodically anyway.
One way to recover from a bad state with reflog
is to find the point in reflog
you are interested in getting back to and run:
git reset --hard <SHA>
Using rebase in a workflow
The previous discussion was focused on using rebase
to alter history with rebase -i
. This is actually a pretty useful feature for a local workflow where you want to make periodic commits while working on a feature and then before merging into a collaborative branch with others you can reorganize your local commits to make your history easier to read or combine commits to create working code at each commit.
The second use case is when performing a rebase
merge. This can be done by replacing your usage of merge
with rebase.
git rebase master
The best way to describe how this works is to imagine that all of the changes on your current branch are stored away leaving you at the common commit where the two branches diverged. Next the branch is “fast forwarded” to the most recent commit on the target branch. Finally the stored commits are applied one by one on top of the new HEAD giving you the chance to resolve conflicts at each commit (as opposed to merge where you only resolve once forming the merge commit). The result is a fresh new set of commits (new time stamps and all, possibly modified with conflict resolution changes) giving the appearance that the changes where made in sequence after the target branch HEAD.
You can also use the --rebase
option with pull.
NOTE: Some workplaces do not like altering commit history and prefer to even disable fast forward merges with --no-ff
when performing a merge. It is obviously up to your judgement on when to use rebasing and if it makes sense for your situation. You can often still use the local interactive form to customize your own workflow as these changes have not yet been pushed to a remote.
Some of this I took from my recent blog post about general git usage. I tried to include more then average information while still keeping it as compact as I could.

- 4,875
- 26
- 45
-
I updated the answer with a common general use case as well as an example of what this operation looks like in the editor I have configured (vi). Let me know if you need any more elaboration. You can also look at the [git-rebase doc here](https://git-scm.com/docs/git-rebase) – Matthew Sanders Nov 17 '15 at 20:03
-
Thank you. I understand this. My local branch now like a string. But I will revert all back, anyway. Because my local master has compete different changesets from `origin/master`. `rebase origin/master` will restore merge loop. It can't be achieved without remotes rebase... Can I `rebase origin/master master && git push -f`? – vp_arth Nov 17 '15 at 20:24
-
Depending on the repository configuration you may be able to force push a branch. If force is disabled you may have to delete and recreate a branch. I would not recommend doing this if you are not the owner of this repository, especially not with master :P git push origin :somebranchtodelete – Matthew Sanders Nov 17 '15 at 20:59
-
I prefer `--delete` for this :) – vp_arth Nov 17 '15 at 21:05
git branch -d branch_name
removes merged branches (Don't worry, it will not delete anything if it's not merged, you'd need git branch -D for that)
To see all currently merge branches try:
git branch --merged
If you want to keep your history linear, I would suggest using git rebase
In general, it's sometimes a good idea to keep your history though.
As usual, I would recommend https://git-scm.com/book/en/v2 chapter 3. It will really help you understand everything better.
If you really actually want to remove commit (deleting a branch only makes them unreachable, then I suggest reading about reflogs - be careful though, few things in git can wreck your repo and permanently remove your code, but this might just be one of them)

- 900
- 7
- 14
-
3I think the OP was asking how to remove the actual commit, not just the branch reference. – Matthew Sanders Nov 17 '15 at 19:20
-
-
This commit is parent for many remote branches :) I think it's a bad idea to touch it. It something like `rm -rf .git; git init;...`. Just wanted to know howto.. – vp_arth Nov 17 '15 at 20:32
If the entire commits' trees are correct, if only the parents need to be changed, then this is very easy to do with a graft and git filter-branch
.
First, pretend that b372fa5^
is b372fa5
's only parent.
echo $(git rev-parse b372fa5 b372fa5^) > .git/info/grafts
Then inspect the history and check whether it's correct:
git log --graph
Then rewrite the commits to make this graft permanent:
git filter-branch
And finally clean up:
rm .git/info/grafts
-
[git filter-branch](https://git-scm.com/docs/git-filter-branch) is indeed another good method. Note: this will also modify history as I explained in my answer. I do like this option, however, for sanitizing a repository of large files for example. – Matthew Sanders Nov 17 '15 at 19:36
-
Yes, as you correctly point out you cannot change the parents of `b372fa5` without changing `b372fa5` itself. Perhaps interesting to the OP is that if it's not actually needed to change `b372fa5` on the remote, if it's just needed to get a clear local view, then it's possible to just stop after creation of the `grafts` file and not make it permanent. It will remain in effect on your local system, and only on your local system, for as long as the `grafts` file is there. – Nov 17 '15 at 19:42
-
@vp_arth `git filter-branch` by default only rewrites the history of the current branch. Do you perhaps have multiple branches that have the commits you mentioned in their history? Additionally, `git filter-branch` saves a backup of what the branch looked like before. Ordinarily you wouldn't see it, but if you check something like `git log --graph --all`, they might pop up there. – Nov 17 '15 at 20:11
-
I always see log with `git log --pretty=format:'%C(auto)%h %ad | %s%d <%an>' --graph --all --date=short` – vp_arth Nov 17 '15 at 20:26
-
@vp_arth Yeah, then it's the `--all` option that does it, it includes the backup created by `git filter-branch`. After you've verified that the rewritten commits are correct, you can choose to get rid of the backups as described [here on SO](http://stackoverflow.com/questions/7654822/remove-refs-original-heads-master-from-git-repo-after-filter-branch-tree-filte), in which case `--all` will no longer cause the old commits to be shown. – Nov 17 '15 at 20:29
-
This commit is parent to many remote branches(all of them linear with 1-2 own commits on their heads, just not rebased yet). I think it can't be just filtered. without complete rebasing of remote branches – vp_arth Nov 17 '15 at 20:36
-
Anyway, great thanks to share your knowledge, I never used `grafts` before :) – vp_arth Nov 17 '15 at 20:38