0

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.

Namal
  • 281
  • 2
  • 10

1 Answers1

1

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.

torek
  • 448,244
  • 59
  • 642
  • 775