<commit_before>/*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package git4idea.push;

import com.intellij.notification.NotificationType;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ui.UIUtil;
import git4idea.GitBranch;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.Notificator;
import git4idea.branch.GitBranchPair;
import git4idea.commands.Git;
import git4idea.commands.GitCommandResult;
import git4idea.config.GitConfigUtil;
import git4idea.config.GitVcsSettings;
import git4idea.config.UpdateMethod;
import git4idea.history.GitHistoryUtils;
import git4idea.history.browser.GitCommit;
import git4idea.jgit.GitHttpAdapter;
import git4idea.repo.GitBranchTrackInfo;
import git4idea.repo.GitRemote;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.settings.GitPushSettings;
import git4idea.update.GitUpdateProcess;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Collects information to push and performs the push.
 *
 * @author <NAME>
 */
public final class GitPusher {

  /**
   * if diff-log is not available (new branch is created, for example), we show a few recent commits made on the branch
   */
  static final int RECENT_COMMITS_NUMBER = 5;
  
  static final GitBranch NO_TARGET_BRANCH = new GitBranch("", false, true);

  private static final Logger LOG = Logger.getInstance(GitPusher.class);
  private static final String INDICATOR_TEXT = "Pushing";
  private static final int MAX_PUSH_ATTEMPTS = 10;

  private final Project myProject;
  private final ProgressIndicator myProgressIndicator;
  private final Collection<GitRepository> myRepositories;
  private final GitVcsSettings mySettings;
  private final GitPushSettings myPushSettings;
  private final Git myGit;

  public static void showPushDialogAndPerformPush(@NotNull final Project project) {
    final GitPushDialog dialog = new GitPushDialog(project);
    dialog.show();
    if (dialog.isOK()) {
      Task.Backgroundable task = new Task.Backgroundable(project, INDICATOR_TEXT, false) {
        @Override
        public void run(@NotNull ProgressIndicator indicator) {
          new GitPusher(project, indicator).push(dialog.getPushInfo());
        }
      };
      GitVcs.runInBackground(task);
    }
  }

  // holds settings chosen in GitRejectedPushUpdate dialog to reuse if the next push is rejected again.
  static class UpdateSettings {
    private final boolean myUpdateAllRoots;
    private final UpdateMethod myUpdateMethod;

    private UpdateSettings(boolean updateAllRoots, UpdateMethod updateMethod) {
      myUpdateAllRoots = updateAllRoots;
      myUpdateMethod = updateMethod;
    }

    public boolean shouldUpdateAllRoots() {
      return myUpdateAllRoots;
    }

    public UpdateMethod getUpdateMethod() {
      return myUpdateMethod;
    }

    public boolean shouldUpdate() {
      return getUpdateMethod() != null;
    }

    @Override
    public String toString() {
      return String.format("UpdateSettings{myUpdateAllRoots=%s, myUpdateMethod=%s}", myUpdateAllRoots, myUpdateMethod);
    }
  }

  public GitPusher(@NotNull Project project, @NotNull ProgressIndicator indicator) {
    myProject = project;
    myProgressIndicator = indicator;
    myRepositories = GitRepositoryManager.getInstance(project).getRepositories();
    mySettings = GitVcsSettings.getInstance(myProject);
    myPushSettings = GitPushSettings.getInstance(myProject);
    myGit = ServiceManager.getService(Git.class);
  }

  /**
   * @param pushSpecs which branches in which repositories should be pushed.
   *                               The most common situation is all repositories in the project with a single currently active branch for
   *                               each of them.
   * @throws VcsException if couldn't query 'git log' about commits to be pushed.
   * @return
   */
  @NotNull
  GitCommitsByRepoAndBranch collectCommitsToPush(@NotNull Map<GitRepository, GitPushSpec> pushSpecs) throws VcsException {
    Map<GitRepository, List<GitBranchPair>> reposAndBranchesToPush = prepareRepositoriesAndBranchesToPush(pushSpecs);
    
    Map<GitRepository, GitCommitsByBranch> commitsByRepoAndBranch = new HashMap<GitRepository, GitCommitsByBranch>();
    for (GitRepository repository : myRepositories) {
      List<GitBranchPair> branchPairs = reposAndBranchesToPush.get(repository);
      if (branchPairs == null) {
        continue;
      }
      GitCommitsByBranch commitsByBranch = collectsCommitsToPush(repository, branchPairs);
      commitsByRepoAndBranch.put(repository, commitsByBranch);
    }
    return new GitCommitsByRepoAndBranch(commitsByRepoAndBranch);
  }

