0

I have pushed a java application to a bitbucket repository, including a couple of empty packages. Two development branches were created from the master branch and some classes were added to different packages. When I've tried to merge the other branch in my local development branch, no conflict appeared but my packages were deleted.I can see my commits in error log, I can even open my classes in IntelliJ through the Verion Control window, but they got deleted with the merge commit.

How can I revert back my changes? I can't cherry pick the commit, since it's already included in the branch. Git reset doesn't work as well. I can recreate the same classes and copy changes one by one if there is nothing else to do, but there must be some better solution.

Ivan
  • 8,508
  • 2
  • 19
  • 30
Nenad
  • 484
  • 3
  • 14
  • 2
    When you say __empty packages__, did you mean a directory? In that case, git doesn't track directories, so any empty directories will be ignored, as far as I know. If you want to tack empty directories, just add `.keep` file in the directory and that should do it. – Prav May 31 '19 at 19:06
  • 2
    Try using the `git reflog` to locate the commit, it should be there on the list, if it was the tip of the branch before merge; if not you can use filtering as well. More on that here https://git-scm.com/docs/git-reflog – Prav May 31 '19 at 19:08
  • Yes, you're right. Java packages are just directories for Git. It's not a big deal if the empty directories are not committed, I expected that, but I wonder how my packages got deleted. I can see the commits in the git log, but the java package and the including java class files, are not there. I can recreate the class files and copy previous versions, but I hope there is a more suitable solution. I'll check the documentation for git reflog, thanks. – Nenad May 31 '19 at 19:16
  • 1
    Maybe https://stackoverflow.com/questions/1407638/git-merge-removing-files-i-want-to-keep or https://git.github.io/htmldocs/howto/revert-a-faulty-merge.html can be of any help. – joran May 31 '19 at 20:37

1 Answers1

-1

This happened because someone—presumably not you—deleted the code.

Remember that merge does not mean make these the same. You tell Git:

git checkout somebranch
git merge anotherbranch

That doesn't mean make a new commit in somebranch that exactly matches the last commit in anotherbranch. Instead, this tells Git:

  • Find some commit at or before the tip of somebranch, that's also at or before the tip of anotherbranch. This commit that comes before either tip has to be on both branches, and should be the best common ancestor. Git calls this commit the merge base.

  • Now that we know that whoever made somebranch—perhaps us—started with commit B (for base), and whoever made anotherbranch (maybe this was us instead) also started with commit B, compare commit B to the tip of somebranch to see what we/they changed. Then, compare B to the tip of anotherbranch to see what they/we changed.

The merge now combines these two sets of changes. For the merge to have decided that file somedir/somepackage.java should be deleted, the change from B to one tip must have been: do nothing at all. The change from B to the other tip must have been: delete file somedir/somepackage.java. The combination of these two changes is obvious: delete the file!

Hence, the correct merge result—the combination of the change made by person S on somebranch who said delete the file, and the non-change by person A on anotherbranch who didn't say anything at all—is to delete the file, and Git did that. (Or, if I have the two branches swapped, it doesn't matter: that's still the automatically-right result. It's right for Git even if, by your smarter-than-Git human evaluation, it's wrong.)

