diff --git a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml index 046a54fe4195..fc4d0c88bbe1 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml @@ -42,6 +42,7 @@ interface="com.intellij.openapi.vcs.actions.VcsQuickListContentProvider"/> + diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/AnnotationGutterActionProvider.java b/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/AnnotationGutterActionProvider.java new file mode 100644 index 000000000000..d3bc6fcda482 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/annotate/AnnotationGutterActionProvider.java @@ -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 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); + +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java index 6f9ce4b23b09..a82236fd3d3a 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotateToggleAction.java @@ -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 bgColorMap = Registry.is("vcs.show.colored.annotations") ? computeBgColors(fileAnnotation) : null; final Map 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 computeLineNumbers(FileAnnotation fileAnnotation) { final SortedList revisions = new SortedList(new Comparator() { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotationPresentation.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotationPresentation.java index bf36fb72b917..f2c887a5078d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotationPresentation.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/actions/AnnotationPresentation.java @@ -85,6 +85,11 @@ class AnnotationPresentation implements TextAnnotationPresentation { return myActions; } + @NotNull + public List getActions() { + return myActions; + } + public void addSourceSwitchListener(final Consumer listener) { mySwitchAction.addSourceSwitchListener(listener); } diff --git a/plugins/github/src/META-INF/plugin.xml b/plugins/github/src/META-INF/plugin.xml index 2a8f25f69588..104297300162 100644 --- a/plugins/github/src/META-INF/plugin.xml +++ b/plugins/github/src/META-INF/plugin.xml @@ -14,6 +14,7 @@ + @@ -28,7 +29,7 @@ - + diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubOpenInBrowserAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubOpenInBrowserAction.java index a25cb131f880..b9cc2f21dd7d 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubOpenInBrowserAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubOpenInBrowserAction.java @@ -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; + } + +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserAction.java index 6187986fb9cc..479033f3edbb 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserAction.java @@ -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; - } - } - } diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserFromAnnotateAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserFromAnnotateAction.java new file mode 100644 index 000000000000..2455847ab35c --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserFromAnnotateAction.java @@ -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; + } + + } + +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserFromLogAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserFromLogAction.java new file mode 100644 index 000000000000..17545e8bca37 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubShowCommitInBrowserFromLogAction.java @@ -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; + } + } + +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubUtil.java b/plugins/github/src/org/jetbrains/plugins/github/GithubUtil.java index 54eb48a85ab9..a0ab2f64d989 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubUtil.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubUtil.java @@ -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 accessToGithubWithModalProgress(final Project project, final Computable computable) { - final Ref result = new Ref(); - 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 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 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 repositories = new ArrayList(); - 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() { - @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 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>() { - @Override - public List 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() { - @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() { - @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 accessToGithubWithModalProgress(final Project project, final Computable computable) { + final Ref result = new Ref(); + 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 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 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 repositories = new ArrayList(); + 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() { + @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 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>() { + @Override + public List 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() { + @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() { + @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; + } +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubAnnotationGutterActionProvider.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubAnnotationGutterActionProvider.java new file mode 100644 index 000000000000..7b7a7c1bcb9e --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubAnnotationGutterActionProvider.java @@ -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); + } + +}