  @NotNull
  private Map<GitRepository, List<GitBranchPair>> prepareRepositoriesAndBranchesToPush(@NotNull Map<GitRepository, GitPushSpec> pushSpecs) throws VcsException {
    Map<GitRepository, List<GitBranchPair>> res = new HashMap<GitRepository, List<GitBranchPair>>();
    for (GitRepository repository : myRepositories) {
      GitPushSpec pushSpec = pushSpecs.get(repository);
      if (pushSpec == null) {
        continue;
      }
      res.put(repository, Collections.singletonList(new GitBranchPair(pushSpec.getSource(), pushSpec.getDest())));
    }
    return res;
  }

  @NotNull
  private GitCommitsByBranch collectsCommitsToPush(@NotNull GitRepository repository, @NotNull List<GitBranchPair> sourcesDestinations)
    throws VcsException {
    Map<GitBranch, GitPushBranchInfo> commitsByBranch = new HashMap<GitBranch, GitPushBranchInfo>();

    for (GitBranchPair sourceDest : sourcesDestinations) {
      GitBranch source = sourceDest.getBranch();
      GitBranch dest = sourceDest.getDest();
      assert dest != null : "Destination branch can't be null here for branch " + source;

      List<GitCommit> commits;
      GitPushBranchInfo.Type type;
      if (dest == NO_TARGET_BRANCH) {
        commits = collectRecentCommitsOnBranch(repository, source);
        type = GitPushBranchInfo.Type.NO_TRACKED_OR_TARGET;
      }
      else if (GitUtil.repoContainsRemoteBranch(repository, dest)) {
        commits = collectCommitsToPush(repository, source.getName(), dest.getName());
        type = GitPushBranchInfo.Type.STANDARD;
      } 
      else {
        commits = collectRecentCommitsOnBranch(repository, source);
        type = GitPushBranchInfo.Type.NEW_BRANCH;
      }
      commitsByBranch.put(source, new GitPushBranchInfo(source, dest, commits, type));
    }

    return new GitCommitsByBranch(commitsByBranch);
  }

  private List<GitCommit> collectRecentCommitsOnBranch(GitRepository repository, GitBranch source) throws VcsException {
    return GitHistoryUtils.history(myProject, repository.getRoot(), "--max-count=" + RECENT_COMMITS_NUMBER, source.getName());
  }

  @NotNull
  private List<GitCommit> collectCommitsToPush(@NotNull GitRepository repository, @NotNull String source, @NotNull String destination)
    throws VcsException {
    return GitHistoryUtils.history(myProject, repository.getRoot(), destination + ".." + source);
  }

  /**
   * Makes push, shows the result in a notification. If push for current branch is rejected, shows a dialog proposing to update.
   */
  public void push(@NotNull GitPushInfo pushInfo) {
    push(pushInfo, null, null, 0);
  }

  /**
   * Makes push, shows the result in a notification. If push for current branch is rejected, shows a dialog proposing to update.
   * If {@code previousResult} and {@code updateSettings} are set, it means that this push is not the first, but is after a successful update.
   * In that case, if push is rejected again, the dialog is not shown, and update is performed automatically with the previously chosen
   * option.
   * Also, at the end results are merged and are shown in a single notification.
   */
  private void push(@NotNull GitPushInfo pushInfo, @Nullable GitPushResult previousResult, @Nullable UpdateSettings updateSettings, int attempt) {
    GitPushResult result = tryPushAndGetResult(pushInfo);
    handleResult(pushInfo, result, previousResult, updateSettings, attempt);
  }

