private @NotNull RepositoryMergeResult mergeRepository(@NotNull GitRepository repository,
                                                         @NotNull String branchToMerge,
                                                         @NotNull List<String> mergeParams) {
    boolean fatalErrorHappened = false;
    boolean alreadyUpToDateRepository = false;
    VirtualFile root = repository.getRoot();

    Hash startHash = getHead(repository);

    GitLocalChangesWouldBeOverwrittenDetector localChangesDetector =
      new GitLocalChangesWouldBeOverwrittenDetector(root, GitLocalChangesWouldBeOverwrittenDetector.Operation.MERGE);
    GitSimpleEventDetector unmergedFiles = new GitSimpleEventDetector(GitSimpleEventDetector.Event.UNMERGED_PREVENTING_MERGE);
    GitUntrackedFilesOverwrittenByOperationDetector untrackedOverwrittenByMerge =
      new GitUntrackedFilesOverwrittenByOperationDetector(root);
    GitSimpleEventDetector mergeConflict = new GitSimpleEventDetector(GitSimpleEventDetector.Event.MERGE_CONFLICT);
    GitSimpleEventDetector alreadyUpToDateDetector = new GitSimpleEventDetector(GitSimpleEventDetector.Event.ALREADY_UP_TO_DATE);
    MyAmbiguousNameDetector ambiguousReferenceDetector = new MyAmbiguousNameDetector();

    GitCommandResult result = myGit.merge(repository, branchToMerge, mergeParams,
                                          localChangesDetector, unmergedFiles, untrackedOverwrittenByMerge, mergeConflict,
                                          alreadyUpToDateDetector, ambiguousReferenceDetector);

    String fullName = myReferenceToMerge.getFullName();
    if (ambiguousReferenceDetector.hasHappened() //happens when e.g., tag with the same name as the branch exists
        && !branchToMerge.equals(fullName)) {
      return mergeRepository(repository, fullName, mergeParams);
    }

    if (result.success()) {
      LOG.info("Merged successfully");
      updateAndRefreshChangedVfs(repository, startHash);
      markSuccessful(repository);
      if (alreadyUpToDateDetector.hasHappened()) {
        alreadyUpToDateRepository = true;
      }
    }
    else if (unmergedFiles.hasHappened()) {
      LOG.info("Unmerged files error!");
      fatalUnmergedFilesError();
      fatalErrorHappened = true;
    }
    else if (localChangesDetector.wasMessageDetected()) {
      LOG.info("Local changes would be overwritten by merge!");
      boolean smartMergeSucceeded = proposeSmartMergePerformAndNotify(repository, localChangesDetector);
      if (!smartMergeSucceeded) {
        fatalErrorHappened = true;
      }
    }
    else if (mergeConflict.hasHappened()) {
      LOG.info("Merge conflict");
      myConflictedRepositories.put(repository, Boolean.FALSE);
      updateAndRefreshChangedVfs(repository, startHash);
      markSuccessful(repository);
    }
    else if (untrackedOverwrittenByMerge.wasMessageDetected()) {
      LOG.info("Untracked files would be overwritten by merge!");
      fatalUntrackedFilesError(repository.getRoot(), untrackedOverwrittenByMerge.getRelativeFilePaths());
      fatalErrorHappened = true;
    }
    else {
      LOG.info("Unknown error. " + result);
      fatalError(getCommonErrorTitle(), result);
      fatalErrorHappened = true;
    }

    return new RepositoryMergeResult(fatalErrorHappened, alreadyUpToDateRepository);
  }
private @NotNull RepositoryMergeResult mergeRepository(@NotNull GitRepository repository,
                                                         @NotNull String branchToMerge,
                                                         @NotNull List<String> mergeParams)