@Nullable
 private List<GithubFullPath> getAvailableForks(@NotNull ProgressIndicator indicator) {
   try {
     List<GithubFullPath> forks =
         ContainerUtil.map(
             GithubUtil.runTask(
                 myProject,
                 myAuthHolder,
                 indicator,
                 new ThrowableConvertor<GithubConnection, List<GithubRepo>, IOException>() {
                   @NotNull
                   @Override
                   public List<GithubRepo> convert(@NotNull GithubConnection connection)
                       throws IOException {
                     return GithubApiUtil.getForks(
                         connection, mySource.getUser(), mySource.getRepository());
                   }
                 }),
             new Function<GithubRepo, GithubFullPath>() {
               @Override
               public GithubFullPath fun(GithubRepo repo) {
                 return repo.getFullPath();
               }
             });
     if (!forks.contains(mySource)) return ContainerUtil.append(forks, mySource);
     return forks;
   } catch (IOException e) {
     GithubNotifications.showWarning(myProject, "Can't load available forks", e);
     return null;
   }
 }
  private void doLoadForksFromGithub(@NotNull ProgressIndicator indicator) throws IOException {
    GithubRepoDetailed repo =
        GithubUtil.runTask(
            myProject,
            myAuthHolder,
            indicator,
            new ThrowableConvertor<GithubConnection, GithubRepoDetailed, IOException>() {
              @NotNull
              @Override
              public GithubRepoDetailed convert(@NotNull GithubConnection connection)
                  throws IOException {
                return GithubApiUtil.getDetailedRepoInfo(
                    connection, myPath.getUser(), myPath.getRepository());
              }
            });

    doAddFork(repo, indicator);
    if (repo.getParent() != null) {
      doAddFork(repo.getParent(), indicator);
    }
    if (repo.getSource() != null) {
      doAddFork(repo.getSource(), indicator);
    }

    mySource = repo.getSource() == null ? repo.getFullPath() : repo.getSource().getFullPath();
  }
  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());
    }
  }
 public void configureRemote(@NotNull final ForkInfo fork) {
   GithubUtil.computeValueInModal(
       myProject,
       "Creating remote..",
       false,
       new Consumer<ProgressIndicator>() {
         @Override
         public void consume(ProgressIndicator indicator) {
           doConfigureRemote(fork);
         }
       });
 }
 @Nullable
 private String doLoadDefaultBranch(
     @NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException {
   GithubRepo repo =
       GithubUtil.runTask(
           myProject,
           myAuthHolder,
           indicator,
           new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() {
             @Override
             public GithubRepo convert(@NotNull GithubConnection connection) throws IOException {
               return GithubApiUtil.getDetailedRepoInfo(
                   connection, fork.getUser(), fork.getRepository());
             }
           });
   return repo.getDefaultBranch();
 }
  @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
  public ForkInfo showTargetDialog() {
    if (myAvailableForks == null) {
      myAvailableForks =
          GithubUtil.computeValueInModal(
              myProject,
              myCurrentBranch,
              new Convertor<ProgressIndicator, List<GithubFullPath>>() {
                @Override
                public List<GithubFullPath> convert(ProgressIndicator indicator) {
                  return getAvailableForks(indicator);
                }
              });
    }

    Convertor<String, ForkInfo> getForkPath =
        new Convertor<String, ForkInfo>() {
          @Nullable
          @Override
          public ForkInfo convert(@NotNull final String user) {
            return GithubUtil.computeValueInModal(
                myProject,
                "Access to GitHub",
                new Convertor<ProgressIndicator, ForkInfo>() {
                  @Nullable
                  @Override
                  public ForkInfo convert(ProgressIndicator indicator) {
                    return findRepositoryByUser(indicator, user);
                  }
                });
          }
        };
    GithubSelectForkDialog dialog =
        new GithubSelectForkDialog(myProject, myAvailableForks, getForkPath);
    DialogManager.show(dialog);
    if (!dialog.isOK()) {
      return null;
    }
    return dialog.getPath();
  }
  public void showDiffDialog(@Nullable final BranchInfo branch) {
    if (branch == null) {
      GithubNotifications.showWarningDialog(
          myProject, "Can't Show Diff", "Target branch is not selected");
      return;
    }

    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;
    }
    if (info == null) {
      GithubNotifications.showErrorDialog(myProject, "Can't Show Diff", "Can't collect diff data");
      return;
    }

    GitCompareBranchesDialog dialog =
        new GitCompareBranchesDialog(
            myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository, true);
    dialog.show();
  }
  @NotNull
  public Couple<String> getDefaultDescriptionMessage(@NotNull final BranchInfo branch) {
    Couple<String> message = branch.getDefaultMessage();
    if (message != null) return message;

    if (branch.getForkInfo().getRemoteName() == null) {
      return getSimpleDefaultDescriptionMessage(branch);
    }

    return GithubUtil.computeValueInModal(
        myProject,
        "Collecting additional data...",
        false,
        new Convertor<ProgressIndicator, Couple<String>>() {
          @Override
          public Couple<String> convert(ProgressIndicator o) {
            String localBranch = myCurrentBranch;
            String targetBranch =
                branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName();
            try {
              List<VcsCommitMetadata> commits =
                  GitHistoryUtils.readLastCommits(
                      myProject, myGitRepository.getRoot(), localBranch, targetBranch);
              if (commits == null) return getSimpleDefaultDescriptionMessage(branch);

              VcsCommitMetadata localCommit = commits.get(0);
              VcsCommitMetadata targetCommit = commits.get(1);

              if (localCommit.getParents().contains(targetCommit.getId())) {
                return Couple.of(localCommit.getSubject(), localCommit.getFullMessage());
              }
              return getSimpleDefaultDescriptionMessage(branch);
            } catch (VcsException e) {
              GithubNotifications.showWarning(myProject, "Can't collect additional data", e);
              return getSimpleDefaultDescriptionMessage(branch);
            }
          }
        });
  }
 @NotNull
 private List<String> loadBranches(
     @NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException {
   return ContainerUtil.map(
       GithubUtil.runTask(
           myProject,
           myAuthHolder,
           indicator,
           new ThrowableConvertor<GithubConnection, List<GithubBranch>, IOException>() {
             @Override
             public List<GithubBranch> convert(@NotNull GithubConnection connection)
                 throws IOException {
               return GithubApiUtil.getRepoBranches(
                   connection, fork.getUser(), fork.getRepository());
             }
           }),
       new Function<GithubBranch, String>() {
         @Override
         public String fun(@NotNull GithubBranch branch) {
           return branch.getName();
         }
       });
 }
  @Nullable
  private GithubPullRequest doCreatePullRequest(
      @NotNull ProgressIndicator indicator,
      @NotNull final BranchInfo branch,
      @NotNull final String title,
      @NotNull final String description) {
    final ForkInfo fork = branch.getForkInfo();

    final String head = myPath.getUser() + ":" + myCurrentBranch;
    final String base = branch.getRemoteName();

    try {
      return GithubUtil.runTask(
          myProject,
          myAuthHolder,
          indicator,
          new ThrowableConvertor<GithubConnection, GithubPullRequest, IOException>() {
            @NotNull
            @Override
            public GithubPullRequest convert(@NotNull GithubConnection connection)
                throws IOException {
              return GithubApiUtil.createPullRequest(
                  connection,
                  fork.getPath().getUser(),
                  fork.getPath().getRepository(),
                  title,
                  description,
                  head,
                  base);
            }
          });
    } catch (IOException e) {
      GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, e);
      return null;
    }
  }
  @Nullable
  public static GithubCreatePullRequestWorker create(
      @NotNull final Project project, @Nullable final VirtualFile file) {
    return GithubUtil.computeValueInModal(
        project,
        "Loading data...",
        new Convertor<ProgressIndicator, GithubCreatePullRequestWorker>() {
          @Override
          public GithubCreatePullRequestWorker convert(ProgressIndicator indicator) {
            Git git = ServiceManager.getService(Git.class);

            GitRepository gitRepository = GithubUtil.getGitRepository(project, file);
            if (gitRepository == null) {
              GithubNotifications.showError(
                  project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository");
              return null;
            }
            gitRepository.update();

            Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository);
            if (remote == null) {
              GithubNotifications.showError(
                  project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote");
              return null;
            }
            String remoteName = remote.getFirst().getName();
            String remoteUrl = remote.getSecond();

            GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl);
            if (path == null) {
              GithubNotifications.showError(
                  project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl);
              return null;
            }

            GitLocalBranch currentBranch = gitRepository.getCurrentBranch();
            if (currentBranch == null) {
              GithubNotifications.showError(
                  project, CANNOT_CREATE_PULL_REQUEST, "No current branch");
              return null;
            }

            GithubAuthDataHolder authHolder;
            try {
              authHolder = GithubUtil.getValidAuthDataHolderFromConfig(project, indicator);
            } catch (IOException e) {
              GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
              return null;
            }

            GithubCreatePullRequestWorker worker =
                new GithubCreatePullRequestWorker(
                    project,
                    git,
                    gitRepository,
                    authHolder,
                    path,
                    remoteName,
                    remoteUrl,
                    currentBranch.getName());

            try {
              worker.initForks(indicator);
            } catch (IOException e) {
              GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e);
              return null;
            }

            return worker;
          }
        });
  }