  @NotNull
  private GitPushResult tryPushAndGetResult(@NotNull GitPushInfo pushInfo) {
    GitPushResult pushResult = new GitPushResult(myProject);
    
    GitCommitsByRepoAndBranch commits = pushInfo.getCommits();
    for (GitRepository repository : commits.getRepositories()) {
      if (commits.get(repository).getAllCommits().size() == 0) {  // don't push repositories where there is nothing to push. Note that when a branch is created, several recent commits are stored in the pushInfo.
        continue;
      }
      GitPushRepoResult repoResult = pushRepository(pushInfo, commits, repository);
      if (repoResult.getType() == GitPushRepoResult.Type.NOT_PUSHING) {
        continue;
      }
      pushResult.append(repository, repoResult);
      GitPushRepoResult.Type resultType = repoResult.getType();
      if (resultType == GitPushRepoResult.Type.CANCEL || resultType == GitPushRepoResult.Type.NOT_AUTHORIZED) { // don't proceed if user has cancelled or couldn't login
        break;
      }
    }
    GitRepositoryManager.getInstance(myProject).updateAllRepositories(GitRepository.TrackedTopic.BRANCHES); // new remote branch may be created
    return pushResult;
  }

  @NotNull
  private GitPushRepoResult pushRepository(@NotNull GitPushInfo pushInfo,
                                           @NotNull GitCommitsByRepoAndBranch commits,
                                           @NotNull GitRepository repository) {
    GitPushSpec pushSpec = pushInfo.getPushSpecs().get(repository);
    GitSimplePushResult simplePushResult = pushAndGetSimpleResult(repository, pushSpec, commits.get(repository));
    String output = simplePushResult.getOutput();
    switch (simplePushResult.getType()) {
      case SUCCESS:
        return successOrErrorRepoResult(commits, repository, output, true);
      case ERROR:
        return successOrErrorRepoResult(commits, repository, output, false);<commit_msg>IDEA-78740 Fix already disposed exception: save the GitRepositoryManager instance.<commit_after>/*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package git4idea.push;

import com.intellij.notification.NotificationType;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ui.UIUtil;
import git4idea.GitBranch;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.Notificator;
import git4idea.branch.GitBranchPair;
import git4idea.commands.Git;
import git4idea.commands.GitCommandResult;
import git4idea.config.GitConfigUtil;
import git4idea.config.GitVcsSettings;
import git4idea.config.UpdateMethod;
import git4idea.history.GitHistoryUtils;
import git4idea.history.browser.GitCommit;
import git4idea.jgit.GitHttpAdapter;
import git4idea.repo.GitBranchTrackInfo;
import git4idea.repo.GitRemote;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import git4idea.settings.GitPushSettings;
import git4idea.update.GitUpdateProcess;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Collects information to push and performs the push.
 *
 * @author <NAME>
 */
public final class GitPusher {

  /**
   * if diff-log is not available (new branch is created, for example), we show a few recent commits made on the branch
   */
  static final int RECENT_COMMITS_NUMBER = 5;
  
  static final GitBranch NO_TARGET_BRANCH = new GitBranch("", false, true);

  private static final Logger LOG = Logger.getInstance(GitPusher.class);
  private static final String INDICATOR_TEXT = "Pushing";
  private static final int MAX_PUSH_ATTEMPTS = 10;

  private final @NotNull Project myProject;
  private final @NotNull GitRepositoryManager myRepositoryManager;
  private final @NotNull ProgressIndicator myProgressIndicator;
  private final @NotNull Collection<GitRepository> myRepositories;
  private final @NotNull GitVcsSettings mySettings;
  private final @NotNull GitPushSettings myPushSettings;
  private final @NotNull Git myGit;

  public static void showPushDialogAndPerformPush(@NotNull final Project project) {
    final GitPushDialog dialog = new GitPushDialog(project);
    dialog.show();
    if (dialog.isOK()) {
      Task.Backgroundable task = new Task.Backgroundable(project, INDICATOR_TEXT, false) {
        @Override
        public void run(@NotNull ProgressIndicator indicator) {
          new GitPusher(project, indicator).push(dialog.getPushInfo());
        }
      };
      GitVcs.runInBackground(task);
    }
  }

  // holds settings chosen in GitRejectedPushUpdate dialog to reuse if the next push is rejected again.
  static class UpdateSettings {
    private final boolean myUpdateAllRoots;
    private final UpdateMethod myUpdateMethod;

    private UpdateSettings(boolean updateAllRoots, UpdateMethod updateMethod) {
      myUpdateAllRoots = updateAllRoots;
      myUpdateMethod = updateMethod;
    }

    public boolean shouldUpdateAllRoots() {
      return myUpdateAllRoots;
    }

    public UpdateMethod getUpdateMethod() {
      return myUpdateMethod;
    }

    public boolean shouldUpdate() {
      return getUpdateMethod() != null;
    }

    @Override
    public String toString() {
      return String.format("UpdateSettings{myUpdateAllRoots=%s, myUpdateMethod=%s}", myUpdateAllRoots, myUpdateMethod);
    }
  }

  public GitPusher(@NotNull Project project, @NotNull ProgressIndicator indicator) {
    myProject = project;
    myProgressIndicator = indicator;
    myRepositoryManager = GitRepositoryManager.getInstance(myProject);
    myRepositories = myRepositoryManager.getRepositories();
    mySettings = GitVcsSettings.getInstance(myProject);
    myPushSettings = GitPushSettings.getInstance(myProject);
    myGit = ServiceManager.getService(Git.class);
  }

  /**
   * @param pushSpecs which branches in which repositories should be pushed.
   *                               The most common situation is all repositories in the project with a single currently active branch for
   *                               each of them.
   * @throws VcsException if couldn't query 'git log' about commits to be pushed.
   * @return
   */
  @NotNull
  GitCommitsByRepoAndBranch collectCommitsToPush(@NotNull Map<GitRepository, GitPushSpec> pushSpecs) throws VcsException {
    Map<GitRepository, List<GitBranchPair>> reposAndBranchesToPush = prepareRepositoriesAndBranchesToPush(pushSpecs);
    
    Map<GitRepository, GitCommitsByBranch> commitsByRepoAndBranch = new HashMap<GitRepository, GitCommitsByBranch>();
    for (GitRepository repository : myRepositories) {
      List<GitBranchPair> branchPairs = reposAndBranchesToPush.get(repository);
      if (branchPairs == null) {
        continue;
      }
      GitCommitsByBranch commitsByBranch = collectsCommitsToPush(repository, branchPairs);
      commitsByRepoAndBranch.put(repository, commitsByBranch);
    }
    return new GitCommitsByRepoAndBranch(commitsByRepoAndBranch);
  }

  @NotNull
  private Map<GitRepository, List<GitBranchPair>> prepareRepositoriesAndBranchesToPush(@NotNull Map<GitRepository, GitPushSpec> pushSpecs) throws VcsException {
    Map<GitRepository, List<GitBranchPair>> res = new HashMap<GitRepository, List<GitBranchPair>>();
    for (GitRepository repository : myRepositories) {
      GitPushSpec pushSpec = pushSpecs.get(repository);
      if (pushSpec == null) {
        continue;
      }
      res.put(repository, Collections.singletonList(new GitBranchPair(pushSpec.getSource(), pushSpec.getDest())));
    }
    return res;
  }

  @NotNull
  private GitCommitsByBranch collectsCommitsToPush(@NotNull GitRepository repository, @NotNull List<GitBranchPair> sourcesDestinations)
    throws VcsException {
    Map<GitBranch, GitPushBranchInfo> commitsByBranch = new HashMap<GitBranch, GitPushBranchInfo>();

    for (GitBranchPair sourceDest : sourcesDestinations) {
      GitBranch source = sourceDest.getBranch();
      GitBranch dest = sourceDest.getDest();
      assert dest != null : "Destination branch can't be null here for branch " + source;

      List<GitCommit> commits;
      GitPushBranchInfo.Type type;
      if (dest == NO_TARGET_BRANCH) {
        commits = collectRecentCommitsOnBranch(repository, source);
        type = GitPushBranchInfo.Type.NO_TRACKED_OR_TARGET;
      }
      else if (GitUtil.repoContainsRemoteBranch(repository, dest)) {
        commits = collectCommitsToPush(repository, source.getName(), dest.getName());
        type = GitPushBranchInfo.Type.STANDARD;
      } 
      else {
        commits = collectRecentCommitsOnBranch(repository, source);
        type = GitPushBranchInfo.Type.NEW_BRANCH;
      }
      commitsByBranch.put(source, new GitPushBranchInfo(source, dest, commits, type));
    }

    return new GitCommitsByBranch(commitsByBranch);
  }

  private List<GitCommit> collectRecentCommitsOnBranch(GitRepository repository, GitBranch source) throws VcsException {
    return GitHistoryUtils.history(myProject, repository.getRoot(), "--max-count=" + RECENT_COMMITS_NUMBER, source.getName());
  }

  @NotNull
  private List<GitCommit> collectCommitsToPush(@NotNull GitRepository repository, @NotNull String source, @NotNull String destination)
    throws VcsException {
    return GitHistoryUtils.history(myProject, repository.getRoot(), destination + ".." + source);
  }

  /**
   * Makes push, shows the result in a notification. If push for current branch is rejected, shows a dialog proposing to update.
   */
  public void push(@NotNull GitPushInfo pushInfo) {
    push(pushInfo, null, null, 0);
  }

  /**
   * Makes push, shows the result in a notification. If push for current branch is rejected, shows a dialog proposing to update.
   * If {@code previousResult} and {@code updateSettings} are set, it means that this push is not the first, but is after a successful update.
   * In that case, if push is rejected again, the dialog is not shown, and update is performed automatically with the previously chosen
   * option.
   * Also, at the end results are merged and are shown in a single notification.
   */
  private void push(@NotNull GitPushInfo pushInfo, @Nullable GitPushResult previousResult, @Nullable UpdateSettings updateSettings, int attempt) {
    GitPushResult result = tryPushAndGetResult(pushInfo);
    handleResult(pushInfo, result, previousResult, updateSettings, attempt);
  }

  @NotNull
  private GitPushResult tryPushAndGetResult(@NotNull GitPushInfo pushInfo) {
    GitPushResult pushResult = new GitPushResult(myProject);
    
    GitCommitsByRepoAndBranch commits = pushInfo.getCommits();
    for (GitRepository repository : commits.getRepositories()) {
      if (commits.get(repository).getAllCommits().size() == 0) {  // don't push repositories where there is nothing to push. Note that when a branch is created, several recent commits are stored in the pushInfo.
        continue;
      }
      GitPushRepoResult repoResult = pushRepository(pushInfo, commits, repository);
      if (repoResult.getType() == GitPushRepoResult.Type.NOT_PUSHING) {
        continue;
      }
      pushResult.append(repository, repoResult);
      GitPushRepoResult.Type resultType = repoResult.getType();
      if (resultType == GitPushRepoResult.Type.CANCEL || resultType == GitPushRepoResult.Type.NOT_AUTHORIZED) { // don't proceed if user has cancelled or couldn't login
        break;
      }
    }
    myRepositoryManager.updateAllRepositories(GitRepository.TrackedTopic.BRANCHES); // new remote branch may be created
    return pushResult;
  }

  @NotNull
  private GitPushRepoResult pushRepository(@NotNull GitPushInfo pushInfo,
                                           @NotNull GitCommitsByRepoAndBranch commits,
                                           @NotNull GitRepository repository) {
    GitPushSpec pushSpec = pushInfo.getPushSpecs().get(repository);
    GitSimplePushResult simplePushResult = pushAndGetSimpleResult(repository, pushSpec, commits.get(repository));
    String output = simplePushResult.getOutput();
    switch (simplePushResult.getType()) {
      case SUCCESS:
        return successOrErrorRepoResult(commits, repository, output, true);
      case ERROR:
        return successOrErrorRepoResult(commits, repository, output, false);
{'fraction_non_alphanumeric': 0.08160150814668522, 'fraction_numerical': 0.0035459401229857713, 'mean_word_length': 4.1502542764678685, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4733459', 'n_tokens_mistral': 6205, 'n_tokens_neox': 6099, 'n_words': 1680}