public void launchFetchRemote(@NotNull final ForkInfo fork) {
    if (fork.getRemoteName() == null) return;

    if (fork.getFetchTask() != null) return;
    synchronized (fork.LOCK) {
      if (fork.getFetchTask() != null) return;

      final MasterFutureTask<Void> task =
          new MasterFutureTask<Void>(
              new Callable<Void>() {
                @Override
                public Void call() throws Exception {
                  doFetchRemote(fork);
                  return null;
                }
              });
      fork.setFetchTask(task);

      ApplicationManager.getApplication()
          .executeOnPooledThread(
              new Runnable() {
                @Override
                public void run() {
                  task.run();
                }
              });
    }
  }
  public boolean checkAction(@Nullable final BranchInfo branch) {
    if (branch == null) {
      GithubNotifications.showWarningDialog(
          myProject, CANNOT_CREATE_PULL_REQUEST, "Target branch is not selected");
      return false;
    }

    DiffInfo info;
    try {
      info =
          GithubUtil.computeValueInModal(
              myProject,
              "Collecting diff data...",
              new ThrowableConvertor<ProgressIndicator, DiffInfo, IOException>() {
                @Override
                public DiffInfo convert(ProgressIndicator indicator) throws IOException {
                  return GithubUtil.runInterruptable(
                      indicator,
                      new ThrowableComputable<DiffInfo, IOException>() {
                        @Override
                        public DiffInfo compute() throws IOException {
                          return getDiffInfo(branch);
                        }
                      });
                }
              });
    } catch (IOException e) {
      GithubNotifications.showError(myProject, "Can't collect diff data", e);
      return true;
    }
    if (info == null) {
      return true;
    }

    ForkInfo fork = branch.getForkInfo();

    String localBranchName = "'" + myCurrentBranch + "'";
    String targetBranchName = "'" + fork.getRemoteName() + "/" + branch.getRemoteName() + "'";
    if (info.getInfo().getBranchToHeadCommits(myGitRepository).isEmpty()) {
      return GithubNotifications.showYesNoDialog(
          myProject,
          "Do you want to proceed anyway?",
          "Empty pull request: the branch "
              + localBranchName
              + " is fully merged to the branch "
              + targetBranchName);
    }
    if (!info.getInfo().getHeadToBranchCommits(myGitRepository).isEmpty()) {
      return GithubNotifications.showYesNoDialog(
          myProject,
          "Do you want to proceed anyway?",
          "The branch "
              + targetBranchName
              + " is not fully merged to the branch "
              + localBranchName);
    }

    return true;
  }
  private void doConfigureRemote(@NotNull ForkInfo fork) {
    if (fork.getRemoteName() != null) return;

    GithubFullPath path = fork.getPath();
    String url = GithubUrlUtil.getCloneUrl(path);

    if (GithubUtil.addGithubRemote(myProject, myGitRepository, path.getUser(), url)) {
      fork.setRemoteName(path.getUser());
    }
  }
  private boolean doFetchRemote(@NotNull ForkInfo fork) {
    if (fork.getRemoteName() == null) return false;

    GitFetchResult result =
        new GitFetcher(myProject, new EmptyProgressIndicator(), false)
            .fetch(myGitRepository.getRoot(), fork.getRemoteName(), null);
    if (!result.isSuccess()) {
      GitFetcher.displayFetchResult(myProject, result, null, result.getErrors());
      return false;
    }
    return true;
  }
  @Nullable
  private ForkInfo findRepositoryByUser(
      @NotNull final ProgressIndicator indicator, @NotNull final String user) {
    for (ForkInfo fork : myForks) {
      if (StringUtil.equalsIgnoreCase(user, fork.getPath().getUser())) {
        return fork;
      }
    }

    try {
      GithubRepo repo =
          GithubUtil.runTask(
              myProject,
              myAuthHolder,
              indicator,
              new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() {
                @Nullable
                @Override
                public GithubRepo convert(@NotNull GithubConnection connection) throws IOException {
                  try {
                    GithubRepoDetailed target =
                        GithubApiUtil.getDetailedRepoInfo(
                            connection, user, mySource.getRepository());
                    if (target.getSource() != null
                        && StringUtil.equals(
                            target.getSource().getUserName(), mySource.getUser())) {
                      return target;
                    }
                  } catch (IOException ignore) {
                    // such repo may not exist
                  }

                  return GithubApiUtil.findForkByUser(
                      connection, mySource.getUser(), mySource.getRepository(), user);
                }
              });

      if (repo == null) return null;
      return doAddFork(repo, indicator);
    } catch (IOException e) {
      GithubNotifications.showError(myProject, "Can't find repository", e);
      return null;
    }
  }
  @Nullable
  private ForkInfo doAddFork(
      @NotNull GithubFullPath path,
      @Nullable String remoteName,
      @NotNull ProgressIndicator indicator) {
    for (ForkInfo fork : myForks) {
      if (fork.getPath().equals(path)) {
        if (fork.getRemoteName() == null && remoteName != null) {
          fork.setRemoteName(remoteName);
        }
        return fork;
      }
    }

    try {
      List<String> branches = loadBranches(path, indicator);
      String defaultBranch = doLoadDefaultBranch(path, indicator);

      ForkInfo fork = new ForkInfo(path, branches, defaultBranch);
      myForks.add(fork);
      if (remoteName != null) {
        fork.setRemoteName(remoteName);
      }
      return fork;
    } catch (IOException e) {
      GithubNotifications.showWarning(
          myProject, "Can't load branches for " + path.getFullName(), e);
      return null;
    }
  }