[github] IDEA-88522 Support show commit on github in Annotated view

Introduce the AnnotationGutterActionProvider extension point to let plugins add custom actions to the annotation popup.
Create GithubShowCommitInBrowserFromAnnotateAction, and also move common stuff from the similar action for the log to GithubShowCommitInBrowserAction.
This commit is contained in:
Kirill Likhodedov
2012-07-13 15:50:16 +04:00
parent 677303b39a
commit 2daa65e13a
11 changed files with 900 additions and 673 deletions
@@ -42,6 +42,7 @@
interface="com.intellij.openapi.vcs.actions.VcsQuickListContentProvider"/>
<extensionPoint name="patch.extension" interface="com.intellij.openapi.diff.impl.patch.PatchEP" area="IDEA_PROJECT"/>
<extensionPoint name="vcsChangesViewRefresher" interface="com.intellij.openapi.vcs.changes.ChangesViewRefresher"/>
<extensionPoint name="vcsAnnotationGutterActionProvider" interface="com.intellij.openapi.vcs.annotate.AnnotationGutterActionProvider"/>
<extensionPoint name="vcschangedetails" interface="com.intellij.openapi.vcs.changes.VcsChangeDetailsProvider" area="IDEA_PROJECT"/>
@@ -0,0 +1,39 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.vcs.annotate;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.extensions.ExtensionPointName;
import org.jetbrains.annotations.NotNull;
/**
* Implement this to add additional custom actions to the popup invoked by right-clicking on the annotation gutter.
*
* @author Kirill Likhodedov
*/
public interface AnnotationGutterActionProvider {
ExtensionPointName<AnnotationGutterActionProvider> EP_NAME = ExtensionPointName.create("com.intellij.vcsAnnotationGutterActionProvider");
/**
* Create an action that will be added to the annotation gutter popup.
* @param annotation annotation which is currently shown on the gutter.
* @return new action that can be invoked from the annotation gutter popup.
*/
@NotNull
AnAction createAction(FileAnnotation annotation);
}
@@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.actions;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
@@ -207,12 +208,6 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware, Ann
new AnnotationPresentation(highlighting, switcher, editorGutter, gutters,
additionalActions.toArray(new AnAction[additionalActions.size()]));
for (AnAction action : additionalActions) {
if (action instanceof LineNumberListener) {
presentation.addLineNumberListener((LineNumberListener)action);
}
}
final Map<String, Color> bgColorMap = Registry.is("vcs.show.colored.annotations") ? computeBgColors(fileAnnotation) : null;
final Map<String, Integer> historyIds = Registry.is("vcs.show.history.numbers") ? computeLineNumbers(fileAnnotation) : null;
@@ -248,9 +243,17 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware, Ann
gutters.add(new HighlightedAdditionalColumn(fileAnnotation, editor, null, presentation, highlighting, bgColorMap));
final AnnotateActionGroup actionGroup = new AnnotateActionGroup(gutters, editorGutter);
presentation.addAction(actionGroup, 1);
presentation.addAction(new ShowHideAdditionalInfoAction(gutters, editorGutter, actionGroup));
gutters.add(new ExtraFieldGutter(fileAnnotation, editor, presentation, bgColorMap, actionGroup));
presentation.addAction(new ShowHideAdditionalInfoAction(gutters, editorGutter, actionGroup));
addActionsFromExtensions(presentation, fileAnnotation);
for (AnAction action : presentation.getActions()) {
if (action instanceof LineNumberListener) {
presentation.addLineNumberListener((LineNumberListener)action);
}
}
for (AnnotationFieldGutter gutter : gutters) {
final AnnotationGutterLineConvertorProxy proxy = new AnnotationGutterLineConvertorProxy(getUpToDateLineNumber, gutter);
if (gutter.isGutterAction()) {
@@ -263,6 +266,16 @@ public class AnnotateToggleAction extends ToggleAction implements DumbAware, Ann
}
}
private static void addActionsFromExtensions(@NotNull AnnotationPresentation presentation, @NotNull FileAnnotation fileAnnotation) {
AnnotationGutterActionProvider[] extensions = AnnotationGutterActionProvider.EP_NAME.getExtensions();
if (extensions.length > 0) {
presentation.addAction(new Separator());
}
for (AnnotationGutterActionProvider provider : extensions) {
presentation.addAction(provider.createAction(fileAnnotation));
}
}
@Nullable
private static Map<String, Integer> computeLineNumbers(FileAnnotation fileAnnotation) {
final SortedList<VcsFileRevision> revisions = new SortedList<VcsFileRevision>(new Comparator<VcsFileRevision>() {
@@ -85,6 +85,11 @@ class AnnotationPresentation implements TextAnnotationPresentation {
return myActions;
}
@NotNull
public List<AnAction> getActions() {
return myActions;
}
public void addSourceSwitchListener(final Consumer<AnnotationSource> listener) {
mySwitchAction.addSourceSwitchListener(listener);
}
+2 -1
View File
@@ -14,6 +14,7 @@
<applicationService serviceInterface="org.jetbrains.plugins.github.GithubSettings"
serviceImplementation="org.jetbrains.plugins.github.GithubSettings"/>
<applicationConfigurable instance="org.jetbrains.plugins.github.ui.GitHubSettingsConfigurable"/>
<vcsAnnotationGutterActionProvider implementation="org.jetbrains.plugins.github.ui.GithubAnnotationGutterActionProvider" />
</extensions>
<actions>
@@ -28,7 +29,7 @@
<add-to-group group-id="ProjectViewPopupMenu"/>
<add-to-group group-id="EditorTabPopupMenu"/>
</action>
<action id="Github.Open.Commit.In.Browser" class="org.jetbrains.plugins.github.GithubShowCommitInBrowserAction">
<action id="Github.Open.Commit.In.Browser" class="org.jetbrains.plugins.github.GithubShowCommitInBrowserFromLogAction">
<add-to-group group-id="Git.LogContextMenu" />
</action>
<action id="Github.Create.Gist" class="org.jetbrains.plugins.github.GithubCreateGistAction">
@@ -1,200 +1,168 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitBranch;
import git4idea.GitUtil;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GithubLoginDialog;
import static org.jetbrains.plugins.github.GithubUtil.*;
/**
* Created by IntelliJ IDEA.
*
* @author oleg
* @date 12/10/10
*/
public class GithubOpenInBrowserAction extends DumbAwareAction {
public static final String CANNOT_OPEN_IN_BROWSER = "Cannot open in browser";
private static final Logger LOG = Logger.getInstance(GithubOpenInBrowserAction.class.getName());
protected GithubOpenInBrowserAction() {
super("Open in browser", "Open corresponding GitHub link in browser", GITHUB_ICON);
}
@Override
public void update(final AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
if (project == null || project.isDefault() || virtualFile == null) {
setVisibleEnabled(e, false, false);
return;
}
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
final GitRepository gitRepository = manager.getRepositoryForFile(virtualFile);
if (gitRepository == null) {
setVisibleEnabled(e, false, false);
return;
}
// Check that given repository is properly configured git repository
if (!isRepositoryOnGitHub(gitRepository)) {
setVisibleEnabled(e, false, false);
return;
}
ChangeListManager changeListManager = ChangeListManager.getInstance(project);
if (changeListManager.isUnversioned(virtualFile)) {
setVisibleEnabled(e, true, false);
return;
}
Change change = changeListManager.getChange(virtualFile);
if (change != null && change.getType() == Change.Type.NEW) {
setVisibleEnabled(e, true, false);
return;
}
setVisibleEnabled(e, true, true);
}
@SuppressWarnings("ConstantConditions")
@Override
public void actionPerformed(final AnActionEvent e) {
final Project project = e.getData(PlatformDataKeys.PROJECT);
while (!checkCredentials(project)) {
final GithubLoginDialog dialog = new GithubLoginDialog(project);
dialog.show();
if (!dialog.isOK()) {
return;
}
}
final VirtualFile root = project.getBaseDir();
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
if (manager == null) {
return;
}
final GitRepository gitRepository = manager.getRepositoryForFile(root);
// Check that given repository is properly configured git repository
final String githubRemoteUrl = findGithubRemoteUrl(gitRepository);
final VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
final String rootPath = root.getPath();
final String path = virtualFile.getPath();
if (!path.startsWith(rootPath)) {
Messages.showErrorDialog(project, "File is not under project root", CANNOT_OPEN_IN_BROWSER);
return;
}
String branch = getBranchNameOnRemote(project, root);
if (branch == null) {
return;
}
String relativePath = path.substring(rootPath.length());
String urlToOpen = makeUrlToOpen(e, relativePath, branch, githubRemoteUrl);
BrowserUtil.launchBrowser(urlToOpen);
}
private static String makeUrlToOpen(@NotNull AnActionEvent e, @NotNull String relativePath, @NotNull String branch,
@NotNull String githubRemoteUrl) {
final StringBuilder builder = new StringBuilder();
builder.append(makeGithubRepoUrlFromRemoteUrl(githubRemoteUrl)).append("/blob/").append(branch).append(relativePath);
final Editor editor = e.getData(PlatformDataKeys.EDITOR);
if (editor != null) {
final int line = editor.getCaretModel().getLogicalPosition().line + 1; // lines are counted internally from 0, but from 1 on github
builder.append("#L").append(line);
}
return builder.toString();
}
@NotNull
private static String makeGithubRepoUrlFromRemoteUrl(@NotNull String remoteUrl) {
remoteUrl = removeEndingDotGit(remoteUrl);
if (remoteUrl.startsWith("http")) {
return remoteUrl;
}
if (remoteUrl.startsWith("git://")) {
return "https" + remoteUrl.substring(3);
}
return convertFromSshToHttp(remoteUrl);
}
@NotNull
private static String convertFromSshToHttp(@NotNull String remoteUrl) {
// Format: git@github.com:account/repository
int indexOfAt = remoteUrl.indexOf("@");
if (indexOfAt < 0) {
throw new IllegalStateException("Invalid remote Github SSH url: " + remoteUrl);
}
String withoutPrefix = remoteUrl.substring(indexOfAt + 1, remoteUrl.length());
return "https://" + withoutPrefix.replace(':', '/');
}
@NotNull
private static String removeEndingDotGit(@NotNull String url) {
final String DOT_GIT = ".git";
if (url.endsWith(DOT_GIT)) {
return url.substring(0, url.length() - DOT_GIT.length());
}
return url;
}
@Nullable
public static String getBranchNameOnRemote(@NotNull Project project, @NotNull VirtualFile root) {
final GitBranch tracked;
try {
final GitBranch current = GitBranch.current(project, root);
if (current == null) {
Messages.showErrorDialog(project, "Cannot find local branch", CANNOT_OPEN_IN_BROWSER);
return null;
}
tracked = current.tracked(project, root);
if (tracked == null || !tracked.isRemote()) {
Messages.showErrorDialog(project, "Cannot find tracked branch for branch: " + current.getFullName(), CANNOT_OPEN_IN_BROWSER);
return null;
}
}
catch (VcsException e1) {
Messages.showErrorDialog(project, "Error occurred while inspecting branches: " + e1, CANNOT_OPEN_IN_BROWSER);
return null;
}
String branch = tracked.getName();
if (branch.startsWith("origin/")) {
branch = branch.substring(7);
}
return branch;
}
}
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitBranch;
import git4idea.GitUtil;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GithubLoginDialog;
import static org.jetbrains.plugins.github.GithubUtil.*;
/**
* Created by IntelliJ IDEA.
*
* @author oleg
* @date 12/10/10
*/
public class GithubOpenInBrowserAction extends DumbAwareAction {
public static final String CANNOT_OPEN_IN_BROWSER = "Cannot open in browser";
private static final Logger LOG = Logger.getInstance(GithubOpenInBrowserAction.class.getName());
protected GithubOpenInBrowserAction() {
super("Open in browser", "Open corresponding GitHub link in browser", GITHUB_ICON);
}
@Override
public void update(final AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
if (project == null || project.isDefault() || virtualFile == null) {
setVisibleEnabled(e, false, false);
return;
}
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
final GitRepository gitRepository = manager.getRepositoryForFile(virtualFile);
if (gitRepository == null) {
setVisibleEnabled(e, false, false);
return;
}
// Check that given repository is properly configured git repository
if (!isRepositoryOnGitHub(gitRepository)) {
setVisibleEnabled(e, false, false);
return;
}
ChangeListManager changeListManager = ChangeListManager.getInstance(project);
if (changeListManager.isUnversioned(virtualFile)) {
setVisibleEnabled(e, true, false);
return;
}
Change change = changeListManager.getChange(virtualFile);
if (change != null && change.getType() == Change.Type.NEW) {
setVisibleEnabled(e, true, false);
return;
}
setVisibleEnabled(e, true, true);
}
@SuppressWarnings("ConstantConditions")
@Override
public void actionPerformed(final AnActionEvent e) {
final Project project = e.getData(PlatformDataKeys.PROJECT);
while (!checkCredentials(project)) {
final GithubLoginDialog dialog = new GithubLoginDialog(project);
dialog.show();
if (!dialog.isOK()) {
return;
}
}
final VirtualFile root = project.getBaseDir();
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
if (manager == null) {
return;
}
final GitRepository gitRepository = manager.getRepositoryForFile(root);
// Check that given repository is properly configured git repository
final String githubRemoteUrl = findGithubRemoteUrl(gitRepository);
final VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
final String rootPath = root.getPath();
final String path = virtualFile.getPath();
if (!path.startsWith(rootPath)) {
Messages.showErrorDialog(project, "File is not under project root", CANNOT_OPEN_IN_BROWSER);
return;
}
String branch = getBranchNameOnRemote(project, root);
if (branch == null) {
return;
}
String relativePath = path.substring(rootPath.length());
String urlToOpen = makeUrlToOpen(e, relativePath, branch, githubRemoteUrl);
BrowserUtil.launchBrowser(urlToOpen);
}
private static String makeUrlToOpen(@NotNull AnActionEvent e, @NotNull String relativePath, @NotNull String branch,
@NotNull String githubRemoteUrl) {
final StringBuilder builder = new StringBuilder();
builder.append(makeGithubRepoUrlFromRemoteUrl(githubRemoteUrl)).append("/blob/").append(branch).append(relativePath);
final Editor editor = e.getData(PlatformDataKeys.EDITOR);
if (editor != null) {
final int line = editor.getCaretModel().getLogicalPosition().line + 1; // lines are counted internally from 0, but from 1 on github
builder.append("#L").append(line);
}
return builder.toString();
}
@Nullable
public static String getBranchNameOnRemote(@NotNull Project project, @NotNull VirtualFile root) {
final GitBranch tracked;
try {
final GitBranch current = GitBranch.current(project, root);
if (current == null) {
Messages.showErrorDialog(project, "Cannot find local branch", CANNOT_OPEN_IN_BROWSER);
return null;
}
tracked = current.tracked(project, root);
if (tracked == null || !tracked.isRemote()) {
Messages.showErrorDialog(project, "Cannot find tracked branch for branch: " + current.getFullName(), CANNOT_OPEN_IN_BROWSER);
return null;
}
}
catch (VcsException e1) {
Messages.showErrorDialog(project, "Error occurred while inspecting branches: " + e1, CANNOT_OPEN_IN_BROWSER);
return null;
}
String branch = tracked.getName();
if (branch.startsWith("origin/")) {
branch = branch.substring(7);
}
return branch;
}
}
@@ -16,104 +16,35 @@
package org.jetbrains.plugins.github;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.history.browser.GitCommit;
import git4idea.repo.GitRepository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Kirill Likhodedov
*/
public class GithubShowCommitInBrowserAction extends DumbAwareAction {
abstract class GithubShowCommitInBrowserAction extends DumbAwareAction {
public GithubShowCommitInBrowserAction() {
super("Open in Browser", "Open the selected commit in browser", GithubUtil.GITHUB_ICON);
}
@Override
public void update(AnActionEvent e) {
EventData eventData = collectData(e);
e.getPresentation().setVisible(eventData != null);
e.getPresentation().setEnabled(eventData != null);
}
@Nullable
private static EventData collectData(AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
if (project == null || project.isDefault()) {
return null;
}
GitCommit commit = e.getData(GitVcs.GIT_COMMIT);
if (commit == null) {
return null;
}
VirtualFile root = commit.getRoot();
GitRepository repository = GitUtil.getRepositoryManager(project).getRepositoryForRoot(root);
if (repository == null || !GithubUtil.isRepositoryOnGitHub(repository)) {
return null;
}
return new EventData(project, repository, commit);
}
@Override
public void actionPerformed(AnActionEvent e) {
EventData eventData = collectData(e);
if (eventData == null) {
return;
}
GitRepository repository = eventData.getRepository();
protected static void openInBrowser(Project project, GitRepository repository, String revisionHash) {
String url = GithubUtil.findGithubRemoteUrl(repository);
if (url == null) {
GithubUtil.LOG.info(String.format("Repository is not under GitHub. Root: %s, Remotes: %s", repository.getRoot(),
GitUtil.getPrintableRemotes(repository.getRemotes())));
return;
}
String userAndRepository = GithubUtil.getUserAndRepositoryOrShowError(eventData.getProject(), url);
url = GithubUtil.makeGithubRepoUrlFromRemoteUrl(url);
String userAndRepository = GithubUtil.getUserAndRepositoryOrShowError(project, url);
if (userAndRepository == null) {
return;
}
String githubUrl = "https://github.com/" + userAndRepository + "/commit/" + eventData.getCommit();
String githubUrl = "https://github.com/" + userAndRepository + "/commit/" + revisionHash;
BrowserUtil.launchBrowser(githubUrl);
}
private static class EventData {
@NotNull private final Project myProject;
@NotNull private final GitRepository myRepository;
@NotNull private final GitCommit myCommit;
private EventData(@NotNull Project project, @NotNull GitRepository repository, @NotNull GitCommit commit) {
myProject = project;
myRepository = repository;
myCommit = commit;
}
@NotNull
public Project getProject() {
return myProject;
}
@NotNull
public GitRepository getRepository() {
return myRepository;
}
@NotNull
public GitCommit getCommit() {
return myCommit;
}
}
}
@@ -0,0 +1,105 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import com.intellij.openapi.vcs.annotate.LineNumberListener;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitUtil;
import git4idea.repo.GitRepository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Kirill Likhodedov
*/
public class GithubShowCommitInBrowserFromAnnotateAction extends GithubShowCommitInBrowserAction implements LineNumberListener {
private final FileAnnotation myAnnotation;
private int myLineNumber = -1;
public GithubShowCommitInBrowserFromAnnotateAction(FileAnnotation annotation) {
super();
myAnnotation = annotation;
}
@Override
public void update(AnActionEvent e) {
EventData eventData = calcData(e);
final boolean enabled = myLineNumber != -1 && myAnnotation.getLineRevisionNumber(myLineNumber) != null;
e.getPresentation().setEnabled(eventData != null && enabled);
e.getPresentation().setVisible(eventData != null && GithubUtil.isRepositoryOnGitHub(eventData.getRepository()));
}
@Override
public void actionPerformed(AnActionEvent e) {
EventData eventData = calcData(e);
if (eventData == null) {
return;
}
final VcsRevisionNumber revisionNumber = myAnnotation.getLineRevisionNumber(myLineNumber);
if (revisionNumber != null) {
openInBrowser(eventData.getProject(), eventData.getRepository(), revisionNumber.asString());
}
}
@Nullable
private static EventData calcData(AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
if (project == null || virtualFile == null) {
return null;
}
GitRepository repository = GitUtil.getRepositoryManager(project).getRepositoryForFile(virtualFile);
if (repository == null) {
return null;
}
return new EventData(project, repository);
}
@Override
public void consume(Integer integer) {
myLineNumber = integer;
}
private static class EventData {
@NotNull private final Project myProject;
@NotNull private final GitRepository myRepository;
private EventData(@NotNull Project project, @NotNull GitRepository repository) {
myProject = project;
myRepository = repository;
}
@NotNull
public Project getProject() {
return myProject;
}
@NotNull
public GitRepository getRepository() {
return myRepository;
}
}
}
@@ -0,0 +1,97 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.history.browser.GitCommit;
import git4idea.repo.GitRepository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Kirill Likhodedov
*/
public class GithubShowCommitInBrowserFromLogAction extends GithubShowCommitInBrowserAction {
@Override
public void update(AnActionEvent e) {
EventData eventData = collectData(e);
e.getPresentation().setVisible(eventData != null && GithubUtil.isRepositoryOnGitHub(eventData.getRepository()));
e.getPresentation().setEnabled(eventData != null);
}
@Nullable
private static EventData collectData(AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
if (project == null || project.isDefault()) {
return null;
}
GitCommit commit = e.getData(GitVcs.GIT_COMMIT);
if (commit == null) {
return null;
}
VirtualFile root = commit.getRoot();
GitRepository repository = GitUtil.getRepositoryManager(project).getRepositoryForRoot(root);
if (repository == null) {
return null;
}
return new EventData(project, repository, commit);
}
@Override
public void actionPerformed(AnActionEvent e) {
EventData eventData = collectData(e);
if (eventData != null) {
openInBrowser(eventData.getProject(), eventData.getRepository(), eventData.getCommit().getHash().getValue());
}
}
private static class EventData {
@NotNull private final Project myProject;
@NotNull private final GitRepository myRepository;
@NotNull private final GitCommit myCommit;
private EventData(@NotNull Project project, @NotNull GitRepository repository, @NotNull GitCommit commit) {
myProject = project;
myRepository = repository;
myCommit = commit;
}
@NotNull
public Project getProject() {
return myProject;
}
@NotNull
public GitRepository getRepository() {
return myRepository;
}
@NotNull
public GitCommit getCommit() {
return myCommit;
}
}
}
@@ -1,391 +1,423 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.tasks.github.GithubApiUtil;
import git4idea.config.GitVcsApplicationSettings;
import git4idea.config.GitVersion;
import git4idea.i18n.GitBundle;
import git4idea.repo.GitRemote;
import git4idea.repo.GitRepository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GithubLoginDialog;
import javax.swing.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Various utility methods for the GutHub plugin.
*
* @author oleg
* @author Kirill Likhodedov
*/
public class GithubUtil {
public static final Icon GITHUB_ICON = IconLoader.getIcon("/org/jetbrains/plugins/github/github_icon.png");
public static final Logger LOG = Logger.getInstance("github");
static final String GITHUB_NOTIFICATION_GROUP = "github";
/**
* @deprecated The host may be defined in different formats. Use {@link com.intellij.tasks.github.GithubApiUtil#getApiUrl(String)} instead.
*/
@Deprecated
public static String getHttpsUrl() {
return "https://" + GithubSettings.getInstance().getHost();
}
/**
* @deprecated TODO Use background progress
*/
@Deprecated
public static <T> T accessToGithubWithModalProgress(final Project project, final Computable<T> computable) {
final Ref<T> result = new Ref<T>();
ProgressManager.getInstance().run(new Task.Modal(project, "Access to GitHub", true) {
public void run(@NotNull ProgressIndicator indicator) {
result.set(computable.compute());
}
});
return result.get();
}
/**
* @deprecated TODO Use background progress
*/
@Deprecated
public static void accessToGithubWithModalProgress(final Project project, final Runnable runnable) {
ProgressManager.getInstance().run(new Task.Modal(project, "Access to GitHub", true) {
public void run(@NotNull ProgressIndicator indicator) {
runnable.run();
}
});
}
private static boolean testConnection(final String url, final String login, final String password) {
GithubUser user = retrieveCurrentUserInfo(url, login, password);
return user != null;
}
@Nullable
private static GithubUser retrieveCurrentUserInfo(@NotNull String url, @NotNull String login, @NotNull String password) {
try {
JsonElement result = GithubApiUtil.getRequest(url, login, password, "/user");
return parseUserInfo(result);
}
catch (IOException e) {
LOG.info(e);
return null;
}
}
@Nullable
private static GithubUser parseUserInfo(@Nullable JsonElement result) {
if (result == null) {
return null;
}
if (!result.isJsonObject()) {
LOG.error(String.format("Unexpected JSON result format: %s", result));
return null;
}
JsonObject obj = (JsonObject)result;
if (!obj.has("plan")) {
return null;
}
GithubUser.Plan plan = parsePlan(obj.get("plan"));
return new GithubUser(plan);
}
@NotNull
private static GithubUser.Plan parsePlan(JsonElement plan) {
if (!plan.isJsonObject()) {
return GithubUser.Plan.FREE;
}
return GithubUser.Plan.fromString(plan.getAsJsonObject().get("name").getAsString());
}
@NotNull
private static List<RepositoryInfo> getAvailableRepos(@NotNull String url, @NotNull String login, @NotNull String password,
boolean ownOnly) {
final String request = (ownOnly ? "/user/repos" : "/user/watched");
try {
JsonElement result = GithubApiUtil.getRequest(url, login, password, request);
if (result == null) {
return Collections.emptyList();
}
return parseRepositoryInfos(result);
}
catch (IOException e) {
LOG.error(e);
return Collections.emptyList();
}
}
@NotNull
private static List<RepositoryInfo> parseRepositoryInfos(@NotNull JsonElement result) {
if (!result.isJsonArray()) {
LOG.assertTrue(result.isJsonObject(), String.format("Unexpected JSON result format: %s", result));
return Collections.singletonList(parseSingleRepositoryInfo(result.getAsJsonObject()));
}
List<RepositoryInfo> repositories = new ArrayList<RepositoryInfo>();
for (JsonElement element : result.getAsJsonArray()) {
LOG.assertTrue(element.isJsonObject(),
String.format("This element should be a JsonObject: %s%nTotal JSON response: %n%s", element, result));
repositories.add(parseSingleRepositoryInfo(element.getAsJsonObject()));
}
return repositories;
}
@NotNull
private static RepositoryInfo parseSingleRepositoryInfo(@NotNull JsonObject result) {
String name = result.get("name").getAsString();
String cloneUrl = result.get("clone_url").getAsString();
String ownerName = result.get("owner").getAsJsonObject().get("login").getAsString();
String parentName = result.has("parent") ? result.get("parent").getAsJsonObject().get("full_name").getAsString(): null;
boolean fork = result.get("fork").getAsBoolean();
return new RepositoryInfo(name, cloneUrl, ownerName, parentName, fork);
}
@Nullable
private static RepositoryInfo getDetailedRepoInfo(@NotNull String url, @NotNull String login, @NotNull String password,
@NotNull String owner, @NotNull String name) {
try {
final String request = "/repos/" + owner + "/" + name;
JsonElement jsonObject = GithubApiUtil.getRequest(url, login, password, request);
if (jsonObject == null) {
LOG.info(String.format("Information about repository is unavailable. Owner: %s, Name: %s", owner, name));
return null;
}
return parseSingleRepositoryInfo(jsonObject.getAsJsonObject());
}
catch (IOException e) {
LOG.info(String.format("Exception was thrown when trying to retrieve information about repository. Owner: %s, Name: %s",
owner, name));
return null;
}
}
public static boolean isPrivateRepoAllowed(final String url, final String login, final String password) {
GithubUser user = retrieveCurrentUserInfo(url, login, password);
if (user == null) {
return false;
}
return user.getPlan().isPrivateRepoAllowed();
}
public static boolean checkCredentials(final Project project) {
final GithubSettings settings = GithubSettings.getInstance();
return checkCredentials(project, settings.getHost(), settings.getLogin(), settings.getPassword());
}
public static boolean checkCredentials(final Project project, final String url, final String login, final String password) {
if (StringUtil.isEmptyOrSpaces(url) || StringUtil.isEmptyOrSpaces(login) || StringUtil.isEmptyOrSpaces(password)){
return false;
}
return accessToGithubWithModalProgress(project, new Computable<Boolean>() {
@Override
public Boolean compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to GitHub");
return testConnection(url, login, password);
}
});
}
/**
* Shows GitHub login settings if credentials are wrong or empty and return the list of all the watched repos by user
* @param project
* @return
*/
@Nullable
public static List<RepositoryInfo> getAvailableRepos(final Project project, final boolean ownOnly) {
while (!checkCredentials(project)){
final GithubLoginDialog dialog = new GithubLoginDialog(project);
dialog.show();
if (!dialog.isOK()){
return null;
}
}
// Otherwise our credentials are valid and they are successfully stored in settings
final GithubSettings settings = GithubSettings.getInstance();
final String validPassword = settings.getPassword();
return accessToGithubWithModalProgress(project, new Computable<List<RepositoryInfo>>() {
@Override
public List<RepositoryInfo> compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Extracting info about available repositories");
return getAvailableRepos(settings.getHost(), settings.getLogin(), validPassword, ownOnly);
}
});
}
/**
* Shows GitHub login settings if credentials are wrong or empty and return the list of all the watched repos by user
* @param project
* @return
*/
@Nullable
public static RepositoryInfo getDetailedRepositoryInfo(final Project project, final String owner, final String name) {
final GithubSettings settings = GithubSettings.getInstance();
final String password = settings.getPassword();
final Boolean validCredentials = accessToGithubWithModalProgress(project, new Computable<Boolean>() {
@Override
public Boolean compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to GitHub");
return testConnection(settings.getHost(), settings.getLogin(), password);
}
});
if (validCredentials == null) {
return null;
}
if (!validCredentials){
final GithubLoginDialog dialog = new GithubLoginDialog(project);
dialog.show();
if (!dialog.isOK()) {
return null;
}
}
// Otherwise our credentials are valid and they are successfully stored in settings
final String validPassword = settings.getPassword();
return accessToGithubWithModalProgress(project, new Computable<RepositoryInfo>() {
@Nullable
@Override
public RepositoryInfo compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Extracting detailed info about repository ''" + name + "''");
return getDetailedRepoInfo(settings.getHost(), settings.getLogin(), validPassword, owner, name);
}
});
}
@Nullable
public static GitRemote findGitHubRemoteBranch(@NotNull GitRepository repository) {
// i.e. find origin which points on my github repo
// Check that given repository is properly configured git repository
for (GitRemote gitRemote : repository.getRemotes()) {
if (getGithubUrl(gitRemote) != null){
return gitRemote;
}
}
return null;
}
@Nullable
public static String getGithubUrl(final GitRemote gitRemote){
final GithubSettings githubSettings = GithubSettings.getInstance();
final String host = githubSettings.getHost();
final String username = githubSettings.getLogin();
// TODO this doesn't work with organizational accounts
final String userRepoMarkerSSHProtocol = host + ":" + username + "/";
final String userRepoMarkerOtherProtocols = host + "/" + username + "/";
for (String pushUrl : gitRemote.getUrls()) {
if (pushUrl.contains(userRepoMarkerSSHProtocol) || pushUrl.contains(userRepoMarkerOtherProtocols)) {
return pushUrl;
}
}
return null;
}
public static 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.getString("find.git.error.title"));
return false;
}
if (!version.isSupported()) {
Messages.showWarningDialog(project, GitBundle.message("find.git.unsupported.message", version.toString(), GitVersion.MIN),
GitBundle.getString("find.git.success.title"));
return false;
}
return true;
}
static boolean isRepositoryOnGitHub(@NotNull GitRepository repository) {
return findGithubRemoteUrl(repository) != null;
}
@Nullable
static String findGithubRemoteUrl(@NotNull GitRepository repository) {
for (GitRemote remote : repository.getRemotes()) {
for (String url : remote.getUrls()) {
if (isGithubUrl(url)) {
return url;
}
}
}
return null;
}
private static boolean isGithubUrl(@NotNull String url) {
return url.contains("github.com");
}
static void setVisibleEnabled(AnActionEvent e, boolean visible, boolean enabled) {
e.getPresentation().setVisible(visible);
e.getPresentation().setEnabled(enabled);
}
@Nullable
public static String getUserAndRepositoryOrShowError(@NotNull Project project, @NotNull String url) {
int index = -1;
if (url.startsWith(getHttpsUrl())) {
index = url.lastIndexOf('/');
if (index == -1) {
Messages.showErrorDialog(project, "Cannot extract info about repository name: " + url, GithubOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER);
return null;
}
index = url.substring(0, index).lastIndexOf('/');
if (index == -1) {
Messages.showErrorDialog(project, "Cannot extract info about repository owner: " + url, GithubOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER);
return null;
}
}
else {
index = url.lastIndexOf(':');
if (index == -1) {
Messages.showErrorDialog(project, "Cannot extract info about repository name and owner: " + url, GithubOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER);
return null;
}
}
String repoInfo = url.substring(index + 1);
if (repoInfo.endsWith(".git")) {
repoInfo = repoInfo.substring(0, repoInfo.length() - 4);
}
return repoInfo;
}
}
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.tasks.github.GithubApiUtil;
import git4idea.config.GitVcsApplicationSettings;
import git4idea.config.GitVersion;
import git4idea.i18n.GitBundle;
import git4idea.repo.GitRemote;
import git4idea.repo.GitRepository;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.github.ui.GithubLoginDialog;
import javax.swing.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Various utility methods for the GutHub plugin.
*
* @author oleg
* @author Kirill Likhodedov
*/
public class GithubUtil {
public static final Icon GITHUB_ICON = IconLoader.getIcon("/org/jetbrains/plugins/github/github_icon.png");
public static final Logger LOG = Logger.getInstance("github");
static final String GITHUB_NOTIFICATION_GROUP = "github";
/**
* @deprecated The host may be defined in different formats. Use {@link com.intellij.tasks.github.GithubApiUtil#getApiUrl(String)} instead.
*/
@Deprecated
public static String getHttpsUrl() {
return "https://" + GithubSettings.getInstance().getHost();
}
/**
* @deprecated TODO Use background progress
*/
@Deprecated
public static <T> T accessToGithubWithModalProgress(final Project project, final Computable<T> computable) {
final Ref<T> result = new Ref<T>();
ProgressManager.getInstance().run(new Task.Modal(project, "Access to GitHub", true) {
public void run(@NotNull ProgressIndicator indicator) {
result.set(computable.compute());
}
});
return result.get();
}
/**
* @deprecated TODO Use background progress
*/
@Deprecated
public static void accessToGithubWithModalProgress(final Project project, final Runnable runnable) {
ProgressManager.getInstance().run(new Task.Modal(project, "Access to GitHub", true) {
public void run(@NotNull ProgressIndicator indicator) {
runnable.run();
}
});
}
private static boolean testConnection(final String url, final String login, final String password) {
GithubUser user = retrieveCurrentUserInfo(url, login, password);
return user != null;
}
@Nullable
private static GithubUser retrieveCurrentUserInfo(@NotNull String url, @NotNull String login, @NotNull String password) {
try {
JsonElement result = GithubApiUtil.getRequest(url, login, password, "/user");
return parseUserInfo(result);
}
catch (IOException e) {
LOG.info(e);
return null;
}
}
@Nullable
private static GithubUser parseUserInfo(@Nullable JsonElement result) {
if (result == null) {
return null;
}
if (!result.isJsonObject()) {
LOG.error(String.format("Unexpected JSON result format: %s", result));
return null;
}
JsonObject obj = (JsonObject)result;
if (!obj.has("plan")) {
return null;
}
GithubUser.Plan plan = parsePlan(obj.get("plan"));
return new GithubUser(plan);
}
@NotNull
private static GithubUser.Plan parsePlan(JsonElement plan) {
if (!plan.isJsonObject()) {
return GithubUser.Plan.FREE;
}
return GithubUser.Plan.fromString(plan.getAsJsonObject().get("name").getAsString());
}
@NotNull
private static List<RepositoryInfo> getAvailableRepos(@NotNull String url, @NotNull String login, @NotNull String password,
boolean ownOnly) {
final String request = (ownOnly ? "/user/repos" : "/user/watched");
try {
JsonElement result = GithubApiUtil.getRequest(url, login, password, request);
if (result == null) {
return Collections.emptyList();
}
return parseRepositoryInfos(result);
}
catch (IOException e) {
LOG.error(e);
return Collections.emptyList();
}
}
@NotNull
private static List<RepositoryInfo> parseRepositoryInfos(@NotNull JsonElement result) {
if (!result.isJsonArray()) {
LOG.assertTrue(result.isJsonObject(), String.format("Unexpected JSON result format: %s", result));
return Collections.singletonList(parseSingleRepositoryInfo(result.getAsJsonObject()));
}
List<RepositoryInfo> repositories = new ArrayList<RepositoryInfo>();
for (JsonElement element : result.getAsJsonArray()) {
LOG.assertTrue(element.isJsonObject(),
String.format("This element should be a JsonObject: %s%nTotal JSON response: %n%s", element, result));
repositories.add(parseSingleRepositoryInfo(element.getAsJsonObject()));
}
return repositories;
}
@NotNull
private static RepositoryInfo parseSingleRepositoryInfo(@NotNull JsonObject result) {
String name = result.get("name").getAsString();
String cloneUrl = result.get("clone_url").getAsString();
String ownerName = result.get("owner").getAsJsonObject().get("login").getAsString();
String parentName = result.has("parent") ? result.get("parent").getAsJsonObject().get("full_name").getAsString(): null;
boolean fork = result.get("fork").getAsBoolean();
return new RepositoryInfo(name, cloneUrl, ownerName, parentName, fork);
}
@Nullable
private static RepositoryInfo getDetailedRepoInfo(@NotNull String url, @NotNull String login, @NotNull String password,
@NotNull String owner, @NotNull String name) {
try {
final String request = "/repos/" + owner + "/" + name;
JsonElement jsonObject = GithubApiUtil.getRequest(url, login, password, request);
if (jsonObject == null) {
LOG.info(String.format("Information about repository is unavailable. Owner: %s, Name: %s", owner, name));
return null;
}
return parseSingleRepositoryInfo(jsonObject.getAsJsonObject());
}
catch (IOException e) {
LOG.info(String.format("Exception was thrown when trying to retrieve information about repository. Owner: %s, Name: %s",
owner, name));
return null;
}
}
public static boolean isPrivateRepoAllowed(final String url, final String login, final String password) {
GithubUser user = retrieveCurrentUserInfo(url, login, password);
if (user == null) {
return false;
}
return user.getPlan().isPrivateRepoAllowed();
}
public static boolean checkCredentials(final Project project) {
final GithubSettings settings = GithubSettings.getInstance();
return checkCredentials(project, settings.getHost(), settings.getLogin(), settings.getPassword());
}
public static boolean checkCredentials(final Project project, final String url, final String login, final String password) {
if (StringUtil.isEmptyOrSpaces(url) || StringUtil.isEmptyOrSpaces(login) || StringUtil.isEmptyOrSpaces(password)){
return false;
}
return accessToGithubWithModalProgress(project, new Computable<Boolean>() {
@Override
public Boolean compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to GitHub");
return testConnection(url, login, password);
}
});
}
/**
* Shows GitHub login settings if credentials are wrong or empty and return the list of all the watched repos by user
* @param project
* @return
*/
@Nullable
public static List<RepositoryInfo> getAvailableRepos(final Project project, final boolean ownOnly) {
while (!checkCredentials(project)){
final GithubLoginDialog dialog = new GithubLoginDialog(project);
dialog.show();
if (!dialog.isOK()){
return null;
}
}
// Otherwise our credentials are valid and they are successfully stored in settings
final GithubSettings settings = GithubSettings.getInstance();
final String validPassword = settings.getPassword();
return accessToGithubWithModalProgress(project, new Computable<List<RepositoryInfo>>() {
@Override
public List<RepositoryInfo> compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Extracting info about available repositories");
return getAvailableRepos(settings.getHost(), settings.getLogin(), validPassword, ownOnly);
}
});
}
/**
* Shows GitHub login settings if credentials are wrong or empty and return the list of all the watched repos by user
* @param project
* @return
*/
@Nullable
public static RepositoryInfo getDetailedRepositoryInfo(final Project project, final String owner, final String name) {
final GithubSettings settings = GithubSettings.getInstance();
final String password = settings.getPassword();
final Boolean validCredentials = accessToGithubWithModalProgress(project, new Computable<Boolean>() {
@Override
public Boolean compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to GitHub");
return testConnection(settings.getHost(), settings.getLogin(), password);
}
});
if (validCredentials == null) {
return null;
}
if (!validCredentials){
final GithubLoginDialog dialog = new GithubLoginDialog(project);
dialog.show();
if (!dialog.isOK()) {
return null;
}
}
// Otherwise our credentials are valid and they are successfully stored in settings
final String validPassword = settings.getPassword();
return accessToGithubWithModalProgress(project, new Computable<RepositoryInfo>() {
@Nullable
@Override
public RepositoryInfo compute() {
ProgressManager.getInstance().getProgressIndicator().setText("Extracting detailed info about repository ''" + name + "''");
return getDetailedRepoInfo(settings.getHost(), settings.getLogin(), validPassword, owner, name);
}
});
}
@Nullable
public static GitRemote findGitHubRemoteBranch(@NotNull GitRepository repository) {
// i.e. find origin which points on my github repo
// Check that given repository is properly configured git repository
for (GitRemote gitRemote : repository.getRemotes()) {
if (getGithubUrl(gitRemote) != null){
return gitRemote;
}
}
return null;
}
@Nullable
public static String getGithubUrl(final GitRemote gitRemote){
final GithubSettings githubSettings = GithubSettings.getInstance();
final String host = githubSettings.getHost();
final String username = githubSettings.getLogin();
// TODO this doesn't work with organizational accounts
final String userRepoMarkerSSHProtocol = host + ":" + username + "/";
final String userRepoMarkerOtherProtocols = host + "/" + username + "/";
for (String pushUrl : gitRemote.getUrls()) {
if (pushUrl.contains(userRepoMarkerSSHProtocol) || pushUrl.contains(userRepoMarkerOtherProtocols)) {
return pushUrl;
}
}
return null;
}
public static 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.getString("find.git.error.title"));
return false;
}
if (!version.isSupported()) {
Messages.showWarningDialog(project, GitBundle.message("find.git.unsupported.message", version.toString(), GitVersion.MIN),
GitBundle.getString("find.git.success.title"));
return false;
}
return true;
}
static boolean isRepositoryOnGitHub(@NotNull GitRepository repository) {
return findGithubRemoteUrl(repository) != null;
}
@Nullable
static String findGithubRemoteUrl(@NotNull GitRepository repository) {
for (GitRemote remote : repository.getRemotes()) {
for (String url : remote.getUrls()) {
if (isGithubUrl(url)) {
return url;
}
}
}
return null;
}
private static boolean isGithubUrl(@NotNull String url) {
return url.contains("github.com");
}
static void setVisibleEnabled(AnActionEvent e, boolean visible, boolean enabled) {
e.getPresentation().setVisible(visible);
e.getPresentation().setEnabled(enabled);
}
@Nullable
public static String getUserAndRepositoryOrShowError(@NotNull Project project, @NotNull String url) {
int index = -1;
if (url.startsWith(getHttpsUrl())) {
index = url.lastIndexOf('/');
if (index == -1) {
Messages.showErrorDialog(project, "Cannot extract info about repository name: " + url, GithubOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER);
return null;
}
index = url.substring(0, index).lastIndexOf('/');
if (index == -1) {
Messages.showErrorDialog(project, "Cannot extract info about repository owner: " + url, GithubOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER);
return null;
}
}
else {
index = url.lastIndexOf(':');
if (index == -1) {
Messages.showErrorDialog(project, "Cannot extract info about repository name and owner: " + url, GithubOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER);
return null;
}
}
String repoInfo = url.substring(index + 1);
if (repoInfo.endsWith(".git")) {
repoInfo = repoInfo.substring(0, repoInfo.length() - 4);
}
return repoInfo;
}
@NotNull
static String makeGithubRepoUrlFromRemoteUrl(@NotNull String remoteUrl) {
remoteUrl = removeEndingDotGit(remoteUrl);
if (remoteUrl.startsWith("http")) {
return remoteUrl;
}
if (remoteUrl.startsWith("git://")) {
return "https" + remoteUrl.substring(3);
}
return convertFromSshToHttp(remoteUrl);
}
@NotNull
private static String convertFromSshToHttp(@NotNull String remoteUrl) {
// Format: git@github.com:account/repository
int indexOfAt = remoteUrl.indexOf("@");
if (indexOfAt < 0) {
throw new IllegalStateException("Invalid remote Github SSH url: " + remoteUrl);
}
String withoutPrefix = remoteUrl.substring(indexOfAt + 1, remoteUrl.length());
return "https://" + withoutPrefix.replace(':', '/');
}
@NotNull
private static String removeEndingDotGit(@NotNull String url) {
final String DOT_GIT = ".git";
if (url.endsWith(DOT_GIT)) {
return url.substring(0, url.length() - DOT_GIT.length());
}
return url;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.github.ui;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.vcs.annotate.AnnotationGutterActionProvider;
import com.intellij.openapi.vcs.annotate.FileAnnotation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.github.GithubShowCommitInBrowserFromAnnotateAction;
/**
* @author Kirill Likhodedov
*/
public class GithubAnnotationGutterActionProvider implements AnnotationGutterActionProvider {
@NotNull
@Override
public AnAction createAction(@NotNull FileAnnotation annotation) {
return new GithubShowCommitInBrowserFromAnnotateAction(annotation);
}
}