The file is still complete, whole and intact, in one of the two commits just before the merge, and also in the merge-base commit. (It's obviously non-existent in the other.) Just extract it from any commit that has it. Those commits still exist, and still hold all their files in their committed state, forever: that's why you have a version control system, so as to hold everything everyone ever did.

Getting a file back

To get the file with path P from commit H (where H is some commit hash such as a123456), run:

git checkout H -- P

e.g., git checkout a123456 -- somedir/somepackage.java. This will extract the copy that's in the commit, placing that copy into your index (ready for your next commit) and your work-tree (ready for you to view it).

torek
  • 448,244
  • 59
  • 642
  • 775
  • No, this can’t be the case. As I wrote, there are new class files in different packages in the 2 different branches, so they couldn’t be deleted on the other branch. – Nenad Jun 01 '19 at 05:31
  • I guarantee that if `git merge` deleted the files, it *is* the case. (If something or someone else deleted the files, that's on them, of course.) Find the merge base of the two commits, using `git merge-base --all `. If this prints more than one hash ID, you have a complex case and there are some extra steps to insert, but chances are there will be just one hash ID. Run `git diff --find-renames` twice, from this merge base to each of the two branch tips, and observe what Git thinks is changed. – torek Jun 01 '19 at 06:02
  • As I wrote, the classes were just created. There are just tree commits, the initial project commit and 2 other commits in separate branches. The base package is the same and I’ve already created the empty packages on the initial commit, so that’s probably the problem. But I guarantee that the files couldn’t be deleted, I’ve just created them. – Nenad Jun 01 '19 at 06:13
  • If there are only the three commits, presumably the parent of each of the two branch tips is the initial commit. (You can use `git log --graph` to verify this.) Then run the two `git diff` commands to compare the three commits: that's what Git sees, so that's what Git merges. If there are new files, Git considers those added: if they have the same file name in both left and right side commits, you will get an "add/add" conflict and have to resolve this conflict manually. If they have different file names, the combination of "create file somedir/file1" on the left and [continued] – torek Jun 01 '19 at 06:16
  • ... "create somedir/file2" on the right is: keep both files. Git's merge uses simple, stupid, file-by-file, then line-by-line comparisons to achieve its result. (This assumes you are using plain `git merge`, not `git merge -s ours`, but that would just keep your branch tip.) – torek Jun 01 '19 at 06:17
  • I did compare the log, I can even see the changes in the other branch, there’s a pull request for it towards the base branch on Bitbucket. There are no deletions there. There must be something with the empty packages in the initial commit, which Is weird because empty folders can’t be committed. There weren’t any conflicts during merging. – Nenad Jun 01 '19 at 06:24
  • It's true that empty folders cannot be committed (see [here](https://stackoverflow.com/questions/115983/how-can-i-add-an-empty-directory-to-a-git-repository/8944077#8944077): if you try it goes wrong). When GitHub shows a folder icon, it's really talking about a *submodule*; submodules are separate Git repositories. If your files are in a different repository (because of a submodule), then they are simply not in *that* repository at all. I don't know if the Bitbucket web interface uses the same idea here (of showing an empty-folder for a submodule), but that's definitely worth checking. – torek Jun 01 '19 at 06:26
  • No mate, it’s the same repository, there are no submoduIes. I see my commit in log, I can open class files in the IntelliJ version control window. But thanks for the suggestions anyway. – Nenad Jun 01 '19 at 06:32
  • When you say "see your commit in log", do you mean that `git log -p` shows you the commit plus the added code/files? If so, the code is in that commit; see the edit to my answer. Note, too, that IntelliJ has a bunch of caching it hides from Git: something you see there is not necessarily committed. I have not used IntelliJ but there are mentions here and there of using IntelliJ to get files back after accidentally deleting them before committing, because IntelliJ has squirreled copies away elsewhere. – torek Jun 01 '19 at 06:35
  • Yes, I've just executed "git log -p" in PowerShell and I can see my files. I've also opened the project in explorer and the files are not there. – Nenad Jun 01 '19 at 06:47
  • OK, so, to get them back, use `git checkout -- `. Meanwhile, if you want to see why Git thought it was appropriate to delete them during merge, use `git merge-base` and the two `git diff` operations to see what Git thinks happened. (If you used a web-based merge button on, e.g., bitbucket, be sure you're viewing the same commits, by hash ID, as the ones that the Git on bitbucket sees.) – torek Jun 01 '19 at 06:51
  • I've just pushed my changes to Bitbucket and there is the same strange behavior as well. I can see the changes in the commits tab, but I can't find the files in the source tab. I've already done `git reset --hard`, but it didn't help. I can only see 2 commits on Bitbucket, the initial commit and the second commit where the class files were added, but they don't exist in source tab. I've messed up something with git references. It's not so serious though, I can create the files again and copy previous contents. – Nenad Jun 01 '19 at 07:04