situation is as follows: I have two commits on my local machine. In the first I added two files that I would like to throw out. I don't care if they are lost or not, also I don't care about the history. In the second commit I had several merge conflicts, that had to be resolved (not in those two files though). So I don't know if a soft reset would be a good idea here, because of the merge conflicts. Maybe it is also better to rebase push everything and then delete the files from the repository? IDk..but I don't want to deal with the merge conflicts again. Thx.
-
Is the first commit solely related to these two unwanted files or are there changes to other files as well? – mnestorov May 15 '20 at 13:08
-
Did you try... git reset – Denis Tsoi May 15 '20 at 13:41
-
Check out [this](https://stackoverflow.com/a/47818810/6577998) and [this](https://stackoverflow.com/a/47578086/6577998) as it might give you the solution you want. – mnestorov May 15 '20 at 13:43
1 Answers
Let's say you have two commits in your own Git repository that you have never allowed any other Git repository to "see". For instance, let's assume that (a) you've never used git push
to send them to someone else, and (b) you don't allow random users to get into your laptop and read all your files.
In this case, you're in great shape, because no one else has those commits. This means you have every option.
... I don't want to deal with the merge conflicts again.
This reduces your set of options slightly.
The things to remember here are:
Git is all about commits. Commits have hash IDs, so you can tell whether any given Git repository has any given commit. For example, you can just run
git show hash-ID
—I recommend cutting and pasting the hash ID from somewhere else because typing one in correctly is way too hard—and see if this Git knows about this hash ID.Git itself finds hash IDs using branch names. That's Git's purpose for branch names: to find hash IDs. You can use them for whatever you want; Git is going to use each name to find one particular commit hash ID.
When you made your two commits, you did so by starting with git checkout branch-name
. (If you didn't run your own git checkout
, Git already did, so one way or another, you might as well have done this.) Then you edited some files, used git add
as usual, and ran git commit
to make the first commit. Having done that, you may have run another git checkout
, or not; then you ran git merge
, or did something that ran git merge
, and made your second commit, which was an actual merge commit—only real merges can have merge conflicts.
It's worth drawing what you have now. You can get git log
to do this for you (see Pretty git branch graphs, and especially this answer), or just draw it yourself. You should do this, not me, because you have your Git repository and I do not and my drawing of a hypothetical Git repository may not match your actual one. Still, your commits might look like this:
...--F--G--H---I <-- master, origin/master
\ \
\ L--M <-- feature (HEAD)
\ /
J--K <-- origin/feature
That is, you made commit L
yourself first:
git checkout feature # new branch `feature` identifies commit `K`
... do some work ...
git add ...
git commit # creates commit L
Then you merged, using git merge
or perhaps git pull
or something along those lines:
git merge origin/master # creates commit M
Once you draw the situation it's much easier to talk about which files are in which commits.
In the first I added two files that I would like to throw out.
Those two files are new in commit L
. That is, when comparing commit K
to commit L
, there may be any number of changes in the snapshots stored under K
and L
, but there are definitely also two files in L
that are not in K
.
I don't care if they are lost or not, also I don't care about the history.
This gives you a lot of options.
In the second commit I had several merge conflicts, that had to be resolved (not in those two files though).
This means merge commit M
contains your answer to problems Git had when merging files-as-seen-in-snapshot-L
with files-as-seen-in-snapshot-I
(with L
and I
being the two parents of merge commit M
).
Since you don't want to repeat that merge work, you should hang on to commit M
for at least some time yet, regardless of what option you choose next.
Option: add a new commit
You can simply add a new commit after M
in which you remove the files:
git rm file1 file2
git commit
This adds a new commit N
:
...--F--G--H---I <-- master, origin/master
\ \
\ L--M--N <-- feature (HEAD)
\ /
J--K <-- origin/feature
Your name feature
now refers to new commit N
, in which those two files are absent.
The advantage is clear: this is very quick and easy to do.
The disadvantage is that those two files are still, and will forever be, present in commits L
and M
. If and when you give commit N
to anyone, you must also give them commits L
and M
, and everyone will now see those two files in those two commits, if they look.
If that's not a problem, this is your easiest option.
You can build two new commits
Suppose that while you don't care much, you'd like to not expose those two files to anyone else. This is pretty easy! You can build two new commits, starting like this:
git checkout -b better-feature <anything that identifies commit K>
You can use a raw hash ID, or in this particular case, as indicated by the drawing, the name origin/feature
, to identify commit K
. You now have:
...--F--G--H---I <-- master, origin/master
\ \
\ L--M <-- feature
\ /
J--K <-- better-feature (HEAD), origin/feature
You can now copy commit L
but not commit the result yet:
git cherry-pick -n <hash-of-L>
(use cut-and-paste from git log
, for instance, to get the hash ID). The -n
tells Git don't commit this yet. Now you can remove your two problematic files:
git rm file1 file2
and then commit:
git commit
to get a copy of L
that lacks the two files, which we'll call L'
:
...--F--G--H---I <-- master, origin/master
\ \
\ L--M--N <-- feature
\ /
J--K <-- origin/feature
\
L' <-- better-feature (HEAD)
Now you have to repeat the merge.
I don't want to deal with the merge conflicts again.
This is the drawback to this approach: you do have to deal with the merge conflicts. But you don't have to actually solve them again, you can just copy your existing solution from existing commit M
. Here is how you might do that:
git merge origin/master # just as before
Git prints a lot of stuff now, including showing the same two merge conflicts as before. But instead of opening up the files to edit them, or using git mergetool
, or whatever you did last time, this time you just tell Git: get me the same answer I used last time.
Let's say that the two conflicted merge files are named conflicts/c1.txt
and conflicts/c2.txt
. You would now run:
git checkout feature -- conflicts/c1.txt conflicts/c2.txt
The files are now resolved, using your previous resolutions! Use git status
to verify this, and look at the files in your favorite editor or file-viewer to see the updated contents.
(In Git 2.23 or later, you might prefer to use git restore
with --source feature
and both options -s
and -w
, rather than git checkout
, here. The effect will be exactly the same though.)
Assuming that resolves everything, and that you took Git's resolution for all other files, you're now ready to finish the merge.
The final result is a new merge M'
that uses the same resolution for those two files. Unlike commit M
but like commit L'
, commit M'
lacks the two files you deliberately didn't add this time, so:
git diff feature better-feature
(which compares commit M
to commit M'
) shows those two files being deleted.
You can now delete the name feature
entirely, rename better-feature
to feature
, set the upstream of the new feature
if necessary / appropriate, and so on. Or you can use git checkout -B feature better-feature
to just force the name feature
to point to the new commit, then delete the name better-feature
, to get:
...--F--G--H---I <-- master, origin/master
\ \
\ L'-M' <-- feature (HEAD)
\ /
J--K <-- origin/feature
Existing commits L
and M
still exist, but because there is no longer a name with which to find them, git log --all --decorate --graph --oneline
won't show them. Running git push origin feature
will send to origin
commits M'
and L'
, but not commits M
and L
.
There are as many other options as you can think of
There are lots of other ways to achieve whatever result you like. Git is a set of tools, not a particular solution to one particular problem. These two solutions, made using the tool-set that Git provides, should solve your particular problem, though.

- 448,244
- 59
- 642
- 775