src/main/java/com/urswolfer/intellij/plugin/gerrit/git/GerritGitUtil.java
public class GerritGitUtil {
    @Inject
    private Logger log;
    @Inject
    private Git git;
    @Inject
    private GitPlatformFacade platformFacade;
    @Inject
    private Application application;
    @Inject
    private VirtualFileManager virtualFileManager;
    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private NotificationService notificationService;

    public Iterable<GitRepository> getRepositories(Project project) {
        GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project);
        return repositoryManager.getRepositories();
    }

    public Optional<GitRepository> getRepositoryForGerritProject(Project project, String gerritProjectName) {
        final Iterable<GitRepository> repositoriesFromRoots = getRepositories(project);
        for (GitRepository repository : repositoriesFromRoots) {
            for (GitRemote remote : repository.getRemotes()) {
                if (remote.getName().equals(gerritProjectName)) {
                    return Optional.of(repository);
                }
                for (String remoteUrl : remote.getUrls()) {
                    remoteUrl = UrlUtils.stripGitExtension(remoteUrl);
                    if (remoteUrl != null && remoteUrl.endsWith(gerritProjectName)) {
                        return Optional.of(repository);
                    }
                }
            }
        }
        return Optional.absent();
    }

    public Optional<GitRemote> getRemoteForChange(Project project, GitRepository gitRepository, FetchInfo fetchInfo) {
        String url = fetchInfo.url;
        for (GitRemote remote : gitRepository.getRemotes()) {
            List<String> repositoryUrls = new ArrayList<String>();
            repositoryUrls.addAll(remote.getUrls());
            repositoryUrls.addAll(remote.getPushUrls());
            for (String repositoryUrl : repositoryUrls) {
                if (UrlUtils.urlHasSameHost(repositoryUrl, url)
                    || UrlUtils.urlHasSameHost(repositoryUrl, gerritSettings.getCloneBaseUrlOrHost())) {
                    return Optional.of(remote);
                }
            }
        }
        NotificationBuilder notification = new NotificationBuilder(project, "Error",
            String.format("Could not fetch commit because no remote url matches Gerrit host.<br/>" +
                "Git repository: '%s'.", gitRepository.getPresentableUrl()));
        notificationService.notifyError(notification);
        return Optional.absent();
    }

    public void fetchChange(final Project project,
                            final GitRepository gitRepository,
                            final FetchInfo fetchInfo,
                            final String commitHash,
                            @Nullable final Callable<Void> fetchCallback) {
        GitVcs.runInBackground(new Task.Backgroundable(project, "Fetching...", false) {
            @Override
            public void run(@NotNull ProgressIndicator indicator) {
                GitRemote remote;
                String fetch;
                String url;
                boolean commitIsFetched = checkIfCommitIsFetched(gitRepository, commitHash);
                if (commitIsFetched) {
                    // 'git fetch' works with a local path instead of a remote -> this way FETCH_HEAD is set
                    remote = new GitRemote(gitRepository.getRoot().getPath(),
                        Collections.<String>emptyList(), Collections.<String>emptySet(), Collections.<String>emptyList(), Collections.<String>emptyList());
                    fetch = commitHash;
                    url = "";
                } else {
                    remote = getRemoteForChange(project, gitRepository, fetchInfo).orNull();
                    if (remote == null) {
                        return;
                    }
                    fetch = fetchInfo.ref;
                    url = remote.getFirstUrl();
                }
                GitFetchResult result = fetchNatively(gitRepository.getGitDir(), remote, url, fetch, project, indicator);
                if (!result.isSuccess()) {
                    GitFetcher.displayFetchResult(project, result, null, result.getErrors());
                }

                try {
                    if (fetchCallback != null) {
                        fetchCallback.call();
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                 }
            }
        });
    }

    public void cherryPickChange(final Project project, final ChangeInfo changeInfo, final String revisionId) {
        FileDocumentManager.getInstance().saveAllDocuments();
        platformFacade.getChangeListManager(project).blockModalNotifications();

        new Task.Backgroundable(project, "Cherry-picking...", false) {
            public void run(@NotNull ProgressIndicator indicator) {
                try {
                    Optional<GitRepository> gitRepositoryOptional = getRepositoryForGerritProject(project, changeInfo.project);
                    if (!gitRepositoryOptional.isPresent()) {
                        NotificationBuilder notification = new NotificationBuilder(project, "Error",
                            String.format("No repository found for Gerrit project: '%s'.", changeInfo.project));
                        notificationService.notifyError(notification);
                        return;
                    }
                    GitRepository gitRepository = gitRepositoryOptional.get();

                    final VirtualFile virtualFile = gitRepository.getGitDir();

                    final String notLoaded = "Not loaded";
                    VcsUser notLoadedUser = new VcsUserImpl(notLoaded, notLoaded);
                    VcsShortCommitDetails gitCommit = new VcsShortCommitDetailsImpl(
                        HashImpl.build(revisionId), Collections.<Hash>emptyList(), 0, virtualFile, notLoaded, notLoadedUser, notLoadedUser, 0);

                    cherryPick(gitRepository, gitCommit, git, platformFacade, project);
                } finally {
                    application.invokeLater(new Runnable() {
                        public void run() {
                            virtualFileManager.syncRefresh();
                            platformFacade.getChangeListManager(project).unblockModalNotifications();
                        }
                    });
                }
            }
        }.queue();
    }

    /**
     * A lot of this code is based on: git4idea.cherrypick.GitCherryPicker#cherryPick() (which is private)
     */
    private boolean cherryPick(@NotNull GitRepository repository, @NotNull VcsShortCommitDetails commit,
                               @NotNull Git git, @NotNull GitPlatformFacade platformFacade, @NotNull Project project) {
        GitSimpleEventDetector conflictDetector = new GitSimpleEventDetector(CHERRY_PICK_CONFLICT);
        GitSimpleEventDetector localChangesOverwrittenDetector = new GitSimpleEventDetector(LOCAL_CHANGES_OVERWRITTEN_BY_CHERRY_PICK);
        GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector =
                new GitUntrackedFilesOverwrittenByOperationDetector(repository.getRoot());
        GitCommandResult result = git.cherryPick(repository, commit.getId().asString(), false,
                conflictDetector, localChangesOverwrittenDetector, untrackedFilesDetector);
        if (result.success()) {
            return true;
        } else if (conflictDetector.hasHappened()) {
            return new CherryPickConflictResolver(project, git, platformFacade, repository.getRoot(),
                    commit.getId().toShortString(), commit.getAuthor().getName(),
                    commit.getSubject()).merge();
        } else if (untrackedFilesDetector.wasMessageDetected()) {
            String description = "Some untracked working tree files would be overwritten by cherry-pick.<br/>" +
                    "Please move, remove or add them before you can cherry-pick. <a href='view'>View them</a>";

            UntrackedFilesNotifier.notifyUntrackedFilesOverwrittenBy(project, repository.getRoot(),
                    untrackedFilesDetector.getRelativeFilePaths(),
                    "cherry-pick", description);
            return false;
        } else if (localChangesOverwrittenDetector.hasHappened()) {
            notificationService.notifyError(new NotificationBuilder(project, "Cherry-Pick Error",
                    "Your local changes would be overwritten by cherry-pick.<br/>Commit your changes or stash them to proceed."));
            return false;
        } else {
            notificationService.notifyError(new NotificationBuilder(project, "Cherry-Pick Error",
                    result.getErrorOutputAsHtmlString()));
            return false;
        }
    }


    /**
     * Copy of: git4idea.cherrypick.GitCherryPicker.CherryPickConflictResolver (which is private)
     */
    private static class CherryPickConflictResolver extends GitConflictResolver {

        public CherryPickConflictResolver(@NotNull Project project, @NotNull Git git, @NotNull GitPlatformFacade facade, @NotNull VirtualFile root,
                                          @NotNull String commitHash, @NotNull String commitAuthor, @NotNull String commitMessage) {
            super(project, git, facade, Collections.singleton(root), makeParams(commitHash, commitAuthor, commitMessage));
        }

        private static Params makeParams(String commitHash, String commitAuthor, String commitMessage) {
            Params params = new Params();
            params.setErrorNotificationTitle("Cherry-picked with conflicts");
            params.setMergeDialogCustomizer(new CherryPickMergeDialogCustomizer(commitHash, commitAuthor, commitMessage));
            return params;
        }

        @Override
        protected void notifyUnresolvedRemain() {
            // we show a [possibly] compound notification after cherry-picking all commits.
        }
    }


    /**
     * Copy of: git4idea.cherrypick.GitCherryPicker.CherryPickMergeDialogCustomizer (which is private)
     */
    private static class CherryPickMergeDialogCustomizer extends MergeDialogCustomizer {

        private String myCommitHash;
        private String myCommitAuthor;
        private String myCommitMessage;

        public CherryPickMergeDialogCustomizer(String commitHash, String commitAuthor, String commitMessage) {
            myCommitHash = commitHash;
            myCommitAuthor = commitAuthor;
            myCommitMessage = commitMessage;
        }

        @Override
        public String getMultipleFileMergeDescription(Collection<VirtualFile> files) {
            return "<html>Conflicts during cherry-picking commit <code>" + myCommitHash + "</code> made by " + myCommitAuthor + "<br/>" +
                    "<code>\"" + myCommitMessage + "\"</code></html>";
        }

        @Override
        public String getLeftPanelTitle(VirtualFile file) {
            return "Local changes";
        }

        @Override
        public String getRightPanelTitle(VirtualFile file, VcsRevisionNumber lastRevisionNumber) {
            return "<html>Changes from cherry-pick <code>" + myCommitHash + "</code>";
        }
    }

    @NotNull
    public GitFetchResult fetchNatively(@NotNull VirtualFile root,
                                        @NotNull GitRemote remote,
                                        @NotNull String url,
                                        @Nullable String branch,
                                        Project project,
                                        ProgressIndicator progressIndicator) {
        final GitLineHandler h = new GitLineHandler(project, root, GitCommand.FETCH);
        h.setUrl(url);
        h.addProgressParameter();

        String remoteName = remote.getName();
        h.addParameters(remoteName);
        if (branch != null) {
            h.addParameters(branch);
        }

        final GitTask fetchTask = new GitTask(project, h, "Fetching " + remote.getFirstUrl());
        fetchTask.setProgressIndicator(progressIndicator);
        fetchTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());

        final AtomicReference<GitFetchResult> result = new AtomicReference<GitFetchResult>();
        fetchTask.execute(true, false, new GitTaskResultHandlerAdapter() {
            @Override
            protected void onSuccess() {
                result.set(GitFetchResult.success());
            }

            @Override
            protected void onCancel() {
                log.info("Cancelled fetch.");
                result.set(GitFetchResult.cancel());
            }

            @Override
            protected void onFailure() {
                log.warn("Error fetching: " + h.errors());
                Collection<Exception> errors = Lists.newArrayList();
                errors.addAll(h.errors());
                result.set(GitFetchResult.error(errors));
            }
        });

        return result.get();
    }

    public boolean checkIfCommitIsFetched(GitRepository repository, String commitHash) {
        FormattedGitLineHandlerListener listener = new FormattedGitLineHandlerListener();
        final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.SHOW);
        h.setSilent(false);
        h.setStdoutSuppressed(false);
        h.addParameters(commitHash);
        h.addParameters("--format=short");
        h.endOptions();
        h.addLineListener(listener);
        GitCommandResult gitCommandResult = git.runCommand(new Computable<GitLineHandler>() {
            @Override
            public GitLineHandler compute() {
                return h;
            }
        });
        boolean success = gitCommandResult.success();
        List<String> output = gitCommandResult.getOutput();
        boolean isCommit = !output.isEmpty() && output.get(0).startsWith("commit");
        return success && isCommit;
    }

    @NotNull
    public Pair<List<GitCommit>, List<GitCommit>> loadCommitsToCompare(@NotNull GitRepository repository, @NotNull final String branchName, @NotNull final Project project) {
        final List<GitCommit> headToBranch;
        final List<GitCommit> branchToHead;
        try {
            headToBranch = GitHistoryUtils.history(project, repository.getRoot(), ".." + branchName);
            branchToHead = GitHistoryUtils.history(project, repository.getRoot(), branchName + "..");
        } catch (VcsException e) {
            // we treat it as critical and report an error
            throw new RuntimeException("Couldn't get [git log .." + branchName + "] on repository [" + repository.getRoot() + "]", e);
        }
        return Pair.create(headToBranch, branchToHead);
    }

    @NotNull
    public GitCommitCompareInfo loadCommitsToCompare(Collection<GitRepository> repositories, String branchName, @NotNull final Project project) {
        GitCommitCompareInfo compareInfo = new GitCommitCompareInfo();
        for (GitRepository repository : repositories) {
            compareInfo.put(repository, loadCommitsToCompare(repository, branchName, project));
//            compareInfo.put(repository, loadTotalDiff(repository, branchName));
        }
        return compareInfo;
    }

    public void setUpstreamBranch(GitRepository repository, String remoteBranch) throws VcsException {
        FormattedGitLineHandlerListener listener = new FormattedGitLineHandlerListener();
        final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.BRANCH);
        h.setSilent(false);
        h.setStdoutSuppressed(false);
        h.addParameters("-u", "remotes/" + remoteBranch);
        h.endOptions();
        h.addLineListener(listener);
        GitCommandResult gitCommandResult = git.runCommand(new Computable<GitLineHandler>() {
            @Override
            public GitLineHandler compute() {
                return h;
            }
        });
        if (!gitCommandResult.success()) {
            throw new VcsException(listener.getHtmlMessage());
        }
    }

    private static class FormattedGitLineHandlerListener implements GitLineHandlerListener {

        private List<String> messages = new ArrayList<String>();

        @Override
        public void onLineAvailable(String s, Key key) {
            if ( s.startsWith("\t") ) {
                s = "<b>" + s.substring(1) + "</b>";
            }
            messages.add(s);
        }

        @Override
        public void processTerminated(int i) {

        }

        @Override
        public void startFailed(Throwable throwable) {

        }

        public String getHtmlMessage() {
            return StringUtil.join(messages, "<br/>");
        }
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/ui/GerritToolWindow.java
public class GerritToolWindow {
    @Inject
    private DiffManager diffManager;
    @Inject
    private GerritUtil gerritUtil;
    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private CommentsDiffTool commentsDiffTool;
    @Inject
    private GerritChangeListPanel changeListPanel;
    @Inject
    private Logger log;
    @Inject
    private GerritChangesFilters changesFilters;
    @Inject
    private RepositoryChangesBrowserProvider repositoryChangesBrowserProvider;

    private GerritChangeDetailsPanel detailsPanel;

    public SimpleToolWindowPanel createToolWindowContent(final Project project) {
        changeListPanel.setProject(project);
        diffManager.registerDiffTool(commentsDiffTool);

        SimpleToolWindowPanel panel = new SimpleToolWindowPanel(true, true);

        ActionToolbar toolbar = createToolbar(project);
        toolbar.setTargetComponent(changeListPanel);
        panel.setToolbar(toolbar.getComponent());

        RepositoryChangesBrowser repositoryChangesBrowser = repositoryChangesBrowserProvider.get(project, changeListPanel);

        JBSplitter detailsSplitter = new JBSplitter(true, 0.6f);
        detailsSplitter.setSplitterProportionKey("Gerrit.ListDetailSplitter.Proportion");
        detailsSplitter.setShowDividerControls(true);

        changeListPanel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.RIGHT | SideBorder.BOTTOM));
        detailsSplitter.setFirstComponent(changeListPanel);

        detailsPanel = new GerritChangeDetailsPanel(project);
        changeListPanel.addListSelectionListener(new Consumer<ChangeInfo>() {
            @Override
            public void consume(ChangeInfo changeInfo) {
                changeSelected(changeInfo, project);
            }
        });
        JPanel details = detailsPanel.getComponent();
        details.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.RIGHT));
        detailsSplitter.setSecondComponent(details);

        JBSplitter horizontalSplitter = new JBSplitter(false, 0.7f);
        horizontalSplitter.setSplitterProportionKey("Gerrit.DetailRepositoryChangeBrowser.Proportion");
        horizontalSplitter.setShowDividerControls(true);
        horizontalSplitter.setFirstComponent(detailsSplitter);
        horizontalSplitter.setSecondComponent(repositoryChangesBrowser);

        panel.setContent(horizontalSplitter);

        List<GitRepository> repositories = GitUtil.getRepositoryManager(project).getRepositories();
        if (!repositories.isEmpty()) {
            reloadChanges(project, false);
        }

        registerVcsChangeListener(project);

        changeListPanel.showSetupHintWhenRequired(project);

        return panel;
    }

    private void registerVcsChangeListener(final Project project) {
        VcsListener vcsListener = new VcsListener() {
            @Override
            public void directoryMappingChanged() {
                forceGitRepositoryMappingInit(project);
            }
        };
        project.getMessageBus().connect().subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, vcsListener);
    }

    private void forceGitRepositoryMappingInit(final Project project) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project);
                repositoryManager.getRepositoryForRoot(project.getBaseDir());
                reloadChanges(project, false);
            }
        });
    }

    private void changeSelected(ChangeInfo changeInfo, final Project project) {
        gerritUtil.getChangeDetails(changeInfo._number, project, new Consumer<ChangeInfo>() {
            @Override
            public void consume(ChangeInfo changeDetails) {
                detailsPanel.setData(changeDetails);
            }
        });
    }

    public void reloadChanges(final Project project, boolean requestSettingsIfNonExistent) {
        getChanges(project, requestSettingsIfNonExistent, changeListPanel);
    }

    private void getChanges(Project project, boolean requestSettingsIfNonExistent, Consumer<LoadChangesProxy> consumer) {
        String apiUrl = gerritSettings.getHost();
        if (Strings.isNullOrEmpty(apiUrl)) {
            if (requestSettingsIfNonExistent) {
                final LoginDialog dialog = new LoginDialog(project, gerritSettings, gerritUtil, log);
                dialog.show();
                if (!dialog.isOK()) {
                    return;
                }
            } else {
                return;
            }
        }
        gerritUtil.getChangesForProject(changesFilters.getQuery(), project, consumer);
    }

    private ActionToolbar createToolbar(final Project project) {
        DefaultActionGroup groupFromConfig = (DefaultActionGroup) ActionManager.getInstance().getAction("Gerrit.Toolbar");
        DefaultActionGroup group = new DefaultActionGroup(groupFromConfig); // copy required (otherwise config action group gets modified)

        DefaultActionGroup filterGroup = new DefaultActionGroup();
        Iterable<ChangesFilter> filters = changesFilters.getFilters();
        for (ChangesFilter filter : filters) {
            filterGroup.add(filter.getAction(project));
        }
        filterGroup.add(new Separator());
        group.add(filterGroup, Constraints.FIRST);

        changesFilters.addObserver(new Observer() {
            @Override
            public void update(Observable observable, Object o) {
                reloadChanges(project, true);
            }
        });

        return ActionManager.getInstance().createActionToolbar("Gerrit.Toolbar", group, true);
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/push/GerritPushExtension.java
@SuppressWarnings("ComponentNotRegistered") // proxy class below is registered
public class GerritPushExtension implements ApplicationComponent {

    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private Logger log;

    public void initComponent() {
        try {
            ClassPool classPool = ClassPool.getDefault();

            ClassLoader gitIdeaPluginClassLoader = GitPushOperation.class.getClassLoader(); // it must be a class which is not modified (loaded) by javassist later on
            ClassLoader gerritPluginClassLoader = GerritPushExtensionPanel.class.getClassLoader();
            classPool.appendClassPath(new LoaderClassPath(gitIdeaPluginClassLoader));
            classPool.appendClassPath(new LoaderClassPath(gerritPluginClassLoader));

            copyGerritPluginClassesToGitPlugin(classPool, gitIdeaPluginClassLoader);

            modifyGitBranchPanel(classPool, gitIdeaPluginClassLoader);
        } catch (Exception e) {
            log.error("Failed to inject Gerrit push UI.", e);
        } catch (Error e) {
            log.error("Failed to inject Gerrit push UI.", e);
        }
    }

    private void modifyGitBranchPanel(ClassPool classPool, ClassLoader classLoader) {
        try {
            boolean pushToGerrit = gerritSettings.getPushToGerrit();

            CtClass gitPushSupportClass = classPool.get("git4idea.push.GitPushSupport");
            CtClass gerritPushOptionsPanelClass = classPool.get("com.urswolfer.intellij.plugin.gerrit.push.GerritPushOptionsPanel");

            gitPushSupportClass.addField(new CtField(gerritPushOptionsPanelClass, "gerritPushOptionsPanel", gitPushSupportClass),
                    "new com.urswolfer.intellij.plugin.gerrit.push.GerritPushOptionsPanel(" + pushToGerrit + ");");

            CtMethod createOptionsPanelMethod = gitPushSupportClass.getDeclaredMethod("createOptionsPanel");
            createOptionsPanelMethod.setBody(
                "{" +
                    "gerritPushOptionsPanel.initPanel(mySettings.getPushTagMode(), git4idea.config.GitVersionSpecialty.SUPPORTS_FOLLOW_TAGS.existsIn(myVcs.getVersion()));" +
                    "return gerritPushOptionsPanel;" +
                "}"
            );

            CtMethod createTargetPanelMethod = gitPushSupportClass.getDeclaredMethod("createTargetPanel");
            createTargetPanelMethod.setBody(
                "{" +
                    "return new com.urswolfer.intellij.plugin.gerrit.push.GerritPushTargetPanel($1, $2, gerritPushOptionsPanel);" +
                "}"
            );

            gitPushSupportClass.toClass(classLoader, GitPushOperation.class.getProtectionDomain());
            gitPushSupportClass.detach();
        } catch (CannotCompileException e) {
            log.error("Failed to inject Gerrit push UI.", e);
        } catch (NotFoundException e) {
            log.error("Failed to inject Gerrit push UI.", e);
        }
    }

    private void copyGerritPluginClassesToGitPlugin(ClassPool classPool, ClassLoader targetClassLoader) {
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushOptionsPanel");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushTargetPanel");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushTargetPanel$1");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$1");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$ChangeActionListener");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$ChangeTextActionListener");
        loadClass(classPool, targetClassLoader, "com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtensionPanel$SettingsStateActionListener");
    }

    private void loadClass(ClassPool classPool, ClassLoader targetClassLoader, String className) {
        try {
            CtClass loadedClass = classPool.get(className);
            loadedClass.toClass(targetClassLoader, GitPushOperation.class.getProtectionDomain());
            loadedClass.detach();
        } catch (CannotCompileException e) {
            log.error("Failed to load class required for Gerrit push UI injections.", e);
        } catch (NotFoundException e) {
            log.error("Failed to load class required for Gerrit push UI injections.", e);
        }
    }

    public void disposeComponent() {}

    @NotNull
    public String getComponentName() {
        return "GerritPushExtension";
    }


    public static final class Proxy implements ApplicationComponent {
        private final GerritPushExtension delegate;

        public Proxy() {
            delegate = GerritModule.getInstance(GerritPushExtension.class);
        }

        @Override
        public void initComponent() {
            delegate.initComponent();
        }

        @Override
        public void disposeComponent() {
            delegate.disposeComponent();
        }

        @NotNull
        @Override
        public String getComponentName() {
            return delegate.getComponentName();
        }
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/rest/GerritUtil.java
public class GerritUtil {

    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private Logger log;
    @Inject
    private NotificationService notificationService;
    @Inject
    private GerritRestApi gerritClient;
    @Inject
    private GerritRestApiFactory gerritRestApiFactory;
    @Inject
    private CertificateManagerClientBuilderExtension certificateManagerClientBuilderExtension;
    @Inject
    private LoggerHttpClientBuilderExtension loggerHttpClientBuilderExtension;
    @Inject
    private ProxyHttpClientBuilderExtension proxyHttpClientBuilderExtension;
    @Inject
    private UserAgentClientBuilderExtension userAgentClientBuilderExtension;
    @Inject
    private SelectedRevisions selectedRevisions;

    public <T> T accessToGerritWithModalProgress(Project project,
                                                 final ThrowableComputable<T, Exception> computable) {
        final AtomicReference<T> result = new AtomicReference<T>();
        final AtomicReference<Exception> exception = new AtomicReference<Exception>();
        ProgressManager.getInstance().run(new Task.Modal(project, "Access to Gerrit", true) {
            public void run(@NotNull ProgressIndicator indicator) {
                try {
                    result.set(computable.compute());
                } catch (Exception e) {
                    exception.set(e);
                }
            }
        });
        //noinspection ThrowableResultOfMethodCallIgnored
        if (exception.get() == null) {
            return result.get();
        }
        throw new RuntimeException(exception.get());
    }

    public void postReview(final String changeId,
                           final String revision,
                           final ReviewInput reviewInput,
                           final Project project,
                           final Consumer<Void> consumer) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).revision(revision).review(reviewInput);
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to post Gerrit review");
    }

    public void postSubmit(final String changeId,
                           final SubmitInput submitInput,
                           final Project project,
                           final Consumer<Void> consumer) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).current().submit(submitInput);
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to submit Gerrit change");
    }

    @SuppressWarnings("unchecked")
    public void postPublish(final String changeId,
                            final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).publish();
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to publish Gerrit change");
    }

    @SuppressWarnings("unchecked")
    public void delete(final String changeId,
                       final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).delete();
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to delete Gerrit change");
    }

    @SuppressWarnings("unchecked")
    public void postAbandon(final String changeId,
                            final AbandonInput abandonInput,
                            final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).abandon(abandonInput);
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to abandon Gerrit change");
    }

    @SuppressWarnings("unchecked")
    public void addReviewer(final String changeId,
                            final String reviewerName,
                            final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).addReviewer(reviewerName);
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to add reviewer");
    }

    /**
     * Star-endpoint added in Gerrit 2.8.
     */
    @SuppressWarnings("unchecked")
    public void changeStarredStatus(final String id,
                                    final boolean starred,
                                    final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    if (starred) {
                        gerritClient.accounts().self().starChange(id);
                    } else {
                        gerritClient.accounts().self().unstarChange(id);
                    }
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to star Gerrit change " +
                "(not supported for Gerrit versions older than 2.8)");
    }

    @SuppressWarnings("unchecked")
    public void setReviewed(final int changeNr,
                            final String revision,
                            final String filePath,
                            final Project project) {
        if (!gerritSettings.isLoginAndPasswordAvailable()) {
            return;
        }
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeNr).revision(revision).setReviewed(filePath, true);
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed set file review status for Gerrit change");
    }

    public void getChangesToReview(Project project, Consumer<List<ChangeInfo>> consumer) {
        Changes.QueryRequest queryRequest = gerritClient.changes().query("is:open+reviewer:self")
            .withOption(ListChangesOption.DETAILED_ACCOUNTS);
        getChanges(queryRequest, project, consumer);
    }

    public void getChangesForProject(String query, final Project project, final Consumer<LoadChangesProxy> consumer) {
        if (!gerritSettings.getListAllChanges()) {
            query = appendQueryStringForProject(project, query);
        }
        getChanges(query, project, consumer);
    }

    public void getChanges(final String query, final Project project, final Consumer<LoadChangesProxy> consumer) {
        Supplier<LoadChangesProxy> supplier = new Supplier<LoadChangesProxy>() {
            @Override
            public LoadChangesProxy get() {
                    Changes.QueryRequest queryRequest = gerritClient.changes().query(query)
                            .withOptions(EnumSet.of(
                                ListChangesOption.ALL_REVISIONS,
                                ListChangesOption.DETAILED_ACCOUNTS,
                                ListChangesOption.CHANGE_ACTIONS,
                                ListChangesOption.CURRENT_ACTIONS,
                                ListChangesOption.DETAILED_LABELS,
                                ListChangesOption.LABELS
                            ));
                    return new LoadChangesProxy(queryRequest, GerritUtil.this, project);
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    public void getChanges(final Changes.QueryRequest queryRequest, final Project project, Consumer<List<ChangeInfo>> consumer) {
        Supplier<List<ChangeInfo>> supplier = new Supplier<List<ChangeInfo>>() {
            @Override
            public List<ChangeInfo> get() {
                try {
                    return queryRequest.get();
                } catch (RestApiException e) {
                    // remove special handling (-> just notify error) once we drop Gerrit < 2.9 support
                    if (e instanceof HttpStatusException) {
                        HttpStatusException httpStatusException = (HttpStatusException) e;
                        if (httpStatusException.getStatusCode() == 400) {
                            boolean tryFallback = false;
                            String message = httpStatusException.getMessage();
                            if (message.matches(".*Content:.*\"-S\".*")) {
                                tryFallback = true;
                                queryRequest.withStart(0); // remove start, trust that sortkey is set
                            }
                            if (message.matches(".*Content:.*\"(CHANGE_ACTIONS|CURRENT_ACTIONS)\".*\"-o\".*")) {
                                tryFallback = true;
                                Set<ListChangesOption> options = queryRequest.getOptions();
                                options.remove(ListChangesOption.CHANGE_ACTIONS);
                                options.remove(ListChangesOption.CURRENT_ACTIONS);
                                queryRequest.withOptions(options);
                            }
                            if (tryFallback) {
                                try {
                                    return queryRequest.get();
                                } catch (RestApiException ex) {
                                    notifyError(ex, "Failed to get Gerrit changes.", project);
                                    return Collections.emptyList();
                                }
                            }
                        }
                    }
                    notifyError(e, "Failed to get Gerrit changes.", project);
                    return Collections.emptyList();
                }
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    private String appendQueryStringForProject(Project project, String query) {
        String projectQueryPart = getProjectQueryPart(project);
        query = Joiner.on('+').skipNulls().join(Strings.emptyToNull(query), Strings.emptyToNull(projectQueryPart));
        return query;
    }

    private String getProjectQueryPart(Project project) {
        List<GitRepository> repositories = GitUtil.getRepositoryManager(project).getRepositories();
        if (repositories.isEmpty()) {
            showAddGitRepositoryNotification(project);
            return "";
        }

        List<GitRemote> remotes = Lists.newArrayList();
        for (GitRepository repository : repositories) {
            remotes.addAll(repository.getRemotes());
        }
        List<String> projectNames = getProjectNames(remotes);
        Iterable<String> projectNamesWithQueryPrefix = Iterables.transform(projectNames, new Function<String, String>() {
            @Override
            public String apply(String input) {
                return "project:" + Url.encode(input);
            }
        });

        if (Iterables.isEmpty(projectNamesWithQueryPrefix)) {
            return "";
        }
        return String.format("(%s)", Joiner.on("+OR+").join(projectNamesWithQueryPrefix));
    }

    public List<String> getProjectNames(Collection<GitRemote> remotes) {
        List<String> projectNames = Lists.newArrayList();
        for (GitRemote remote : remotes) {
            for (String remoteUrl : remote.getUrls()) {
                remoteUrl = UrlUtils.stripGitExtension(remoteUrl);
                String projectName = getProjectName(gerritSettings.getHost(), gerritSettings.getCloneBaseUrl(),
                    remoteUrl);
                if (!Strings.isNullOrEmpty(projectName) && remoteUrl.endsWith(projectName)) {
                    projectNames.add(projectName);
                }
            }
        }
        return projectNames;
    }

    private String getProjectName(String gerritUrl, String gerritCloneBaseUrl,  String url) {
        String baseUrl = Strings.isNullOrEmpty(gerritCloneBaseUrl) ? gerritUrl : gerritCloneBaseUrl;
        if (!baseUrl.endsWith("/")) {
            baseUrl = baseUrl + "/";
        }

        String basePath = UrlUtils.createUriFromGitConfigString(baseUrl).getPath();
        String path = UrlUtils.createUriFromGitConfigString(url).getPath();

        if (path.length() >= basePath.length() && path.startsWith(basePath)) {
            path = path.substring(basePath.length());
        }

        path = UrlUtils.stripGitExtension(path);

        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }
        // gerrit project names usually don't start with a slash
        if (path.startsWith("/")) {
            path = path.substring(1);
        }

        return path;
    }

    public void showAddGitRepositoryNotification(final Project project) {
        NotificationBuilder notification = new NotificationBuilder(project, "Insufficient dependencies for Gerrit plugin",
                "Please configure a Git repository.<br/><a href='vcs'>Open Settings</a>")
                .listener(new NotificationListener() {
                    @Override
                    public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
                        if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED && event.getDescription().equals("vcs")) {
                            ShowSettingsUtil.getInstance().showSettingsDialog(project, ActionsBundle.message("group.VcsGroup.text"));
                        }
                    }
                });
        notificationService.notifyWarning(notification);
    }

    public void getChangeDetails(final int changeNr, final Project project, final Consumer<ChangeInfo> consumer) {
        Supplier<ChangeInfo> supplier = new Supplier<ChangeInfo>() {
            @Override
            public ChangeInfo get() {
                try {
                    EnumSet<ListChangesOption> options = EnumSet.of(
                            ListChangesOption.ALL_REVISIONS,
                            ListChangesOption.MESSAGES,
                            ListChangesOption.DETAILED_ACCOUNTS,
                            ListChangesOption.LABELS,
                            ListChangesOption.DETAILED_LABELS);
                    try {
                        return gerritClient.changes().id(changeNr).get(options);
                    } catch (HttpStatusException e) {
                        // remove special handling (-> just notify error) once we drop Gerrit < 2.7 support
                        if (e.getStatusCode() == 400) {
                            options.remove(ListChangesOption.MESSAGES);
                            return gerritClient.changes().id(changeNr).get(options);
                        } else {
                            throw e;
                        }
                    }
                } catch (RestApiException e) {
                    notifyError(e, "Failed to get Gerrit change.", project);
                    return new ChangeInfo();
                }
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    /**
     * Support starting from Gerrit 2.7.
     */
    public void getComments(final int changeNr,
                            final String revision,
                            final Project project,
                            final boolean includePublishedComments,
                            final boolean includeDraftComments,
                            final Consumer<Map<String, List<CommentInfo>>> consumer) {

        Supplier<Map<String, List<CommentInfo>>> supplier = new Supplier<Map<String, List<CommentInfo>>>() {
            @Override
            public Map<String, List<CommentInfo>> get() {
                try {
                    Map<String, List<CommentInfo>> comments;
                    if (includePublishedComments) {
                        comments = gerritClient.changes().id(changeNr).revision(revision).comments();
                    } else {
                        comments = Maps.newHashMap();
                    }

                    Map<String, List<CommentInfo>> drafts;
                    if (includeDraftComments && gerritSettings.isLoginAndPasswordAvailable()) {
                        drafts = gerritClient.changes().id(changeNr).revision(revision).drafts();
                    } else {
                        drafts = Maps.newHashMap();
                    }

                    HashMap<String, List<CommentInfo>> allComments = new HashMap<String, List<CommentInfo>>(drafts);
                    for (Map.Entry<String, List<CommentInfo>> entry : comments.entrySet()) {
                        List<CommentInfo> commentInfos = allComments.get(entry.getKey());
                        if (commentInfos != null) {
                            commentInfos.addAll(entry.getValue());
                        } else {
                            allComments.put(entry.getKey(), entry.getValue());
                        }
                    }
                    return allComments;
                } catch (RestApiException e) {
                    // remove check once we drop Gerrit < 2.7 support and fail in any case
                    if (!(e instanceof HttpStatusException) || ((HttpStatusException) e).getStatusCode() != 404) {
                        notifyError(e, "Failed to get Gerrit comments.", project);
                    }
                    return new TreeMap<String, List<CommentInfo>>();
                }
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    public void saveDraftComment(final int changeNr,
                                 final String revision,
                                 final DraftInput draftInput,
                                 final Project project,
                                 final Consumer<CommentInfo> consumer) {
        Supplier<CommentInfo> supplier = new Supplier<CommentInfo>() {
            @Override
            public CommentInfo get() {
                try {
                    CommentInfo commentInfo;
                    if (draftInput.id != null) {
                        commentInfo = gerritClient.changes().id(changeNr).revision(revision)
                                .draft(draftInput.id).update(draftInput);
                    } else {
                        DraftApi draftApi = gerritClient.changes().id(changeNr).revision(revision)
                                .createDraft(draftInput);
                        commentInfo = draftApi.get();
                    }
                    return commentInfo;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to save draft comment");
    }

    public void deleteDraftComment(final int changeNr,
                                   final String revision,
                                   final String draftCommentId,
                                   final Project project,
                                   final Consumer<Void> consumer) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeNr).revision(revision).draft(draftCommentId).delete();
                    return null;
                } catch (RestApiException e) {
                    throw new RuntimeException(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to delete draft comment");
    }

    private boolean testConnection(GerritAuthData gerritAuthData) throws RestApiException {
        // we need to test with a temporary client with probably new (unsaved) credentials
        GerritApi tempClient = createClientWithCustomAuthData(gerritAuthData);
        Changes.QueryRequest query = tempClient.changes().query();
        if (gerritAuthData.isLoginAndPasswordAvailable()) {
            query.withQuery("reviewer:self");
        }
        query.withLimit(1).get();
        return true;
    }

    /**
     * Checks if user has set up correct user credentials for access in the settings.
     *
     * @return true if we could successfully login with these credentials, false if authentication failed or in the case of some other error.
     */
    public boolean checkCredentials(final Project project) {
        try {
            return checkCredentials(project, gerritSettings);
        } catch (Exception e) {
            // this method is a quick-check if we've got valid user setup.
            // if an exception happens, we'll show the reason in the login dialog that will be shown right after checkCredentials failure.
            log.info(e);
            return false;
        }
    }

    public boolean checkCredentials(Project project, final GerritAuthData gerritAuthData) {
        if (Strings.isNullOrEmpty(gerritAuthData.getHost())) {
            return false;
        }
        Boolean result = accessToGerritWithModalProgress(project, new ThrowableComputable<Boolean, Exception>() {
            @Override
            public Boolean compute() throws Exception {
                ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to Gerrit");
                return testConnection(gerritAuthData);
            }
        });
        return result == null ? false : result;
    }

    /**
     * Shows Gerrit login settings if credentials are wrong or empty and return the list of all projects
     */
    public List<ProjectInfo> getAvailableProjects(final Project project) {
        while (!checkCredentials(project)) {
            final LoginDialog dialog = new LoginDialog(project, gerritSettings, this, log);
            dialog.show();
            if (!dialog.isOK()) {
                return null;
            }
        }
        // Otherwise our credentials are valid and they are successfully stored in settings
        return accessToGerritWithModalProgress(project, new ThrowableComputable<List<ProjectInfo>, Exception>() {
            @Override
            public List<ProjectInfo> compute() throws Exception {
                ProgressManager.getInstance().getProgressIndicator().setText("Extracting info about available repositories");
                return gerritClient.projects().list().get();
            }
        });
    }

    public FetchInfo getFirstFetchInfo(ChangeInfo changeDetails) {
        if (changeDetails.revisions == null) {
            return null;
        }
        RevisionInfo revisionInfo = changeDetails.revisions.get(selectedRevisions.get(changeDetails));
        return getFirstFetchInfo(revisionInfo);
    }

    public FetchInfo getFirstFetchInfo(RevisionInfo revisionInfo) {
        if (revisionInfo == null) {
            return null;
        }
        return Iterables.getFirst(revisionInfo.fetch.values(), null);
    }

    @SuppressWarnings("UnresolvedPropertyKey")
    public boolean testGitExecutable(final Project project) {
        final GitVcsApplicationSettings settings = GitVcsApplicationSettings.getInstance();
        final String executable = settings.getPathToGit();
        final GitVersion version;
        try {
            version = GitVersion.identifyVersion(executable);
        } catch (Exception e) {
            Messages.showErrorDialog(project, e.getMessage(), GitBundle.message("find.git.error.title"));
            return false;
        }

        if (!version.isSupported()) {
            Messages.showWarningDialog(project, GitBundle.message("find.git.unsupported.message", version.toString(), GitVersion.MIN),
                    GitBundle.message("find.git.success.title"));
            return false;
        }
        return true;
    }

    public String getErrorTextFromException(Throwable t) {
        String message = t.getMessage();
        if (message == null) {
            message = "(No exception message available)";
            log.error(message, t);
        }
        return message;
    }

    private <T> void accessGerrit(final Supplier<T> supplier, final Consumer<T> consumer, final Project project) {
        accessGerrit(supplier, consumer, project, null);
    }

    /**
     * @param errorMessage if the provided supplier throws an exception, this error message is displayed (if it is not null)
     *                     and the provided consumer will not be executed.
     */
    private <T> void accessGerrit(final Supplier<T> supplier,
                              final Consumer<T> consumer,
                              final Project project,
                              final String errorMessage) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                if (project.isDisposed()) {
                    return;
                }
                Task.Backgroundable backgroundTask = new Task.Backgroundable(project, "Accessing Gerrit", true) {
                    public void run(@NotNull ProgressIndicator indicator) {
                        if (project.isDisposed()) {
                            return;
                        }
                        try {
                            final T result = supplier.get();
                            ApplicationManager.getApplication().invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    if (project.isDisposed()) {
                                        return;
                                    }
                                    //noinspection unchecked
                                    consumer.consume(result);
                                }
                            });
                        } catch (RuntimeException e) {
                            if (errorMessage != null) {
                                notifyError(e, errorMessage, project);
                            } else {
                                throw e;
                            }
                        }
                    }
                };
                gerritSettings.preloadPassword();
                backgroundTask.queue();
            }
        });
    }

    private void notifyError(Throwable throwable, String errorMessage, Project project) {
        NotificationBuilder notification = new NotificationBuilder(project, errorMessage, getErrorTextFromException(throwable));
        notificationService.notifyError(notification);
    }

    private GerritApi createClientWithCustomAuthData(GerritAuthData gerritAuthData) {
        return gerritRestApiFactory.create(
            gerritAuthData,
            certificateManagerClientBuilderExtension,
            loggerHttpClientBuilderExtension,
            proxyHttpClientBuilderExtension,
            userAgentClientBuilderExtension);
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/extension/GerritCheckoutProvider.java
public class GerritCheckoutProvider implements CheckoutProvider {

    private static final Function<ProjectInfo, String> GET_ID_FUNCTION = new Function<ProjectInfo, String>() {
        public String apply(ProjectInfo from) {
            return from.id;
        }
    };
    private static final Ordering<ProjectInfo> ID_REVERSE_ORDERING = Ordering.natural().onResultOf(GET_ID_FUNCTION).reverse();

    @Inject
    private LocalFileSystem localFileSystem;
    @Inject
    private GerritUtil gerritUtil;
    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private Logger log;
    @Inject
    private NotificationService notificationService;
    @Inject
    private GerritRestApi gerritApi;

    @Override
    public void doCheckout(@NotNull final Project project, @Nullable final Listener listener) {
        if (!gerritUtil.testGitExecutable(project)) {
            return;
        }
        FileDocumentManager.getInstance().saveAllDocuments();
        List<ProjectInfo> availableProjects = null;
        try {
            availableProjects = gerritUtil.getAvailableProjects(project);
        } catch (Exception e) {
            log.info(e);
            NotificationBuilder notification = new NotificationBuilder(
                    project,
                    "Couldn't get the list of Gerrit repositories",
                    gerritUtil.getErrorTextFromException(e));
            notificationService.notifyError(notification);
        }
        if (availableProjects == null) {
            return;
        }
        ImmutableSortedSet<ProjectInfo> orderedProjects =
            ImmutableSortedSet.orderedBy(ID_REVERSE_ORDERING).addAll(availableProjects).build();

        String url = getCloneBaseUrl();

        final GitCloneDialog dialog = new GitCloneDialog(project);
        for (ProjectInfo projectInfo : orderedProjects) {
            dialog.prependToHistory(url + '/' + Url.decode(projectInfo.id));
        }
        dialog.show();
        if (!dialog.isOK()) {
            return;
        }
        dialog.rememberSettings();
        final VirtualFile destinationParent = localFileSystem.findFileByIoFile(new File(dialog.getParentDirectory()));
        if (destinationParent == null) {
            return;
        }
        final String sourceRepositoryURL = dialog.getSourceRepositoryURL();
        final String directoryName = dialog.getDirectoryName();
        final String parentDirectory = dialog.getParentDirectory();

        Git git = ServiceManager.getService(Git.class);

        Listener listenerWrapper = addCommitMsgHookListener(listener, directoryName, parentDirectory, project);

        GitCheckoutProvider.clone(project, git, listenerWrapper, destinationParent, sourceRepositoryURL, directoryName, parentDirectory);
    }

    @Override
    public String getVcsName() {
        return "Gerrit";
    }

    /**
     * If set, return the clone base url from the preferences. Otherwise, try to determine the Git clone url by
     * fetching a random change and processing its fetch url. If it fails, fall back to Gerrit host url config.
     *
     * This can be cleaned up once https://code.google.com/p/gerrit/issues/detail?id=2208 is implemented.
     */
    private String getCloneBaseUrl() {
        if (!Strings.isNullOrEmpty(gerritSettings.getCloneBaseUrl())) {
            return gerritSettings.getCloneBaseUrl();
        }
        String url = gerritSettings.getHost();
        try {
            List<ChangeInfo> changeInfos = gerritApi.changes().query()
                .withLimit(1)
                .withOption(ListChangesOption.CURRENT_REVISION)
                .get();
            if (changeInfos.isEmpty()) {
                log.info("ChangeInfo list is empty.");
                return url;
            }
            ChangeInfo changeInfo = Iterables.getOnlyElement(changeInfos);
            FetchInfo fetchInfo = gerritUtil.getFirstFetchInfo(changeInfo);
            if (fetchInfo != null) {
                String projectName = changeInfo.project;
                url = fetchInfo.url.replaceAll("/" + projectName + "$", "");
            }
        } catch (RestApiException e) {
            log.info(e);
        }
        return url;
    }

    /*
     * Since this is a listener which needs to be executed in any case, it cannot be a normal checkout-listener.
     * Checkout-listeners only get executed when "previous" listener got not executed (returns false).
     * Example: If user decides to setup a new project from newly created checkout, our listener does not get executed.
     */
    private Listener addCommitMsgHookListener(final Listener listener, final String directoryName, final String parentDirectory, final Project project) {
        return new Listener() {
            @Override
            public void directoryCheckedOut(File directory, VcsKey vcs) {
                setupCommitMsgHook(parentDirectory, directoryName, project);

                if (listener != null) listener.directoryCheckedOut(directory, vcs);
            }

            @Override
            public void checkoutCompleted() {
                if (listener != null) listener.checkoutCompleted();
            }
        };
    }

    private void setupCommitMsgHook(String parentDirectory, String directoryName, Project project) {
        try {
            InputStream commitMessageHook = gerritApi.tools().getCommitMessageHook();
            File targetFile = new File(parentDirectory + '/' + directoryName + "/.git/hooks/commit-msg");
            ByteStreams.copy(commitMessageHook, new FileOutputStream(targetFile));
            //noinspection ResultOfMethodCallIgnored
            targetFile.setExecutable(true);

            NotificationBuilder notification = new NotificationBuilder(
                project,
                "Gerrit Checkout done",
                "Commit-Message Hook has been set up.");
            notificationService.notify(notification);
        } catch (Exception e) {
            log.info(e);
            NotificationBuilder notification = new NotificationBuilder(
                    project,
                    "Couldn't set up Gerrit Commit-Message Hook. Please do it manually.",
                    gerritUtil.getErrorTextFromException(e));
            notificationService.notifyError(notification);
        }
    }

    public static final class Proxy implements CheckoutProvider {
        private final CheckoutProvider delegate;

        public Proxy() {
            delegate = GerritModule.getInstance(GerritCheckoutProvider.class);
        }

        @Override
        public void doCheckout(@NotNull Project project, @Nullable Listener listener) {
            delegate.doCheckout(project, listener);
        }

        @Override
        @NonNls
        public String getVcsName() {
            return delegate.getVcsName();
        }
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/util/UtilsModule.java
public class UtilsModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(PathUtils.class);
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/ui/diff/GerritDiffModule.java
public class GerritDiffModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AddCommentActionBuilder.class);
        bind(CommentsDiffTool.class);
        bind(CommentBalloonBuilder.class);
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/ui/GerritUiModule.java
public class GerritUiModule extends AbstractModule {
    @Override
    protected void configure() {
        install(new GerritFilterModule());
        bind(GerritSelectRevisionInfoColumn.class);
        Multibinder<GerritChangeNodeDecorator> decorators = Multibinder.newSetBinder(binder(), GerritChangeNodeDecorator.class);
        decorators.addBinding().to(GerritCommentCountChangeNodeDecorator.class);
        bind(RepositoryChangesBrowserProvider.class);
        bind(SettingsPanel.class);
        bind(GerritSettingsConfigurable.class);
        bind(GerritUpdatesNotificationComponent.class).asEagerSingleton();
        bind(GerritChangeListPanel.class);
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/ui/action/GerritActionsModule.java
public class GerritActionsModule extends AbstractModule {

    @Override
    protected void configure() {
        bind(ReviewActionFactory.class);

        bind(AddReviewersAction.class);
        bind(FetchAction.class);
        bind(CheckoutAction.class);
        bind(CherryPickAction.class);
        bind(CompareBranchAction.class);
        bind(OpenInBrowserAction.class);
        bind(SettingsAction.class);
        bind(SubmitAction.class);
        bind(AbandonAction.class);
        bind(RefreshAction.class);
        bind(StarAction.class);
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/extension/GerritHttpAuthDataProvider.java
public class GerritHttpAuthDataProvider implements GitHttpAuthDataProvider {

    @Inject
    private GerritSettings gerritSettings;

    @Nullable
    @Override
    public AuthData getAuthData(@NotNull String url) {
        if (!gerritSettings.getHost().equalsIgnoreCase(url)) {
            return null;
        }
        String password = gerritSettings.getPassword();
        if (StringUtil.isEmptyOrSpaces(gerritSettings.getLogin()) || StringUtil.isEmptyOrSpaces(password)) {
            return null;
        }
        return new AuthData(gerritSettings.getLogin(), password);
    }

    @Override
    public void forgetPassword(@NotNull String url) {
        if (gerritSettings.getHost().equalsIgnoreCase(url)) {
            gerritSettings.forgetPassword();
        }
    }

    public static final class Proxy implements GitHttpAuthDataProvider {
        private final GitHttpAuthDataProvider delegate;

        public Proxy() {
            delegate = GerritModule.getInstance(GerritHttpAuthDataProvider.class);
        }

        @Nullable
        @Override
        public AuthData getAuthData(@NotNull String url) {
            return delegate.getAuthData(url);
        }

        @Override
        public void forgetPassword(@NotNull String url) {
            delegate.forgetPassword(url);
        }
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/rest/GerritRestModule.java
public class GerritRestModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(CertificateManagerClientBuilderExtension.class);
        bind(ProxyHttpClientBuilderExtension.class);
        bind(UserAgentClientBuilderExtension.class);
        bind(GerritRestApiFactory.class);
        bind(GerritRestApi.class).toProvider(new GerritApiProvider());
    }
}
src/main/java/com/urswolfer/intellij/plugin/gerrit/util/NotificationService.java
public class NotificationService {
    public void notifyError(NotificationBuilder notificationBuilder) {
        notify(notificationBuilder.type(NotificationType.ERROR));
    }
    
    public void notifyWarning(NotificationBuilder notificationBuilder) {
        notify(notificationBuilder.type(NotificationType.WARNING));
    }

    public void notifyInformation(NotificationBuilder notificationBuilder) {
        notify(notificationBuilder.type(NotificationType.INFORMATION));
    }

    public void notify(NotificationBuilder notificationBuilder) {
        notificationBuilder.get().notify(notificationBuilder.getProject());
    }
}


Question: import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.intellij.openapi.components.ServiceManager;
import com.urswolfer.gerrit.client.rest.GerritAuthData;
import com.urswolfer.intellij.plugin.gerrit.extension.GerritCheckoutProvider;
import com.urswolfer.intellij.plugin.gerrit.extension.GerritHttpAuthDataProvider;
import com.urswolfer.intellij.plugin.gerrit.git.GerritGitUtil;
import com.urswolfer.intellij.plugin.gerrit.push.GerritPushExtension;
import com.urswolfer.intellij.plugin.gerrit.rest.GerritRestModule;
import com.urswolfer.intellij.plugin.gerrit.rest.GerritUtil;
import com.urswolfer.intellij.plugin.gerrit.ui.GerritToolWindow;
import com.urswolfer.intellij.plugin.gerrit.ui.GerritUiModule;
import com.urswolfer.intellij.plugin.gerrit.ui.action.GerritActionsModule;
import com.urswolfer.intellij.plugin.gerrit.ui.diff.GerritDiffModule;
import com.urswolfer.intellij.plugin.gerrit.util.NotificationService;
import com.urswolfer.intellij.plugin.gerrit.util.UtilsModule;
/*
 * Copyright 2013 Urs Wolfer
 *
 * 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 com.urswolfer.intellij.plugin.gerrit;


/**
 * @author Thomas Forrer
 */
public class GerritModule extends AbstractModule {

    protected static final Supplier<Injector> injector = Suppliers.memoize(new Supplier<Injector>() {
        @Override
        public Injector get() {
            return Guice.createInjector(new GerritModule());
        }
    });

    public static <T> T getInstance(Class<T> type) {
        return injector.get().getInstance(type);
    }

    protected GerritModule() {}

    @Override
    protected void configure() {
        installOpenIdeDependenciesModule();

        setupSettingsProvider();

        bind(NotificationService.class);
        bind(SelectedRevisions.class).toInstance(new SelectedRevisions());

        bind(GerritGitUtil.class);
        bind(GerritUtil.class);

Answer:         bind(GerritToolWindow.class);