├── .gitignore
├── .idea
├── .name
├── compiler.xml
├── encodings.xml
├── misc.xml
├── modules.xml
├── scopes
│ └── scope_settings.xml
└── vcs.xml
├── IDEA-GitLab-Integration.iml
├── LICENSE
├── META-INF
└── plugin.xml
├── README.md
├── resources
└── ru
│ └── trylogic
│ └── idea
│ └── gitlab
│ └── gitlab_icon.png
└── src
├── icons
└── GitlabIcons.java
└── ru
└── trylogic
└── idea
└── gitlab
└── integration
├── actions
└── GitLabOpenInBrowserAction.java
└── utils
└── GitlabUrlUtil.java
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /out
3 | IDEA-GitLab-Integration.jar
4 |
5 | .idea/workspace.xml
6 | .idea/rebel_project.xml
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | IDEA-GitLab-Integration
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/scopes/scope_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/IDEA-GitLab-Integration.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Sergey Egorov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/META-INF/plugin.xml:
--------------------------------------------------------------------------------
1 |
2 | ru.trylogic.idea.gitlab.integration
3 | GitLab integration
4 | 1.0.4
5 | Sergei BSiDeUp Egorov
6 |
7 | GitLab integration plugin.
9 | Support "Open file in browser" command.
10 | ]]>
11 |
12 |
15 |
16 |
17 |
18 |
19 | com.intellij.modules.vcs
20 | com.intellij.modules.lang
21 | Git4Idea
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | IDEA-GitLab-Integration
2 | =======================
3 |
4 | This plugin adds an `Open on GitLab` entry in the right-click menu of files.
5 |
--------------------------------------------------------------------------------
/resources/ru/trylogic/idea/gitlab/gitlab_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsideup/IDEA-GitLab-Integration/db5cae0d76cd70fae172bb9533bc0b6ec35c1672/resources/ru/trylogic/idea/gitlab/gitlab_icon.png
--------------------------------------------------------------------------------
/src/icons/GitlabIcons.java:
--------------------------------------------------------------------------------
1 | package icons;
2 |
3 | import com.intellij.openapi.util.IconLoader;
4 |
5 | import javax.swing.*;
6 |
7 | /**
8 | * NOTE THIS FILE IS AUTO-GENERATED by the build/scripts/icons.gant
9 | * Don't repeat mistakes of others ;-)
10 | */
11 | public class GitlabIcons {
12 | private static Icon load(String path) {
13 | return IconLoader.getIcon(path, GitlabIcons.class);
14 | }
15 |
16 | public static final Icon Gitlab_icon = load("/ru/trylogic/idea/gitlab/gitlab_icon.png"); // 16x16
17 | }
--------------------------------------------------------------------------------
/src/ru/trylogic/idea/gitlab/integration/actions/GitLabOpenInBrowserAction.java:
--------------------------------------------------------------------------------
1 | package ru.trylogic.idea.gitlab.integration.actions;
2 |
3 | import com.intellij.ide.BrowserUtil;
4 | import com.intellij.openapi.actionSystem.*;
5 | import com.intellij.openapi.diagnostic.Logger;
6 | import com.intellij.openapi.editor.Editor;
7 | import com.intellij.openapi.editor.SelectionModel;
8 | import com.intellij.openapi.project.DumbAwareAction;
9 | import com.intellij.openapi.project.Project;
10 | import com.intellij.openapi.ui.popup.JBPopupFactory;
11 | import com.intellij.openapi.ui.popup.ListPopup;
12 | import com.intellij.openapi.vcs.VcsNotifier;
13 | import com.intellij.openapi.vcs.changes.Change;
14 | import com.intellij.openapi.vcs.changes.ChangeListManager;
15 | import com.intellij.openapi.vfs.VirtualFile;
16 | import git4idea.GitLocalBranch;
17 | import git4idea.GitRemoteBranch;
18 | import git4idea.GitUtil;
19 | import git4idea.repo.GitRemote;
20 | import git4idea.repo.GitRepository;
21 | import git4idea.repo.GitRepositoryManager;
22 | import icons.GitlabIcons;
23 | import org.jetbrains.annotations.NotNull;
24 | import org.jetbrains.annotations.Nullable;
25 | import ru.trylogic.idea.gitlab.integration.utils.GitlabUrlUtil;
26 |
27 | import java.util.ArrayList;
28 | import java.util.List;
29 |
30 | public class GitLabOpenInBrowserAction extends DumbAwareAction {
31 |
32 | public static final Logger LOG = Logger.getInstance("gitlab");
33 |
34 | public static final String CANNOT_OPEN_IN_BROWSER = "Cannot open in browser";
35 |
36 | protected GitLabOpenInBrowserAction() {
37 | super("Open on GitLab", "Open corresponding link in browser", GitlabIcons.Gitlab_icon);
38 | }
39 |
40 | static void setVisibleEnabled(AnActionEvent e, boolean visible, boolean enabled) {
41 | e.getPresentation().setVisible(visible);
42 | e.getPresentation().setEnabled(enabled);
43 | }
44 |
45 | static void showError(Project project, String title, String message) {
46 | showError(project, title, message, null);
47 | }
48 |
49 | static void showError(Project project, String title, String message, String debugInfo) {
50 | LOG.warn(title + "; " + message);
51 | if (debugInfo != null) {
52 | LOG.warn(debugInfo);
53 | }
54 | VcsNotifier.getInstance(project).notifyError(title, message);
55 | }
56 |
57 | @Nullable
58 | static String makeUrlToOpen(@Nullable Editor editor,
59 | @NotNull String relativePath,
60 | @NotNull String branch,
61 | @NotNull String remoteUrl) {
62 | final StringBuilder builder = new StringBuilder();
63 | final String repoUrl = GitlabUrlUtil.makeRepoUrlFromRemoteUrl(remoteUrl);
64 | if (repoUrl == null) {
65 | return null;
66 | }
67 | builder.append(repoUrl).append("/blob/").append(branch).append(relativePath);
68 |
69 | if (editor != null && editor.getDocument().getLineCount() >= 1) {
70 | // lines are counted internally from 0, but from 1 on gitlab
71 | SelectionModel selectionModel = editor.getSelectionModel();
72 | final int begin = editor.getDocument().getLineNumber(selectionModel.getSelectionStart()) + 1;
73 | final int selectionEnd = selectionModel.getSelectionEnd();
74 | int end = editor.getDocument().getLineNumber(selectionEnd) + 1;
75 | if (editor.getDocument().getLineStartOffset(end - 1) == selectionEnd) {
76 | end -= 1;
77 | }
78 | builder.append("#L").append(begin).append('-').append(end);
79 | }
80 |
81 | return builder.toString();
82 | }
83 |
84 | @Nullable
85 | public static String getBranchNameOnRemote(@NotNull Project project, @NotNull GitRepository repository) {
86 | GitLocalBranch currentBranch = repository.getCurrentBranch();
87 | if (currentBranch == null) {
88 | showError(project, CANNOT_OPEN_IN_BROWSER,
89 | "Can't open the file on GitLab when repository is on detached HEAD. Please checkout a branch.");
90 | return null;
91 | }
92 |
93 | GitRemoteBranch tracked = currentBranch.findTrackedBranch(repository);
94 | if (tracked == null) {
95 | showError(project, CANNOT_OPEN_IN_BROWSER, "Can't open the file on GitLab when current branch doesn't have a tracked branch.",
96 | "Current branch: " + currentBranch + ", tracked info: " + repository.getBranchTrackInfos());
97 | return null;
98 | }
99 |
100 | return tracked.getNameForRemoteOperations();
101 | }
102 |
103 | @Override
104 | public void update(final AnActionEvent e) {
105 | Project project = e.getData(PlatformDataKeys.PROJECT);
106 | VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
107 | if (project == null || project.isDefault() || virtualFile == null) {
108 | setVisibleEnabled(e, false, false);
109 | return;
110 | }
111 |
112 | final GitRepository gitRepository = GitUtil.getRepositoryManager(project).getRepositoryForFile(virtualFile);
113 | if (gitRepository == null) {
114 | setVisibleEnabled(e, false, false);
115 | return;
116 | }
117 |
118 | ChangeListManager changeListManager = ChangeListManager.getInstance(project);
119 | if (changeListManager.isUnversioned(virtualFile)) {
120 | setVisibleEnabled(e, true, false);
121 | return;
122 | }
123 |
124 | Change change = changeListManager.getChange(virtualFile);
125 | if (change != null && change.getType() == Change.Type.NEW) {
126 | setVisibleEnabled(e, true, false);
127 | return;
128 | }
129 |
130 | setVisibleEnabled(e, true, true);
131 | }
132 |
133 | @Override
134 | public void actionPerformed(final AnActionEvent e) {
135 | final Project project = e.getData(PlatformDataKeys.PROJECT);
136 | final VirtualFile virtualFile = e.getData(PlatformDataKeys.VIRTUAL_FILE);
137 | final Editor editor = e.getData(PlatformDataKeys.EDITOR);
138 | if (virtualFile == null || project == null || project.isDisposed()) {
139 | return;
140 | }
141 |
142 | GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
143 | final GitRepository repository = manager.getRepositoryForFile(virtualFile);
144 | if (repository == null) {
145 | StringBuilder details = new StringBuilder("file: " + virtualFile.getPresentableUrl() + "; Git repositories: ");
146 | for (GitRepository repo : manager.getRepositories()) {
147 | details.append(repo.getPresentableUrl()).append("; ");
148 | }
149 | showError(project, CANNOT_OPEN_IN_BROWSER, "Can't find git repository", details.toString());
150 | return;
151 | }
152 |
153 | final String rootPath = repository.getRoot().getPath();
154 | final String path = virtualFile.getPath();
155 |
156 | List remoteSelectedActions = new ArrayList();
157 |
158 | for (GitRemote remote : repository.getRemotes()) {
159 | remoteSelectedActions.add(new RemoteSelectedAction(project, repository, editor, remote, rootPath, path));
160 | }
161 |
162 | if (remoteSelectedActions.size() > 1) {
163 | DefaultActionGroup remotesActionGroup = new DefaultActionGroup();
164 | remotesActionGroup.addAll(remoteSelectedActions);
165 | DataContext dataContext = e.getDataContext();
166 | final ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup(
167 | "Select remote",
168 | remotesActionGroup,
169 | dataContext,
170 | JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
171 | true);
172 |
173 | popup.showInBestPositionFor(dataContext);
174 | } else if (remoteSelectedActions.size() == 1) {
175 | remoteSelectedActions.get(0).actionPerformed(null);
176 | } else {
177 | showError(project, CANNOT_OPEN_IN_BROWSER, "Can't find gitlab remote");
178 | }
179 | }
180 |
181 |
182 | }
183 |
184 | class RemoteSelectedAction extends AnAction {
185 |
186 | private final Editor editor;
187 |
188 | private final GitRemote remote;
189 |
190 | private final String rootPath;
191 |
192 | private final String path;
193 |
194 | private final Project project;
195 |
196 | private final GitRepository repository;
197 |
198 | public RemoteSelectedAction(@NotNull Project project, @NotNull GitRepository repository, @Nullable Editor editor,
199 | @NotNull GitRemote remote, @NotNull String rootPath, @NotNull String path) {
200 | super(remote.getName());
201 | this.project = project;
202 | this.repository = repository;
203 | this.editor = editor;
204 | this.remote = remote;
205 | this.rootPath = rootPath;
206 | this.path = path;
207 | }
208 |
209 | @Override
210 | public void update(AnActionEvent e) {
211 | super.update(e);
212 | setEnabledInModalContext(true);
213 | e.getPresentation().setEnabled(true);
214 | }
215 |
216 | @Override
217 | public void actionPerformed(@Nullable AnActionEvent unused) {
218 | if (!path.startsWith(rootPath)) {
219 | GitLabOpenInBrowserAction.showError(project, GitLabOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER,
220 | "File is not under repository root", "Root: " + rootPath + ", file: " + path);
221 | return;
222 | }
223 |
224 | String branch = GitLabOpenInBrowserAction.getBranchNameOnRemote(project, repository);
225 | if (branch == null) {
226 | return;
227 | }
228 |
229 | String remoteUrl = remote.getFirstUrl();
230 |
231 | if (remoteUrl == null) {
232 | GitLabOpenInBrowserAction.showError(project, GitLabOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER,
233 | "Can't obtain url for remote", remote.getName());
234 | return;
235 | }
236 |
237 | String relativePath = path.substring(rootPath.length());
238 | String urlToOpen = GitLabOpenInBrowserAction.makeUrlToOpen(editor, relativePath, branch, remoteUrl);
239 | if (urlToOpen == null) {
240 | GitLabOpenInBrowserAction.showError(project, GitLabOpenInBrowserAction.CANNOT_OPEN_IN_BROWSER,
241 | "Can't create properly url", remote.getFirstUrl());
242 | return;
243 | }
244 |
245 | BrowserUtil.launchBrowser(urlToOpen);
246 | }
247 | }
--------------------------------------------------------------------------------
/src/ru/trylogic/idea/gitlab/integration/utils/GitlabUrlUtil.java:
--------------------------------------------------------------------------------
1 | package ru.trylogic.idea.gitlab.integration.utils;
2 |
3 | import com.intellij.openapi.ui.Messages;
4 | import com.intellij.openapi.util.text.StringUtil;
5 | import git4idea.repo.GitRemote;
6 | import git4idea.repo.GitRepository;
7 | import org.jetbrains.annotations.NotNull;
8 | import org.jetbrains.annotations.Nullable;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class GitlabUrlUtil {
14 |
15 | @Nullable
16 | public static String findRemoteUrl(@NotNull GitRepository repository) {
17 | List remotes = new ArrayList();
18 | for (GitRemote remote : repository.getRemotes()) {
19 | if (remote.getName().equals("origin")) {
20 | return remote.getFirstUrl();
21 | }
22 | }
23 | return null;
24 | }
25 |
26 | public static String makeRepoUrlFromRemoteUrl(@NotNull String remoteUrl) {
27 | String cleanedFromDotGit = StringUtil.trimEnd(remoteUrl, ".git");
28 |
29 | if (remoteUrl.startsWith("http://") || remoteUrl.startsWith("https://")) {
30 | return cleanedFromDotGit;
31 | } else if (remoteUrl.startsWith("git@")) {
32 | String cleanedFromGitAt = StringUtil.trimStart(cleanedFromDotGit, "git@");
33 |
34 | return "http://" + StringUtil.replace(cleanedFromGitAt, ":", "/");
35 | } else {
36 | throw new IllegalStateException("Invalid remote Gitlab url: " + remoteUrl);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------