public @NotNull GitPushResult execute() {
    PushUpdateSettings updateSettings = readPushUpdateSettings();
    Label beforePushLabel = null;
    Label afterPushLabel = null;
    Map<GitRepository, String> preUpdatePositions = updateRootInfoAndRememberPositions();
    Boolean rebaseOverMergeProblemDetected = null;

    final Map<GitRepository, GitPushRepoResult> results = new HashMap<>();
    Map<GitRepository, GitUpdateResult> updatedRoots = new HashMap<>();

    try {
      Collection<GitRepository> remainingRoots = myPushSpecs.keySet();
      for (int pushAttempt = 0;
           pushAttempt < MAX_PUSH_ATTEMPTS && !remainingRoots.isEmpty();
           pushAttempt++, remainingRoots = getRejectedAndNotPushed(results)) {
        LOG.debug("Starting push attempt #" + pushAttempt);
        Map<GitRepository, GitPushRepoResult> resultMap = push(myRepositoryManager.sortByDependency(remainingRoots));
        results.putAll(resultMap);

        GroupedPushResult result = GroupedPushResult.group(resultMap);

        // stop if error happens, or if push is rejected for a custom reason (not because a pull is needed)
        if (!result.errors.isEmpty() || !result.customRejected.isEmpty()) {
          break;
        }

        if (!result.rejected.isEmpty()) {

          if (myForceMode.isForce() ||
              pushingToNotTrackedBranch(result.rejected) ||
              pushingNotCurrentBranch(result.rejected)) {
            break;
          }

          // propose to update if rejected
          if (pushAttempt == 0 && !mySettings.autoUpdateIfPushRejected()) {
            // the dialog will be shown => check for rebase-over-merge problem in advance to avoid showing several dialogs in a row
            rebaseOverMergeProblemDetected = !findRootsWithMergeCommits(myRepositoryManager.getRepositories()).isEmpty();

            updateSettings = showDialogAndGetExitCode(result.rejected.keySet(), updateSettings,
                                                      rebaseOverMergeProblemDetected.booleanValue());
            if (updateSettings == null) break;
            savePushUpdateSettings(updateSettings, rebaseOverMergeProblemDetected.booleanValue());
          }

          if (beforePushLabel == null) { // put the label only before the very first update
            beforePushLabel = LocalHistory.getInstance().putSystemLabel(myProject,
                                                                        GitBundle.message("push.local.history.system.label.before"));
          }
          Collection<GitRepository> rootsToUpdate = myRepositoryManager.getRepositories();
          LOG.debug("roots to update: " + rootsToUpdate);
          GitUpdateResult updateResult = update(rootsToUpdate, updateSettings.getUpdateMethod(), rebaseOverMergeProblemDetected == null);
          for (GitRepository repository : rootsToUpdate) {
            updatedRoots.put(repository, updateResult); // TODO update result in GitUpdateProcess is a single for several roots
          }
          if (!updateResult.isSuccess() ||
              updateResult == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS || updateResult == GitUpdateResult.INCOMPLETE) {
            break;
          }
        }
      }
    }
    finally {
      if (beforePushLabel != null) {
        afterPushLabel = LocalHistory.getInstance().putSystemLabel(myProject, GitBundle.message("push.local.history.system.label.after"));
      }
      for (GitRepository repository : myPushSpecs.keySet()) {
        repository.update();
      }
    }
    return prepareCombinedResult(results, updatedRoots, preUpdatePositions, beforePushLabel, afterPushLabel);
  }
public @NotNull GitPushResult execute()