├── settings.gradle ├── docs ├── clone.png ├── share.png ├── comments.png ├── settings.png ├── addServer.png ├── codeReview.png ├── listMerge.png └── createMerge.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── icons │ │ │ ├── loading.gif │ │ │ ├── gitLabSmall.png │ │ │ └── gitLabLogo.svg │ │ └── META-INF │ │ │ └── plugin.xml │ └── java │ │ └── com │ │ └── ppolivka │ │ └── gitlabprojects │ │ ├── component │ │ ├── Searchable.java │ │ └── SearchBoxModel.java │ │ ├── configuration │ │ ├── ApiToRepoUrlConverter.java │ │ ├── SettingsConfigurableProvider.java │ │ ├── SettingError.java │ │ ├── SettingsDialog.java │ │ ├── ProjectState.java │ │ ├── SettingsView.form │ │ ├── SettingsView.java │ │ ├── ServerConfiguration.form │ │ ├── ServerConfiguration.java │ │ └── SettingsState.java │ │ ├── merge │ │ ├── request │ │ │ ├── EmptyUser.java │ │ │ ├── SearchableUser.java │ │ │ ├── GitLabMergeRequestAction.java │ │ │ ├── SearchableUsers.java │ │ │ ├── CreateMergeRequestDialog.java │ │ │ └── CreateMergeRequestDialog.form │ │ ├── info │ │ │ ├── DiffInfo.java │ │ │ └── BranchInfo.java │ │ ├── list │ │ │ ├── GitLabMergeRequestListAction.java │ │ │ ├── GitLabMergeRequestListDialog.form │ │ │ ├── GitLabMergeRequestListDialog.java │ │ │ ├── CodeReviewDialog.form │ │ │ ├── CodeReviewDialog.java │ │ │ └── GitLabMergeRequestListWorker.java │ │ ├── helper │ │ │ └── GitLabProjectMatcher.java │ │ ├── GitLabMergeRequestWorker.java │ │ └── GitLabDiffViewWorker.java │ │ ├── dto │ │ └── GitlabServer.java │ │ ├── common │ │ ├── GitLabIcons.java │ │ ├── ReadOnlyTableModel.java │ │ ├── NoGitLabApiAction.java │ │ └── GitLabApiAction.java │ │ ├── exception │ │ ├── GitLabException.java │ │ └── MergeRequestException.java │ │ ├── api │ │ ├── dto │ │ │ ├── NamespaceDto.java │ │ │ ├── ProjectDto.java │ │ │ └── ServerDto.java │ │ └── ApiFacade.java │ │ ├── util │ │ ├── MessageUtil.java │ │ └── GitLabUtil.java │ │ ├── comment │ │ ├── CommentDetail.java │ │ ├── AddCommentDialog.form │ │ ├── GitLabCommentsListWorker.java │ │ ├── AddCommentDialog.java │ │ ├── CommentsDialog.form │ │ ├── CommentsDialog.java │ │ └── CommentDetail.form │ │ ├── checkout │ │ └── GitLabRepositoryHostingService.java │ │ └── share │ │ ├── GitLabShareDialog.java │ │ ├── GitLabShareDialog.form │ │ └── GitLabShareAction.java └── test │ └── java │ └── com │ └── ppolivka │ └── gitlabprojects │ ├── util │ ├── DummyDisposable.java │ ├── GitLabUtilTest.java │ └── DummyApplication.java │ ├── configuration │ └── ApiToRepoUrlConverterTest.java │ └── merge │ └── helper │ └── GitLabProjectMatcherTest.java ├── LICENSE ├── README.md ├── .gitignore ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'GitLabProjects' 2 | 3 | -------------------------------------------------------------------------------- /docs/clone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/clone.png -------------------------------------------------------------------------------- /docs/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/share.png -------------------------------------------------------------------------------- /docs/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/comments.png -------------------------------------------------------------------------------- /docs/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/settings.png -------------------------------------------------------------------------------- /docs/addServer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/addServer.png -------------------------------------------------------------------------------- /docs/codeReview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/codeReview.png -------------------------------------------------------------------------------- /docs/listMerge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/listMerge.png -------------------------------------------------------------------------------- /docs/createMerge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/docs/createMerge.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/icons/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/src/main/resources/icons/loading.gif -------------------------------------------------------------------------------- /src/main/resources/icons/gitLabSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wudeyong/GitLabProjects/HEAD/src/main/resources/icons/gitLabSmall.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jun 11 17:24:58 CEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-rc-2-all.zip 7 | -------------------------------------------------------------------------------- /src/test/java/com/ppolivka/gitlabprojects/util/DummyDisposable.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.util; 2 | 3 | import com.intellij.openapi.Disposable; 4 | 5 | /** 6 | * Dummy implementation of Disposable for unit tests 7 | * 8 | * @author ppolivka 9 | * @since 1.3.6 10 | */ 11 | public class DummyDisposable implements Disposable { 12 | @Override 13 | public void dispose() { 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/component/Searchable.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.component; 2 | 3 | import java.util.Collection; 4 | 5 | /** 6 | * Interface for searching actions 7 | * 8 | * @author ppolivka 9 | * @since 1.4.0 10 | */ 11 | public interface Searchable { 12 | 13 | /** 14 | * Returns collections of R objects based on T 15 | * @param toSearch 16 | * @return 17 | */ 18 | Collection search(T toSearch); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/ApiToRepoUrlConverter.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import lombok.SneakyThrows; 4 | 5 | import java.net.URI; 6 | 7 | public class ApiToRepoUrlConverter { 8 | 9 | @SneakyThrows 10 | public static String convertApiUrlToRepoUrl(String apiUrl) { 11 | URI uri = new URI(apiUrl); 12 | String domain = uri.getHost(); 13 | return domain.startsWith("www.") ? domain.substring(4) : domain; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/request/EmptyUser.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.request; 2 | 3 | /** 4 | * Empty Searchable User implementation 5 | * User for query implementation and various other pplaceholder 6 | * 7 | * @author ppolivka 8 | * @since 1.4.0 9 | */ 10 | public class EmptyUser extends SearchableUser { 11 | private String user; 12 | 13 | public EmptyUser(String user) { 14 | super(null); 15 | this.user = user; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return user; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Pavel Polivka 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/dto/GitlabServer.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.dto; 2 | 3 | 4 | import lombok.Data; 5 | 6 | @Data 7 | public class GitlabServer { 8 | 9 | private String apiUrl = ""; 10 | private String apiToken = ""; 11 | private String repositoryUrl = ""; 12 | private CheckoutType preferredConnection = CheckoutType.SSH; 13 | private boolean removeSourceBranch = true; 14 | 15 | @Override 16 | public String toString() { 17 | return apiUrl; 18 | } 19 | 20 | public enum CheckoutType { 21 | SSH, 22 | HTTPS; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/request/SearchableUser.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.request; 2 | 3 | import org.gitlab.api.models.GitlabUser; 4 | 5 | /** 6 | * One user returned from Search, used by combo box model 7 | * 8 | * @author ppolivka 9 | * @since 1.4.0 10 | */ 11 | public class SearchableUser { 12 | 13 | private GitlabUser user; 14 | 15 | public SearchableUser(GitlabUser user) { 16 | this.user = user; 17 | } 18 | 19 | public GitlabUser getGitLabUser() { 20 | return user; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return user.getName(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/common/GitLabIcons.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.common; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | import com.intellij.util.ImageLoader; 5 | import com.intellij.util.ui.JBImageIcon; 6 | 7 | import javax.swing.*; 8 | 9 | /** 10 | * Class encapsulating all custom GitLab Project icons 11 | * 12 | * @author ppolivka 13 | * @since 28.10.2015 14 | */ 15 | public class GitLabIcons { 16 | 17 | public static Icon gitLabIcon = IconLoader.findIcon("/icons/gitLabSmall.png"); 18 | 19 | public static JBImageIcon loadingIcon = new JBImageIcon(ImageLoader.loadFromResource("/icons/loading.gif")); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/SettingsConfigurableProvider.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import com.intellij.openapi.options.Configurable; 4 | import com.intellij.openapi.options.ConfigurableProvider; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * Provider of SettingsConfigurable 9 | * 10 | * @author ppolivka 11 | * @since 9.10.2015 12 | */ 13 | public class SettingsConfigurableProvider extends ConfigurableProvider { 14 | 15 | @Nullable 16 | @Override 17 | public Configurable createConfigurable() { 18 | SettingsView settingsView = new SettingsView(); 19 | settingsView.setup(); 20 | return settingsView; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/SettingError.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | /** 4 | * Enum of all errors that can be thrown during settings 5 | * 6 | * @author ppolivka 7 | * @since 27.10.2015 8 | */ 9 | public enum SettingError { 10 | NOT_A_URL("Provided server url is not valid (must be in format http(s)://server.com)"), 11 | SERVER_CANNOT_BE_REACHED("Provided server cannot be reached"), 12 | INVALID_API_TOKEN("Provided API Token is not valid"), 13 | GENERAL_ERROR("Error during connection to server"); 14 | 15 | final String message; 16 | 17 | SettingError(String message) { 18 | this.message = message; 19 | } 20 | 21 | public String message() { 22 | return message; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/exception/GitLabException.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.exception; 2 | 3 | 4 | import org.jetbrains.annotations.NonNls; 5 | 6 | public class GitLabException extends RuntimeException { 7 | 8 | public GitLabException() { 9 | } 10 | 11 | public GitLabException(@NonNls String message) { 12 | super(message); 13 | } 14 | 15 | public GitLabException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public GitLabException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public GitLabException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/exception/MergeRequestException.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.exception; 2 | 3 | /** 4 | * Exception for actions related to merge requests 5 | * 6 | * @author ppolivka 7 | * @since 31.10.2015 8 | */ 9 | public class MergeRequestException extends Throwable { 10 | 11 | public MergeRequestException() { 12 | } 13 | 14 | public MergeRequestException(String message) { 15 | super(message); 16 | } 17 | 18 | public MergeRequestException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public MergeRequestException(Throwable cause) { 23 | super(cause); 24 | } 25 | 26 | public MergeRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/api/dto/NamespaceDto.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.api.dto; 2 | 3 | /** 4 | * Dto Class Representing one namespace 5 | * 6 | * @author ppolivka 7 | * @since 28.10.2015 8 | */ 9 | public class NamespaceDto { 10 | 11 | private int id; 12 | private String path; 13 | private String kind; 14 | 15 | public NamespaceDto() { 16 | } 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public void setId(int id) { 23 | this.id = id; 24 | } 25 | 26 | public String getPath() { 27 | return path; 28 | } 29 | 30 | public void setPath(String path) { 31 | this.path = path; 32 | } 33 | 34 | public String getKind() { 35 | return kind; 36 | } 37 | 38 | public void setKind(String kind) { 39 | this.kind = kind; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return path; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GitLab Projects 2 | Simple plugin that is adding support for GitLab specific actions to JetBrain IDEs 3 | 4 | ### Features: 5 | * **GitLab Checkout support** - add GitLab autocompleter to IDE Git checkout dialog 6 | * **GitLab Share dialog** - allows quick import of new projects to GitLab, user can specify namespace and project visibility 7 | * **GitLab Merge Request dialog** - user can quickly create new merge requests from current branch 8 | * **GitLab Merge Request List dialog** - user can list and accept all open code reviews 9 | 10 | ### Development 11 | This project is using the intellij gradle build plugin. 12 | https://github.com/JetBrains/gradle-intellij-plugin 13 | 14 | To build do 15 | 16 | `gradle build` 17 | 18 | To run it in IDE and debug 19 | 20 | `gradle runIdea` 21 | 22 | 23 | ### Contributing 24 | Everybody is welcome to contribute to this project. 25 | 26 | Submit your pull requests to **master** branch. 27 | 28 | Master is always exact code that is used in latest release. -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/util/MessageUtil.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.util; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vcs.VcsNotifier; 5 | 6 | /** 7 | * Notification utils 8 | * 9 | * @author ppolivka 10 | * @since 5.11.2015 11 | */ 12 | public class MessageUtil { 13 | 14 | public static void showErrorDialog(Project project, String message, String title) { 15 | VcsNotifier.getInstance(project).notifyError(title, message); 16 | } 17 | 18 | public static void showWarningDialog(Project project, String message, String title) { 19 | VcsNotifier.getInstance(project).notifyWarning(title, message); 20 | } 21 | 22 | public static void showInfoMessage(Project project, String message, String title) { 23 | VcsNotifier.getInstance(project).notifyInfo(title, message); 24 | } 25 | 26 | public static VcsNotifier getNotifier(Project project) { 27 | return VcsNotifier.getInstance(project); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/info/DiffInfo.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.info; 2 | 3 | import git4idea.util.GitCommitCompareInfo; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | /** 7 | * Class containing info about diff 8 | * 9 | * @author ppolivka 10 | * @since 31.10.2015 11 | */ 12 | public class DiffInfo { 13 | @NotNull 14 | private final GitCommitCompareInfo myInfo; 15 | @NotNull 16 | private final String myFrom; 17 | @NotNull 18 | private final String myTo; 19 | 20 | public DiffInfo(@NotNull GitCommitCompareInfo info, @NotNull String from, @NotNull String to) { 21 | myInfo = info; 22 | myFrom = from; // HEAD 23 | myTo = to; // BASE 24 | } 25 | 26 | @NotNull 27 | public GitCommitCompareInfo getInfo() { 28 | return myInfo; 29 | } 30 | 31 | @NotNull 32 | public String getFrom() { 33 | return myFrom; 34 | } 35 | 36 | @NotNull 37 | public String getTo() { 38 | return myTo; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/com/ppolivka/gitlabprojects/configuration/ApiToRepoUrlConverterTest.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.Parameterized; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | 11 | @RunWith(Parameterized.class) 12 | public class ApiToRepoUrlConverterTest { 13 | 14 | @Parameterized.Parameters 15 | public static Collection data() { 16 | return Arrays.asList(new Object[][] { 17 | { "https://gitlab.com", "gitlab.com" }, 18 | { "https://www.gitlab.com", "gitlab.com" }, 19 | { "http://gitlab.com/", "gitlab.com" } 20 | }); 21 | } 22 | 23 | @Parameterized.Parameter 24 | public String apiUrl; 25 | @Parameterized.Parameter(1) 26 | public String repoUrl; 27 | 28 | @Test 29 | public void convertApiUrlToRepoUrl() { 30 | Assert.assertEquals(repoUrl, ApiToRepoUrlConverter.convertApiUrlToRepoUrl(apiUrl)); 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/CommentDetail.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.comment; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.*; 9 | import java.util.Date; 10 | 11 | /** 12 | * Dialog listing details of one comment 13 | * 14 | * @author ppolivka 15 | * @since 1.3.2 16 | */ 17 | public class CommentDetail extends DialogWrapper { 18 | private JPanel panel; 19 | private JLabel authorName; 20 | private JLabel dateText; 21 | private JTextArea bodyText; 22 | 23 | protected CommentDetail(@Nullable Project project, @NotNull String name, @NotNull Date date, @NotNull String body) { 24 | super(project); 25 | init(); 26 | setTitle("Comment Detail"); 27 | authorName.setText(name); 28 | dateText.setText(date.toString()); 29 | bodyText.setText(body); 30 | } 31 | 32 | @Nullable 33 | @Override 34 | protected JComponent createCenterPanel() { 35 | return panel; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/list/GitLabMergeRequestListAction.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.list; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.ppolivka.gitlabprojects.common.GitLabApiAction; 6 | import git4idea.DialogManager; 7 | 8 | /** 9 | * Action for accepting merge request 10 | * 11 | * @author ppolivka 12 | * @since 31.10.2015 13 | */ 14 | public class GitLabMergeRequestListAction extends GitLabApiAction { 15 | 16 | public GitLabMergeRequestListAction() { 17 | super("_List Merge Requests...", "List of all merge requests for this project", AllIcons.Vcs.MergeSourcesTree); 18 | } 19 | 20 | @Override 21 | public void apiValidAction(AnActionEvent anActionEvent) { 22 | GitLabMergeRequestListWorker mergeRequestListWorker = GitLabMergeRequestListWorker.create(project, file); 23 | GitLabMergeRequestListDialog gitLabMergeRequestListDialog = new GitLabMergeRequestListDialog(project, mergeRequestListWorker, file); 24 | DialogManager.show(gitLabMergeRequestListDialog); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/api/dto/ProjectDto.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.api.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * DTO Class representing one GitLab Project 7 | * 8 | * @author ppolivka 9 | * @since 10.10.2015 10 | */ 11 | public class ProjectDto implements Serializable { 12 | private String name; 13 | private String namespace; 14 | private String sshUrl; 15 | private String httpUrl; 16 | 17 | public String getName() { 18 | return name; 19 | } 20 | 21 | public void setName(String name) { 22 | this.name = name; 23 | } 24 | 25 | public String getNamespace() { 26 | return namespace; 27 | } 28 | 29 | public void setNamespace(String namespace) { 30 | this.namespace = namespace; 31 | } 32 | 33 | public String getSshUrl() { 34 | return sshUrl; 35 | } 36 | 37 | public void setSshUrl(String sshUrl) { 38 | this.sshUrl = sshUrl; 39 | } 40 | 41 | public String getHttpUrl() { 42 | return httpUrl; 43 | } 44 | 45 | public void setHttpUrl(String httpUrl) { 46 | this.httpUrl = httpUrl; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/common/ReadOnlyTableModel.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.common; 2 | 3 | import javax.swing.table.DefaultTableModel; 4 | import java.util.Vector; 5 | 6 | /** 7 | * JTable table model that is not supporting editing of cells in table 8 | * 9 | * @author ppolivka 10 | * @since 22.12.2015 11 | */ 12 | public class ReadOnlyTableModel extends DefaultTableModel { 13 | 14 | public ReadOnlyTableModel() { 15 | } 16 | 17 | public ReadOnlyTableModel(int rowCount, int columnCount) { 18 | super(rowCount, columnCount); 19 | } 20 | 21 | public ReadOnlyTableModel(Vector columnNames, int rowCount) { 22 | super(columnNames, rowCount); 23 | } 24 | 25 | public ReadOnlyTableModel(Object[] columnNames, int rowCount) { 26 | super(columnNames, rowCount); 27 | } 28 | 29 | public ReadOnlyTableModel(Vector data, Vector columnNames) { 30 | super(data, columnNames); 31 | } 32 | 33 | public ReadOnlyTableModel(Object[][] data, Object[] columnNames) { 34 | super(data, columnNames); 35 | } 36 | 37 | @Override 38 | public boolean isCellEditable(int row, int column) { 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/request/GitLabMergeRequestAction.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.request; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.ppolivka.gitlabprojects.common.GitLabApiAction; 6 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 7 | import git4idea.DialogManager; 8 | 9 | /** 10 | * GitLab Merge Request Action class 11 | * 12 | * @author ppolivka 13 | * @since 30.10.2015 14 | */ 15 | public class GitLabMergeRequestAction extends GitLabApiAction { 16 | 17 | public GitLabMergeRequestAction() { 18 | super("Create _Merge Request...", "Creates merge request from current branch", AllIcons.Vcs.Merge); 19 | } 20 | 21 | @Override 22 | public void apiValidAction(AnActionEvent anActionEvent) { 23 | 24 | if (!GitLabUtil.testGitExecutable(project)) { 25 | return; 26 | } 27 | 28 | GitLabCreateMergeRequestWorker mergeRequestWorker = GitLabCreateMergeRequestWorker.create(project, file); 29 | if(mergeRequestWorker != null) { 30 | CreateMergeRequestDialog createMergeRequestDialog = new CreateMergeRequestDialog(project, mergeRequestWorker); 31 | DialogManager.show(createMergeRequestDialog); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /GitLabProjects.zip 2 | # Created by .ignore support plugin (hsz.mobi) 3 | ### JetBrains template 4 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 5 | 6 | .idea 7 | 8 | *.iml 9 | 10 | 11 | ## File-based project format: 12 | *.ipr 13 | *.iws 14 | 15 | ## Plugin-specific files: 16 | 17 | # IntelliJ 18 | /out/ 19 | 20 | # mpeltonen/sbt-idea plugin 21 | .idea_modules/ 22 | 23 | # JIRA plugin 24 | atlassian-ide-plugin.xml 25 | 26 | # Crashlytics plugin (for Android Studio and IntelliJ) 27 | com_crashlytics_export_strings.xml 28 | crashlytics.properties 29 | crashlytics-build.properties 30 | ### Java template 31 | *.class 32 | 33 | # Mobile Tools for Java (J2ME) 34 | .mtj.tmp/ 35 | 36 | # Package Files # 37 | *.war 38 | *.ear 39 | 40 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 41 | hs_err_pid* 42 | ### Gradle template 43 | .gradle 44 | /build/ 45 | 46 | # Ignore Gradle GUI config 47 | gradle-app.setting 48 | 49 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 50 | !gradle-wrapper.jar 51 | 52 | # Cache of project 53 | .gradletasknamecache 54 | 55 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 56 | # gradle/wrapper/gradle-wrapper.properties 57 | 58 | .gradle/ 59 | Would 60 | remove build/idea-sandbox/system/log/ 61 | build/ -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/SettingsDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import com.intellij.openapi.options.ConfigurationException; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.ui.DialogWrapper; 6 | import com.intellij.openapi.ui.ValidationInfo; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import javax.swing.*; 10 | 11 | import static com.ppolivka.gitlabprojects.configuration.SettingsView.DIALOG_TITLE; 12 | 13 | /** 14 | * Wrapper around settings view 15 | * 16 | * @author ppolivka 17 | * @since 17.11.2015 18 | */ 19 | public class SettingsDialog extends DialogWrapper { 20 | 21 | private SettingsView settingsView = new SettingsView(); 22 | 23 | public SettingsDialog(@Nullable Project project) { 24 | super(project); 25 | init(); 26 | } 27 | 28 | @Override 29 | protected void init() { 30 | super.init(); 31 | setTitle(DIALOG_TITLE); 32 | settingsView.setup(); 33 | } 34 | 35 | @Nullable 36 | @Override 37 | protected JComponent createCenterPanel() { 38 | return settingsView.createComponent(); 39 | } 40 | 41 | public boolean isModified() { 42 | return settingsView.isModified(); 43 | } 44 | 45 | public void apply() throws ConfigurationException { 46 | settingsView.apply(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/list/GitLabMergeRequestListDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/api/dto/ServerDto.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.api.dto; 2 | 3 | import java.util.Objects; 4 | 5 | public class ServerDto { 6 | 7 | private String host; 8 | private String token; 9 | private boolean defaultRemoveBranch; 10 | 11 | public String getHost() { 12 | return host; 13 | } 14 | 15 | public void setHost(String host) { 16 | this.host = host; 17 | } 18 | 19 | public String getToken() { 20 | return token; 21 | } 22 | 23 | public void setToken(String token) { 24 | this.token = token; 25 | } 26 | 27 | public boolean isDefaultRemoveBranch() { 28 | return defaultRemoveBranch; 29 | } 30 | 31 | public void setDefaultRemoveBranch(boolean defaultRemoveBranch) { 32 | this.defaultRemoveBranch = defaultRemoveBranch; 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | ServerDto serverDto = (ServerDto) o; 40 | return defaultRemoveBranch == serverDto.defaultRemoveBranch && 41 | Objects.equals(host, serverDto.host) && 42 | Objects.equals(token, serverDto.token); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | 48 | return Objects.hash(host, token, defaultRemoveBranch); 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return host; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/AddCommentDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/info/BranchInfo.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.info; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Class containing info about branch 9 | * 10 | * @author ppolivka 11 | * @since 31.10.2015 12 | */ 13 | public class BranchInfo { 14 | 15 | private String name; 16 | private String remoteName; 17 | private boolean remoteOnly = false; 18 | 19 | public BranchInfo(String name, String remoteName) { 20 | this(name, remoteName, false); 21 | } 22 | 23 | public BranchInfo(String name, String remoteName, boolean remoteOnly) { 24 | this.name = name; 25 | this.remoteName = remoteName; 26 | this.remoteOnly = remoteOnly; 27 | } 28 | 29 | public String getFullName() { 30 | return remoteOnly ? getFullRemoteName() : getName(); 31 | } 32 | 33 | public String getFullRemoteName() { 34 | return this.getRemoteName() + "/" + this.getName(); 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public void setName(String name) { 42 | this.name = name; 43 | } 44 | 45 | public String getRemoteName() { 46 | return remoteName; 47 | } 48 | 49 | public void setRemoteName(String remoteName) { 50 | this.remoteName = remoteName; 51 | } 52 | 53 | @Override 54 | public String toString() { 55 | return name; 56 | } 57 | 58 | @Override 59 | public boolean equals(Object o) { 60 | if (this == o) { 61 | return true; 62 | } 63 | if (!(o instanceof BranchInfo)) { 64 | return false; 65 | } 66 | BranchInfo that = (BranchInfo) o; 67 | return Objects.equals(name, that.name); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return Objects.hash(name); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/common/NoGitLabApiAction.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.common; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.CommonDataKeys; 5 | import com.intellij.openapi.project.DumbAwareAction; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.*; 12 | 13 | /** 14 | * Abstract Action class that provides method for validating GitLab API Settings 15 | * 16 | * @author ppolivka 17 | * @since 22.12.2015 18 | */ 19 | public abstract class NoGitLabApiAction extends DumbAwareAction { 20 | 21 | protected static SettingsState settingsState = SettingsState.getInstance(); 22 | 23 | protected Project project; 24 | protected VirtualFile file; 25 | 26 | public NoGitLabApiAction() { 27 | } 28 | 29 | public NoGitLabApiAction(@Nullable String text) { 30 | super(text); 31 | } 32 | 33 | public NoGitLabApiAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) { 34 | super(text, description, icon); 35 | } 36 | 37 | @Override 38 | public void actionPerformed(AnActionEvent anActionEvent) { 39 | project = anActionEvent.getData(CommonDataKeys.PROJECT); 40 | file = anActionEvent.getData(CommonDataKeys.VIRTUAL_FILE); 41 | 42 | if (project == null || project.isDisposed()) { 43 | return; 44 | } 45 | 46 | if(settingsState.getGitlabServers().size() == 0) { 47 | return; 48 | } 49 | 50 | apiValidAction(anActionEvent); 51 | } 52 | 53 | /** 54 | * Abstract method that is called after GitLab Api is validated, 55 | * we can assume that login credentials are there and api valid 56 | * 57 | * @param anActionEvent event information 58 | */ 59 | public abstract void apiValidAction(AnActionEvent anActionEvent); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/request/SearchableUsers.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.request; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.ppolivka.gitlabprojects.component.Searchable; 6 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 7 | import com.ppolivka.gitlabprojects.util.MessageUtil; 8 | import org.gitlab.api.models.GitlabProject; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.Collection; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | import static java.util.Collections.emptyList; 17 | 18 | /** 19 | * Searchable users model 20 | * 21 | * @author ppolivka 22 | * @since 1.4.0 23 | */ 24 | public class SearchableUsers implements Searchable { 25 | 26 | private Project project; 27 | private VirtualFile file; 28 | private GitlabProject gitlabProject; 29 | private Collection initialModel; 30 | private static SettingsState settingsState = SettingsState.getInstance(); 31 | 32 | public SearchableUsers(Project project, VirtualFile file, GitlabProject gitlabProject) { 33 | this.project = project; 34 | this.file = file; 35 | this.gitlabProject = gitlabProject; 36 | this.initialModel = search(""); 37 | } 38 | 39 | @Override 40 | public Collection search(String toSearch) { 41 | try { 42 | List users = settingsState.api(project, file).searchUsers(gitlabProject, toSearch).stream().map(SearchableUser::new).collect(Collectors.toList()); 43 | List resultingUsers = new ArrayList<>(); 44 | resultingUsers.addAll(users); 45 | return resultingUsers; 46 | } catch (IOException e) { 47 | MessageUtil.showErrorDialog(project, "New remote origin cannot be added to this project.", "Cannot Add New Remote"); 48 | } 49 | return emptyList(); 50 | } 51 | 52 | public Collection getInitialModel() { 53 | return initialModel; 54 | } 55 | 56 | public void setInitialModel(Collection initialModel) { 57 | this.initialModel = initialModel; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/GitLabCommentsListWorker.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.comment; 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.intellij.util.containers.Convertor; 7 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 8 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 9 | import org.gitlab.api.models.GitlabMergeRequest; 10 | import org.gitlab.api.models.GitlabNote; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | import java.io.IOException; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 18 | 19 | /** 20 | * Worker for extracting comments on merge request 21 | * 22 | * @author ppolivka 23 | * @since 1.3.2 24 | */ 25 | public class GitLabCommentsListWorker { 26 | 27 | static SettingsState settingsState = SettingsState.getInstance(); 28 | 29 | GitlabMergeRequest mergeRequest; 30 | List comments; 31 | VirtualFile file; 32 | 33 | public GitlabMergeRequest getMergeRequest() { 34 | return mergeRequest; 35 | } 36 | 37 | public void setMergeRequest(GitlabMergeRequest mergeRequest) { 38 | this.mergeRequest = mergeRequest; 39 | } 40 | 41 | public List getComments() { 42 | return comments; 43 | } 44 | 45 | public void setComments(List comments) { 46 | this.comments = comments; 47 | } 48 | 49 | public static GitLabCommentsListWorker create(@NotNull final Project project, @NotNull final GitlabMergeRequest mergeRequest, final VirtualFile file) { 50 | return GitLabUtil.computeValueInModal(project, "Loading comments...", (Convertor) indicator -> { 51 | GitLabCommentsListWorker commentsListWorker = new GitLabCommentsListWorker(); 52 | commentsListWorker.setMergeRequest(mergeRequest); 53 | try { 54 | commentsListWorker.setComments(settingsState.api(project, file).getMergeRequestComments(mergeRequest)); 55 | } catch (IOException e) { 56 | commentsListWorker.setComments(Collections.emptyList()); 57 | showErrorDialog(project, "Cannot load comments from GitLab API", "Cannot Load Comments"); 58 | } 59 | 60 | return commentsListWorker; 61 | }); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/test/java/com/ppolivka/gitlabprojects/util/GitLabUtilTest.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.util; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 5 | import org.junit.Assert; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | /** 10 | * Test for parsing gitlab urls 11 | * 12 | * @author ppolivka 13 | * @since 1.3.6 14 | */ 15 | public class GitLabUtilTest { 16 | 17 | 18 | private SettingsState settingsState = new SettingsState(); 19 | 20 | @Before 21 | public void setUp() throws Exception { 22 | ApplicationManager.setApplication(new DummyApplication(settingsState), new DummyDisposable()); 23 | } 24 | 25 | @Test 26 | public void isGitLabUrl() throws Exception { 27 | testUrl("https://gitlab.com/", "https://gitlab.com/Polivka/130regression.git", true); 28 | testUrl("https://gitlab.com", "https://gitlab.com/Polivka/130regression.git", true); 29 | testUrl("https://gitlab.com", "https://gitlab.com/Polivka/130regression", true); 30 | testUrl("https://gitlab.com:8080/", "https://gitlab.com:8080/Polivka/130regression.git", true); 31 | testUrl("https://gitlab.com:8080/", "https://gitlab.com:8084/Polivka/130regression.git", true); 32 | testUrl("https://gitlab.com:8080/", "http://gitlab.com:8084/Polivka/130regression.git", true); 33 | testUrl("http://gitlab.com:8080/", "https://gitlab.com:8084/Polivka/130regression.git", true); 34 | testUrl("https://gitlab.com/", "git@gitlab.com:Polivka/130regression.git", true); 35 | testUrl("https://gitlab.com:8080/", "git@gitlab.com:8080:Polivka/130regression.git", true); 36 | testUrl("https://gitlab.com:8080/", "git@gitlab.com:8084:Polivka/130regression.git", true); 37 | testUrl("https://gitlab-1.com:8080/", "git@gitlab-1.com:8084:Polivka/130regression.git", true); 38 | testUrl("https://gitlab-1.com:8080/", "git@gitlab-2.com:8084:Polivka/130regression.git", false); 39 | testUrl("http://gitlab-1.com:8080/", "git@gitlab-1.com:8084:Polivka/130regression.git", true); 40 | testUrl("http://gitlab-1.com/", "git@gitlab-1.com:Polivka/130regression.git", true); 41 | testUrl("http://192.168.1.10/", "git@192.168.1.10:Polivka/130regression.git", true); 42 | testUrl("http://192.168.1.10/", "http://192.168.1.10/Polivka/130regression.git", true); 43 | } 44 | 45 | private void testUrl(String settings, String remote, boolean shouldBe) { 46 | settingsState.setHost(settings); 47 | Assert.assertEquals(GitLabUtil.isGitLabUrl(settings, remote), shouldBe); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/helper/GitLabProjectMatcher.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.helper; 2 | 3 | import com.ppolivka.gitlabprojects.configuration.ProjectState; 4 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 5 | import com.ppolivka.gitlabprojects.exception.GitLabException; 6 | import git4idea.repo.GitRemote; 7 | import git4idea.repo.GitRepository; 8 | import org.apache.commons.lang.StringUtils; 9 | import org.gitlab.api.models.GitlabProject; 10 | 11 | import java.util.Collection; 12 | import java.util.Optional; 13 | 14 | public class GitLabProjectMatcher { 15 | 16 | private static SettingsState settingsState = SettingsState.getInstance(); 17 | 18 | public Optional resolveProject(ProjectState projectState, GitRemote remote, GitRepository repository) { 19 | String remoteProjectName = remote.getName(); 20 | String remoteUrl = remote.getFirstUrl(); 21 | 22 | if(projectState.getProjectId(remoteUrl) == null) { 23 | try { 24 | Collection projects = settingsState.api(repository).getProjects(); 25 | for (GitlabProject gitlabProject : projects) { 26 | if (gitlabProject.getName().toLowerCase().equals(remoteProjectName.toLowerCase()) || urlMatch(remoteUrl, gitlabProject.getSshUrl()) || urlMatch(remoteUrl, gitlabProject.getHttpUrl())) { 27 | Integer projectId = gitlabProject.getId(); 28 | projectState.setProjectId(remoteUrl, projectId); 29 | return Optional.of(gitlabProject); 30 | } 31 | } 32 | } catch (Throwable throwable) { 33 | throw new GitLabException("Cannot match project.", throwable); 34 | } 35 | } else { 36 | try { 37 | return Optional.of(settingsState.api(repository).getProject(projectState.getProjectId(remoteUrl))); 38 | } catch (Exception e) { 39 | projectState.setProjectId(remoteUrl, null); 40 | } 41 | } 42 | 43 | return Optional.empty(); 44 | } 45 | 46 | private boolean urlMatch(String remoteUrl, String apiUrl) { 47 | String formattedRemoteUrl = remoteUrl.trim(); 48 | String formattedApiUrl = apiUrl.trim(); 49 | formattedRemoteUrl = formattedRemoteUrl.replace("https://", ""); 50 | formattedRemoteUrl = formattedRemoteUrl.replace("http://", ""); 51 | return StringUtils.isNotBlank(formattedApiUrl) && StringUtils.isNotBlank(formattedRemoteUrl) && formattedApiUrl.toLowerCase().contains(formattedRemoteUrl.toLowerCase()); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/AddCommentDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.comment; 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.ui.DialogWrapper; 6 | import com.intellij.openapi.ui.ValidationInfo; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.util.containers.Convertor; 9 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 10 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 11 | import org.apache.commons.lang.StringUtils; 12 | import org.gitlab.api.models.GitlabMergeRequest; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import javax.swing.*; 17 | import java.io.IOException; 18 | 19 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 20 | 21 | /** 22 | * Dialog for adding comments 23 | * 24 | * @author ppolivka 25 | * @since 1.3.2 26 | */ 27 | public class AddCommentDialog extends DialogWrapper { 28 | 29 | static SettingsState settingsState = SettingsState.getInstance(); 30 | 31 | private JPanel panel; 32 | private JTextArea commentText; 33 | 34 | private Project project; 35 | private VirtualFile file; 36 | private GitlabMergeRequest mergeRequest; 37 | 38 | protected AddCommentDialog(@Nullable Project project, @NotNull GitlabMergeRequest mergeRequest, VirtualFile file) { 39 | super(project); 40 | this.project = project; 41 | this.mergeRequest = mergeRequest; 42 | init(); 43 | } 44 | 45 | @Override 46 | protected void init() { 47 | super.init(); 48 | setTitle("Add Comment"); 49 | setOKButtonText("Add"); 50 | } 51 | 52 | @Override 53 | protected void doOKAction() { 54 | super.doOKAction(); 55 | GitLabUtil.computeValueInModal(project, "Adding comment...", (Convertor) indicator -> { 56 | String comment = commentText.getText(); 57 | if (StringUtils.isNotBlank(comment)) { 58 | try { 59 | settingsState.api(project, file).addComment(mergeRequest, comment); 60 | } catch (IOException e) { 61 | showErrorDialog(project, "Cannot add comment.", "Cannot Add Comment"); 62 | } 63 | } 64 | return null; 65 | }); 66 | } 67 | 68 | @Nullable 69 | @Override 70 | protected ValidationInfo doValidate() { 71 | if(StringUtils.isBlank(commentText.getText())) { 72 | return new ValidationInfo("Comment text cannot be empty.", commentText); 73 | } 74 | return super.doValidate(); 75 | } 76 | 77 | @Nullable 78 | @Override 79 | protected JComponent createCenterPanel() { 80 | return panel; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/CommentsDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/ProjectState.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import com.intellij.openapi.components.*; 4 | import com.intellij.openapi.project.Project; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Project specific setting 13 | * 14 | * @author ppolivka 15 | * @since 30.10.2015 16 | */ 17 | @State( 18 | name = "GitlabProjectsProjectSettings", 19 | storages = { 20 | @Storage(file = StoragePathMacros.WORKSPACE_FILE) 21 | } 22 | ) 23 | public class ProjectState implements PersistentStateComponent { 24 | 25 | private State projectState = new State(); 26 | 27 | public static ProjectState getInstance(@NotNull Project project) { 28 | return ServiceManager.getService(project, ProjectState.class); 29 | } 30 | 31 | @Nullable 32 | @Override 33 | public State getState() { 34 | return projectState; 35 | } 36 | 37 | @Override 38 | public void loadState(State state) { 39 | projectState = state; 40 | } 41 | 42 | public static class State { 43 | public String lastMergedBranch; 44 | public Boolean deleteMergedBranch; 45 | public Boolean mergeAsWorkInProgress; 46 | public Map projectIdMap = new HashMap(); 47 | } 48 | 49 | public Integer getProjectId(String gitRepository) { 50 | if(projectState.projectIdMap != null) { 51 | return projectState.projectIdMap.get(gitRepository.hashCode()); 52 | } 53 | return null; 54 | } 55 | 56 | public void setProjectId(String gitRepository, Integer projectId) { 57 | if(projectState.projectIdMap == null) { 58 | projectState.projectIdMap = new HashMap<>(); 59 | } 60 | projectState.projectIdMap.put(gitRepository.hashCode(), projectId); 61 | } 62 | 63 | public String getLastMergedBranch() { 64 | return projectState.lastMergedBranch; 65 | } 66 | 67 | public void setLastMergedBranch(String lastMergedBranch) { 68 | projectState.lastMergedBranch = lastMergedBranch; 69 | } 70 | 71 | public Boolean getDeleteMergedBranch() { 72 | return projectState.deleteMergedBranch; 73 | } 74 | 75 | public void setDeleteMergedBranch(Boolean deleteMergedBranch) { 76 | projectState.deleteMergedBranch = deleteMergedBranch; 77 | } 78 | 79 | public Boolean getMergeAsWorkInProgress() { 80 | return projectState.mergeAsWorkInProgress; 81 | } 82 | 83 | public void setMergeAsWorkInProgress(Boolean mergeAsWorkInProgress) { 84 | projectState.mergeAsWorkInProgress = mergeAsWorkInProgress; 85 | } 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/checkout/GitLabRepositoryHostingService.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.checkout; 2 | 3 | import com.intellij.dvcs.hosting.RepositoryListLoader; 4 | import com.intellij.dvcs.hosting.RepositoryListLoadingException; 5 | import com.intellij.openapi.progress.ProgressIndicator; 6 | import com.intellij.openapi.project.Project; 7 | import com.ppolivka.gitlabprojects.api.dto.ProjectDto; 8 | import com.ppolivka.gitlabprojects.configuration.SettingsDialog; 9 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 10 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 11 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 12 | import git4idea.DialogManager; 13 | import git4idea.remote.GitRepositoryHostingService; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.io.IOException; 17 | import java.util.*; 18 | 19 | public class GitLabRepositoryHostingService extends GitRepositoryHostingService { 20 | @NotNull 21 | @Override 22 | public String getServiceDisplayName() { 23 | return "GitLab"; 24 | } 25 | 26 | @NotNull 27 | @Override 28 | public RepositoryListLoader getRepositoryListLoader(@NotNull Project project) { 29 | return new RepositoryListLoader() { 30 | 31 | private SettingsState settingsState = SettingsState.getInstance(); 32 | 33 | @Override 34 | public boolean isEnabled() { 35 | return settingsState.isEnabled(); 36 | } 37 | 38 | @Override 39 | public boolean enable() { 40 | SettingsDialog settingsDialog = new SettingsDialog(project); 41 | DialogManager.show(settingsDialog); 42 | return isEnabled(); 43 | } 44 | 45 | @NotNull 46 | @Override 47 | public List getAvailableRepositories(@NotNull ProgressIndicator progressIndicator) throws RepositoryListLoadingException { 48 | try { 49 | List repos = new ArrayList<>(); 50 | GitLabUtil.runInterruptable(progressIndicator, () -> { 51 | try { 52 | return settingsState.loadMapOfServersAndProjects(settingsState.getGitlabServers()); 53 | } catch (Throwable throwable) { 54 | throwable.printStackTrace(); 55 | } 56 | return new HashMap>(); 57 | }).forEach((server, projects) -> { 58 | if(GitlabServer.CheckoutType.SSH.equals(server.getPreferredConnection())) { 59 | projects.forEach(project -> repos.add(project.getSshUrl())); 60 | } else { 61 | projects.forEach(project -> repos.add(project.getHttpUrl())); 62 | } 63 | }); 64 | return repos; 65 | } catch (IOException e) { 66 | return Collections.emptyList(); 67 | } 68 | } 69 | }; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/CommentsDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.comment; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.ppolivka.gitlabprojects.common.ReadOnlyTableModel; 7 | import git4idea.DialogManager; 8 | import org.gitlab.api.models.GitlabNote; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.*; 12 | import javax.swing.table.TableModel; 13 | import java.awt.event.MouseAdapter; 14 | import java.awt.event.MouseEvent; 15 | import java.util.Date; 16 | import java.util.List; 17 | 18 | /** 19 | * Dialog for listing comments 20 | * 21 | * @author ppolivka 22 | * @since 1.3.2 23 | */ 24 | public class CommentsDialog extends DialogWrapper { 25 | 26 | private JPanel panel; 27 | private JTable comments; 28 | private JButton addCommentButton; 29 | 30 | private Project project; 31 | private VirtualFile file; 32 | private GitLabCommentsListWorker worker; 33 | 34 | public CommentsDialog(@Nullable Project project, GitLabCommentsListWorker worker, VirtualFile file) { 35 | super(project); 36 | this.project = project; 37 | this.worker = worker; 38 | this.file = file; 39 | init(); 40 | } 41 | 42 | @Override 43 | protected void init() { 44 | super.init(); 45 | 46 | setTitle("Comments"); 47 | 48 | reloadModel(); 49 | 50 | comments.addMouseListener(new MouseAdapter() { 51 | @Override 52 | public void mousePressed(MouseEvent me) { 53 | if (me.getClickCount() == 2) { 54 | String name = (String) comments.getValueAt(comments.getSelectedRow(), 0); 55 | Date date = (Date) comments.getValueAt(comments.getSelectedRow(), 1); 56 | String body = (String) comments.getValueAt(comments.getSelectedRow(), 2); 57 | DialogManager.show(new CommentDetail(project, name, date, body)); 58 | } 59 | } 60 | }); 61 | 62 | addCommentButton.addActionListener(e -> { 63 | new AddCommentDialog(project, worker.getMergeRequest(), file).show(); 64 | this.worker = GitLabCommentsListWorker.create(project, worker.getMergeRequest(), file); 65 | reloadModel(); 66 | comments.repaint(); 67 | }); 68 | 69 | } 70 | 71 | private void reloadModel() { 72 | comments.setModel(commentsModel(worker.getComments())); 73 | comments.getColumnModel().getColumn(0).setPreferredWidth(100); 74 | comments.getColumnModel().getColumn(1).setPreferredWidth(150); 75 | comments.getColumnModel().getColumn(2).setPreferredWidth(400); 76 | comments.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 77 | } 78 | 79 | @Nullable 80 | @Override 81 | protected JComponent createCenterPanel() { 82 | return panel; 83 | } 84 | 85 | private TableModel commentsModel(List notes) { 86 | Object[] columnNames = {"Author", "Date", "Text"}; 87 | Object[][] data = new Object[notes.size()][columnNames.length]; 88 | int i = 0; 89 | notes.sort((o1, o2) -> o2.getCreatedAt().compareTo(o1.getCreatedAt())); 90 | for(GitlabNote mergeRequest : notes) { 91 | Object[] row = new Object[columnNames.length]; 92 | row[0] = mergeRequest.getAuthor().getName(); 93 | row[1] = mergeRequest.getCreatedAt(); 94 | row[2] = mergeRequest.getBody(); 95 | data[i] = row; 96 | i++; 97 | } 98 | return new ReadOnlyTableModel(data, columnNames); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/comment/CommentDetail.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.ppolivka.gitlabprojects 3 | GitLab Projects 4 | 2.0.1 5 | Pavel Polivka 6 | 7 | GitLab Projects Plugin 9 |

Simple plugin that is adding support for GitLab specific actions to JetBrain IDEs

10 |

Features:

11 |
    12 |
  • GitLab Checkout support - add GitLab autocompleter to IDE Git checkout dialog
  • 13 |
  • GitLab Share dialog - allows quick import of new projects to GitLab, user can specify namespace and project visibility
  • 14 |
  • GitLab Merge Request dialog - user can quickly create new merge requests from current branch
  • 15 |
  • GitLab Merge Request List dialog - user can list and accept all open code reviews
  • 16 |
17 | ]]>
18 | 19 | Bugfixing release
21 | List only active projects.
22 | Changes to the UI of the setting dialog to make it more clear.
23 | Wording unification with GitHub integration.
24 | ]]> 25 |
26 | 27 | 28 | 29 | com.intellij.modules.platform 30 | com.intellij.modules.vcs 31 | 32 | 33 | 34 | 35 | 37 | 38 | 39 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | Git4Idea 72 | 73 |
74 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/list/GitLabMergeRequestListDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.list; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.ppolivka.gitlabprojects.common.ReadOnlyTableModel; 7 | import org.gitlab.api.models.GitlabMergeRequest; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.*; 12 | import javax.swing.table.TableModel; 13 | import java.util.List; 14 | 15 | /** 16 | * Dialog that is listing all active merge request in git lab repo 17 | * 18 | * @author ppolivka 19 | * @since 31.10.2015 20 | */ 21 | public class GitLabMergeRequestListDialog extends DialogWrapper { 22 | 23 | private JPanel mainView; 24 | private JTable listOfRequests; 25 | 26 | private Project project; 27 | private VirtualFile file; 28 | 29 | final GitLabMergeRequestListWorker mergeRequestListWorker; 30 | 31 | public GitLabMergeRequestListDialog(@Nullable Project project, @NotNull GitLabMergeRequestListWorker mergeRequestListWorker, VirtualFile file) { 32 | super(project); 33 | this.project = project; 34 | this.mergeRequestListWorker = mergeRequestListWorker; 35 | this.file = file; 36 | init(); 37 | } 38 | 39 | @Override 40 | protected void init() { 41 | super.init(); 42 | setTitle("List of Merge Requests"); 43 | 44 | setOKActionEnabled(false); 45 | setOKButtonText("Code Review"); 46 | setHorizontalStretch(2); 47 | 48 | List mergeRequests = mergeRequestListWorker.getMergeRequests(); 49 | listOfRequests.setModel(mergeRequestModel(mergeRequests)); 50 | listOfRequests.getColumnModel().getColumn(0).setPreferredWidth(200); 51 | listOfRequests.getColumnModel().getColumn(5).setWidth(0); 52 | listOfRequests.getColumnModel().getColumn(5).setMinWidth(0); 53 | listOfRequests.getColumnModel().getColumn(5).setMaxWidth(0); 54 | listOfRequests.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 55 | 56 | listOfRequests.getSelectionModel().addListSelectionListener(event -> setOKActionEnabled(true)); 57 | } 58 | 59 | @Override 60 | protected void doOKAction() { 61 | GitlabMergeRequest mergeRequest = 62 | (GitlabMergeRequest) listOfRequests.getValueAt(listOfRequests.getSelectedRow(), 5); 63 | CodeReviewDialog codeReviewDialog = new CodeReviewDialog(project, mergeRequest, mergeRequestListWorker, file); 64 | codeReviewDialog.show(); 65 | if (codeReviewDialog.isOK()) { 66 | super.doOKAction(); 67 | } 68 | } 69 | 70 | private TableModel mergeRequestModel(List mergeRequests) { 71 | Object[] columnNames = {"Merge request", "Author", "Source", "Target", "Assignee", ""}; 72 | Object[][] data = new Object[mergeRequests.size()][columnNames.length]; 73 | int i = 0; 74 | for (GitlabMergeRequest mergeRequest : mergeRequests) { 75 | Object[] row = new Object[columnNames.length]; 76 | row[0] = mergeRequest.getTitle(); 77 | row[1] = mergeRequest.getAuthor().getName(); 78 | row[2] = mergeRequest.getSourceBranch(); 79 | row[3] = mergeRequest.getTargetBranch(); 80 | String assignee = ""; 81 | if (mergeRequest.getAssignee() != null) { 82 | assignee = mergeRequest.getAssignee().getName(); 83 | } 84 | row[4] = assignee; 85 | row[5] = mergeRequest; 86 | data[i] = row; 87 | i++; 88 | } 89 | return new ReadOnlyTableModel(data, columnNames); 90 | } 91 | 92 | @Nullable 93 | @Override 94 | protected JComponent createCenterPanel() { 95 | return mainView; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/common/GitLabApiAction.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.common; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.actionSystem.CommonDataKeys; 5 | import com.intellij.openapi.options.ConfigurationException; 6 | import com.intellij.openapi.progress.ProgressIndicator; 7 | import com.intellij.openapi.project.DumbAwareAction; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.util.containers.Convertor; 11 | import com.ppolivka.gitlabprojects.configuration.SettingsDialog; 12 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 13 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import javax.swing.*; 18 | 19 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 20 | 21 | /** 22 | * Abstract Action class that provides method for validating GitLab API Settings 23 | * 24 | * @author ppolivka 25 | * @since 22.12.2015 26 | */ 27 | public abstract class GitLabApiAction extends DumbAwareAction { 28 | 29 | protected static SettingsState settingsState = SettingsState.getInstance(); 30 | 31 | protected Project project; 32 | protected VirtualFile file; 33 | 34 | public GitLabApiAction() { 35 | } 36 | 37 | public GitLabApiAction(@Nullable String text) { 38 | super(text); 39 | } 40 | 41 | public GitLabApiAction(@Nullable String text, @Nullable String description, @Nullable Icon icon) { 42 | super(text, description, icon); 43 | } 44 | 45 | @Override 46 | public void actionPerformed(AnActionEvent anActionEvent) { 47 | project = anActionEvent.getData(CommonDataKeys.PROJECT); 48 | file = anActionEvent.getData(CommonDataKeys.VIRTUAL_FILE); 49 | 50 | if (project == null || project.isDisposed()) { 51 | return; 52 | } 53 | 54 | if(!validateGitLabApi(project, file)) { 55 | return; 56 | } 57 | 58 | apiValidAction(anActionEvent); 59 | } 60 | 61 | /** 62 | * Abstract method that is called after GitLab Api is validated, 63 | * we can assume that login credentials are there and api valid 64 | * 65 | * @param anActionEvent event information 66 | */ 67 | public abstract void apiValidAction(AnActionEvent anActionEvent); 68 | 69 | /** 70 | * Validate git lab api settings 71 | * If API is not valid, Setting dialog will be displayed 72 | * If API is still not configured after that false is returned 73 | * 74 | * @param project the project 75 | * @return true if API is OK, false if not 76 | */ 77 | public static boolean validateGitLabApi(@NotNull Project project, VirtualFile virtualFile) { 78 | Boolean isApiSetup = GitLabUtil.computeValueInModal(project, "Validating GitLab Api...",false, new Convertor() { 79 | @Override 80 | public Boolean convert(ProgressIndicator progressIndicator) { 81 | try { 82 | settingsState.isApiValid(project, virtualFile); 83 | return true; 84 | } catch (Throwable e) { 85 | return false; 86 | } 87 | } 88 | }); 89 | boolean isOk = true; 90 | if (!isApiSetup) { 91 | //Git Lab Not configured 92 | SettingsDialog configurationDialog = new SettingsDialog(project); 93 | configurationDialog.show(); 94 | if (configurationDialog.isOK() && configurationDialog.isModified()) { 95 | try { 96 | configurationDialog.apply(); 97 | } catch (ConfigurationException ignored) { 98 | isOk = false; 99 | } 100 | } 101 | if(isOk) { 102 | isOk = configurationDialog.isOK(); 103 | } 104 | } 105 | if(!isOk) { 106 | showErrorDialog(project, "Cannot log-in to GitLab Server with provided token", "Cannot Login To GitLab"); 107 | } 108 | return isOk; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/GitLabMergeRequestWorker.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge; 2 | 3 | import com.intellij.openapi.components.ServiceManager; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.util.Pair; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.ppolivka.gitlabprojects.configuration.ProjectState; 8 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 9 | import com.ppolivka.gitlabprojects.exception.MergeRequestException; 10 | import com.ppolivka.gitlabprojects.merge.helper.GitLabProjectMatcher; 11 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 12 | import git4idea.commands.Git; 13 | import git4idea.repo.GitRemote; 14 | import git4idea.repo.GitRepository; 15 | import org.gitlab.api.models.GitlabProject; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | import java.util.Optional; 20 | 21 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 22 | 23 | /** 24 | * Interface for worker classes that are related to merge requests 25 | * 26 | * @author ppolivka 27 | * @since 31.10.2015 28 | */ 29 | public interface GitLabMergeRequestWorker { 30 | 31 | String CANNOT_CREATE_MERGE_REQUEST = "Cannot Create Merge Request"; 32 | 33 | Git getGit(); 34 | 35 | Project getProject(); 36 | 37 | ProjectState getProjectState(); 38 | 39 | GitRepository getGitRepository(); 40 | 41 | String getRemoteUrl(); 42 | 43 | GitlabProject getGitlabProject(); 44 | 45 | String getRemoteProjectName(); 46 | 47 | GitLabDiffViewWorker getDiffViewWorker(); 48 | 49 | void setGit(Git git); 50 | 51 | void setProject(Project project); 52 | 53 | void setProjectState(ProjectState projectState); 54 | 55 | void setGitRepository(GitRepository gitRepository); 56 | 57 | void setRemoteUrl(String remoteUrl); 58 | 59 | void setGitlabProject(GitlabProject gitlabProject); 60 | 61 | void setRemoteProjectName(String remoteProjectName); 62 | 63 | void setDiffViewWorker(GitLabDiffViewWorker diffViewWorker); 64 | 65 | class Util { 66 | 67 | private static SettingsState settingsState = SettingsState.getInstance(); 68 | private static GitLabProjectMatcher projectMatcher = new GitLabProjectMatcher(); 69 | 70 | public static void fillRequiredInfo(@NotNull final GitLabMergeRequestWorker mergeRequestWorker, @NotNull final Project project, @Nullable final VirtualFile file) throws MergeRequestException { 71 | ProjectState projectState = ProjectState.getInstance(project); 72 | mergeRequestWorker.setProjectState(projectState); 73 | 74 | mergeRequestWorker.setProject(project); 75 | 76 | Git git = ServiceManager.getService(Git.class); 77 | mergeRequestWorker.setGit(git); 78 | 79 | GitRepository gitRepository = GitLabUtil.getGitRepository(project, file); 80 | if (gitRepository == null) { 81 | showErrorDialog(project, "Can't find git repository", CANNOT_CREATE_MERGE_REQUEST); 82 | throw new MergeRequestException(); 83 | } 84 | gitRepository.update(); 85 | mergeRequestWorker.setGitRepository(gitRepository); 86 | 87 | Pair remote = GitLabUtil.findGitLabRemote(gitRepository); 88 | if (remote == null) { 89 | showErrorDialog(project, "Can't find GitLab remote", CANNOT_CREATE_MERGE_REQUEST); 90 | throw new MergeRequestException(); 91 | } 92 | 93 | String remoteProjectName = remote.first.getName(); 94 | mergeRequestWorker.setRemoteProjectName(remoteProjectName); 95 | mergeRequestWorker.setRemoteUrl(remote.getSecond()); 96 | 97 | try { 98 | Integer projectId; 99 | Optional gitlabProject = projectMatcher.resolveProject(projectState, remote.getFirst(), gitRepository); 100 | projectId = gitlabProject.orElseThrow(() -> new RuntimeException("No project found")).getId(); 101 | 102 | mergeRequestWorker.setGitlabProject(settingsState.api(gitRepository).getProject(projectId)); 103 | } catch (Exception e) { 104 | showErrorDialog(project, "Cannot find this project in GitLab Remote", CANNOT_CREATE_MERGE_REQUEST); 105 | throw new MergeRequestException(e); 106 | } 107 | 108 | mergeRequestWorker.setDiffViewWorker(new GitLabDiffViewWorker(project, mergeRequestWorker.getGitRepository())); 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/SettingsView.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 |
94 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/component/SearchBoxModel.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.component; 2 | 3 | import com.ppolivka.gitlabprojects.merge.request.EmptyUser; 4 | import com.ppolivka.gitlabprojects.merge.request.SearchableUser; 5 | import com.ppolivka.gitlabprojects.merge.request.SearchableUsers; 6 | 7 | import javax.swing.*; 8 | import java.awt.event.ItemEvent; 9 | import java.awt.event.ItemListener; 10 | import java.awt.event.KeyEvent; 11 | import java.awt.event.KeyListener; 12 | import java.util.*; 13 | import java.util.Timer; 14 | 15 | /** 16 | * Searchable ComboBox model with autocomplete and background loading 17 | * 18 | * @author ppolivka 19 | * @since 1.4.0 20 | */ 21 | public class SearchBoxModel extends AbstractListModel implements ComboBoxModel, KeyListener, ItemListener { 22 | private JComboBox comboBox; 23 | private transient ComboBoxEditor comboBoxEditor; 24 | private SearchableUsers searchableUsers; 25 | 26 | private List data = new ArrayList<>(); 27 | private SearchableUser selectedUser = null; 28 | 29 | private volatile long lastKeyPressTime = 0L; 30 | private transient Timer timer; 31 | 32 | private String lastQuery = ""; 33 | 34 | public SearchBoxModel(JComboBox comboBox, SearchableUsers searchableUsers) { 35 | this.comboBox = comboBox; 36 | this.comboBoxEditor = comboBox.getEditor(); 37 | this.comboBoxEditor.getEditorComponent().addKeyListener(this); 38 | this.searchableUsers = searchableUsers; 39 | this.data.addAll(searchableUsers.getInitialModel()); 40 | timer = new Timer(); 41 | } 42 | 43 | private void updateModel(String in) { 44 | timer.schedule(new TimerTask() { 45 | @Override 46 | public void run() { 47 | SwingUtilities.invokeLater(() -> { 48 | if((System.currentTimeMillis() - lastKeyPressTime) > 200) { 49 | if(in != null && !"".equals(in) && !in.equals(lastQuery)) { 50 | data.clear(); 51 | data = Arrays.asList(new EmptyUser(in), new EmptyUser("loading...")); 52 | dataChanged(); 53 | data = new ArrayList<>(); 54 | data.add(new EmptyUser(in)); 55 | data.addAll(searchableUsers.search(in)); 56 | lastQuery = in; 57 | dataChanged(); 58 | } else if (in == null || "".equals(in)){ 59 | data.clear(); 60 | data.addAll(searchableUsers.getInitialModel()); 61 | dataChanged(); 62 | } 63 | } 64 | }); 65 | } 66 | }, 200); 67 | } 68 | 69 | private void dataChanged() { 70 | super.fireContentsChanged(this, 0, data.size()); 71 | comboBox.hidePopup(); 72 | comboBox.showPopup(); 73 | if (!data.isEmpty()) { 74 | comboBox.setSelectedIndex(0); 75 | } 76 | } 77 | 78 | @Override 79 | public void itemStateChanged(ItemEvent e) { 80 | comboBoxEditor.setItem(e.getItem()); //to string was here 81 | comboBox.setSelectedItem(e.getItem()); 82 | } 83 | 84 | @Override 85 | public void keyTyped(KeyEvent e) { 86 | //noop 87 | } 88 | 89 | @Override 90 | public void keyPressed(KeyEvent e) { 91 | lastKeyPressTime = System.currentTimeMillis(); 92 | } 93 | 94 | @Override 95 | public void keyReleased(KeyEvent e) { 96 | String str = comboBoxEditor.getItem().toString(); 97 | JTextField jtf = (JTextField) comboBoxEditor.getEditorComponent(); 98 | int currentPosition = jtf.getCaretPosition(); 99 | 100 | if (e.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { 101 | if (e.getKeyCode() != KeyEvent.VK_ENTER) { 102 | comboBoxEditor.setItem(str); 103 | jtf.setCaretPosition(currentPosition); 104 | } 105 | } else if (e.getKeyCode() == KeyEvent.VK_ENTER) { 106 | comboBox.setSelectedIndex(comboBox.getSelectedIndex()); 107 | } else { 108 | updateModel(comboBox.getEditor().getItem().toString()); 109 | comboBoxEditor.setItem(str); 110 | jtf.setCaretPosition(currentPosition); 111 | } 112 | } 113 | 114 | @Override 115 | public void setSelectedItem(Object anItem) { 116 | if(anItem instanceof SearchableUser) { 117 | this.selectedUser = (SearchableUser) anItem; 118 | } 119 | } 120 | 121 | @Override 122 | public Object getSelectedItem() { 123 | return selectedUser; 124 | } 125 | 126 | @Override 127 | public int getSize() { 128 | return data.size(); 129 | } 130 | 131 | @Override 132 | public Object getElementAt(int index) { 133 | return data.get(index); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/resources/icons/gitLabLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo-square 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/test/java/com/ppolivka/gitlabprojects/merge/helper/GitLabProjectMatcherTest.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.helper; 2 | 3 | import com.intellij.openapi.application.ApplicationManager; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import com.ppolivka.gitlabprojects.api.ApiFacade; 7 | import com.ppolivka.gitlabprojects.api.dto.ServerDto; 8 | import com.ppolivka.gitlabprojects.configuration.ProjectState; 9 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 10 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 11 | import com.ppolivka.gitlabprojects.util.DummyApplication; 12 | import com.ppolivka.gitlabprojects.util.DummyDisposable; 13 | import git4idea.repo.GitRemote; 14 | import git4idea.repo.GitRepository; 15 | import org.gitlab.api.models.GitlabProject; 16 | import org.junit.Assert; 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | 20 | import java.io.IOException; 21 | import java.util.*; 22 | 23 | import static java.util.Collections.emptyList; 24 | 25 | public class GitLabProjectMatcherTest { 26 | 27 | GitLabProjectMatcher gitLabProjectMatcher; 28 | 29 | DummySettingState settingsState; 30 | 31 | @Before 32 | public void setUp() throws Exception { 33 | settingsState = new DummySettingState(); 34 | ApplicationManager.setApplication(new DummyApplication(settingsState), new DummyDisposable()); 35 | gitLabProjectMatcher = new GitLabProjectMatcher(); 36 | } 37 | 38 | @Test 39 | public void resolveProjectValidHttp() throws Exception { 40 | testResolving("https://gitlab.com/Polivka/kugkg.git", "https://gitlab.com/Polivka/kugkg.git", false, true); 41 | } 42 | 43 | @Test 44 | public void resolveProjectValidHttpMixup() throws Exception { 45 | testResolving("https://gitlab.com/Polivka/kugkg.git", "http://gitlab.com/Polivka/kugkg.git", false, true); 46 | } 47 | 48 | @Test 49 | public void resolveProjectNotResolvingSsh() throws Exception { 50 | testResolving("git@gitlab1.com:Polivka/kugkg", "git@gitlab2.com:Polivka/kugkg.git", true, false); 51 | } 52 | 53 | @Test 54 | public void resolveProjectNotResolvingHttp() throws Exception { 55 | testResolving("https://gitlab1.com/Polivka/kugkg", "https://gitlab2.com/Polivka/kugkg.git", false, false); 56 | } 57 | 58 | @Test 59 | public void resolveProjectMissingSuffixHttp() throws Exception { 60 | testResolving("https://gitlab.com/Polivka/kugkg", "https://gitlab.com/Polivka/kugkg.git", false, true); 61 | } 62 | 63 | private void testResolving(String remoteUrl, String projectUrl, boolean ssh, boolean shouldBeResolved) { 64 | ProjectState projectState = new ProjectState(); 65 | GitRemote remote = remote("origin", remoteUrl); 66 | GitlabProject project = project(1, projectUrl, ssh); 67 | settingsState.addProject(1, project); 68 | Optional resolvedProject = gitLabProjectMatcher.resolveProject(projectState, remote, null); 69 | Assert.assertNotNull(resolvedProject); 70 | Assert.assertEquals(shouldBeResolved, resolvedProject.isPresent()); 71 | } 72 | 73 | 74 | private GitlabProject project(Integer id, String url, boolean ssh) { 75 | GitlabProject project = new GitlabProject(); 76 | project.setId(id); 77 | project.setName(""); 78 | if(ssh) { 79 | project.setHttpUrl(""); 80 | project.setSshUrl(url); 81 | } else { 82 | project.setHttpUrl(url); 83 | project.setSshUrl(""); 84 | } 85 | return project; 86 | } 87 | 88 | private GitRemote remote(String name, String url) { 89 | return new GitRemote(name, Arrays.asList(url), emptyList(), emptyList(), emptyList()); 90 | } 91 | 92 | private class DummyApiFacade extends ApiFacade { 93 | private Map projects = new HashMap<>(); 94 | 95 | public void addProject(Integer id, GitlabProject project) { 96 | projects.put(id, project); 97 | } 98 | 99 | @Override 100 | public GitlabProject getProject(Integer id) throws IOException { 101 | return projects.get(id); 102 | } 103 | 104 | @Override 105 | public Collection getProjects() throws Throwable { 106 | return projects.values(); 107 | } 108 | } 109 | 110 | private class DummySettingState extends SettingsState { 111 | DummyApiFacade apiFacade = new DummyApiFacade(); 112 | 113 | @Override 114 | public ApiFacade api(Project project, VirtualFile file) { 115 | return apiFacade; 116 | } 117 | 118 | @Override 119 | public ApiFacade api(GitRepository gitRepository) { 120 | return apiFacade; 121 | } 122 | 123 | @Override 124 | public ApiFacade api(GitlabServer serverDto) { 125 | return apiFacade; 126 | } 127 | 128 | public void addProject(Integer id, GitlabProject project) { 129 | apiFacade.addProject(id, project); 130 | } 131 | } 132 | 133 | } -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/list/CodeReviewDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/SettingsView.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import com.intellij.openapi.options.ConfigurationException; 4 | import com.intellij.openapi.options.SearchableConfigurable; 5 | import com.intellij.openapi.ui.ValidationInfo; 6 | import com.ppolivka.gitlabprojects.api.dto.ServerDto; 7 | import com.ppolivka.gitlabprojects.common.ReadOnlyTableModel; 8 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 9 | import git4idea.DialogManager; 10 | import org.apache.commons.lang.StringUtils; 11 | import org.jetbrains.annotations.Nls; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | import javax.swing.*; 16 | import javax.swing.event.DocumentEvent; 17 | import javax.swing.event.DocumentListener; 18 | import javax.swing.table.TableModel; 19 | import java.awt.*; 20 | import java.awt.event.ActionEvent; 21 | import java.awt.event.ActionListener; 22 | import java.io.IOException; 23 | import java.net.URI; 24 | import java.net.UnknownHostException; 25 | import java.util.Collection; 26 | import java.util.concurrent.ExecutorService; 27 | import java.util.concurrent.Executors; 28 | import java.util.concurrent.Future; 29 | import java.util.concurrent.TimeUnit; 30 | import java.util.regex.Matcher; 31 | import java.util.regex.Pattern; 32 | 33 | import static org.apache.commons.lang.StringUtils.isNotBlank; 34 | 35 | /** 36 | * Dialog for GitLab setting configuration 37 | * 38 | * @author ppolivka 39 | * @since 27.10.2015 40 | */ 41 | public class SettingsView implements SearchableConfigurable { 42 | 43 | public static final String DIALOG_TITLE = "GitLab Settings"; 44 | SettingsState settingsState = SettingsState.getInstance(); 45 | 46 | private JPanel mainPanel; 47 | private JTable serverTable; 48 | private JButton addNewOneButton; 49 | private JButton editButton; 50 | private JButton deleteButton; 51 | 52 | public void setup() { 53 | addNewOneButton.addActionListener(e -> { 54 | ServerConfiguration serverConfiguration = new ServerConfiguration(null); 55 | DialogManager.show(serverConfiguration); 56 | reset(); 57 | }); 58 | deleteButton.addActionListener(e -> { 59 | GitlabServer server = getSelectedServer(); 60 | if(server != null) { 61 | settingsState.deleteServer(server); 62 | reset(); 63 | } 64 | }); 65 | editButton.addActionListener(e -> { 66 | GitlabServer server = getSelectedServer(); 67 | ServerConfiguration serverConfiguration = new ServerConfiguration(server); 68 | DialogManager.show(serverConfiguration); 69 | reset(); 70 | }); 71 | } 72 | 73 | private GitlabServer getSelectedServer() { 74 | if(serverTable.getSelectedRow() >= 0) { 75 | return (GitlabServer) serverTable.getValueAt(serverTable.getSelectedRow(), 0); 76 | } 77 | return null; 78 | } 79 | 80 | private TableModel serverModel(Collection servers) { 81 | Object[] columnNames = {"", "Server", "Token", "Checkout Method"}; 82 | Object[][] data = new Object[servers.size()][columnNames.length]; 83 | int i = 0; 84 | for (GitlabServer server : servers) { 85 | Object[] row = new Object[columnNames.length]; 86 | row[0] = server; 87 | row[1] = server.getApiUrl(); 88 | row[2] = server.getApiToken(); 89 | row[3] = server.getPreferredConnection().name(); 90 | data[i] = row; 91 | i++; 92 | } 93 | return new ReadOnlyTableModel(data, columnNames); 94 | } 95 | 96 | @NotNull 97 | @Override 98 | public String getId() { 99 | return DIALOG_TITLE; 100 | } 101 | 102 | @Nullable 103 | @Override 104 | public Runnable enableSearch(String s) { 105 | return null; 106 | } 107 | 108 | @Nls 109 | @Override 110 | public String getDisplayName() { 111 | return DIALOG_TITLE; 112 | } 113 | 114 | @Nullable 115 | @Override 116 | public String getHelpTopic() { 117 | return null; 118 | } 119 | 120 | @Nullable 121 | @Override 122 | public JComponent createComponent() { 123 | reset(); 124 | return mainPanel; 125 | } 126 | 127 | @Override 128 | public boolean isModified() { 129 | return false; 130 | } 131 | 132 | @Override 133 | public void apply() throws ConfigurationException { 134 | 135 | } 136 | 137 | @Override 138 | public void reset() { 139 | fill(settingsState); 140 | } 141 | 142 | @Override 143 | public void disposeUIResources() { 144 | 145 | } 146 | 147 | public void fill(SettingsState settingsState) { 148 | serverTable.setModel(serverModel(settingsState.getGitlabServers())); 149 | serverTable.getColumnModel().getColumn(0).setMinWidth(0); 150 | serverTable.getColumnModel().getColumn(0).setMaxWidth(0); 151 | serverTable.getColumnModel().getColumn(0).setWidth(0); 152 | serverTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 153 | serverTable.getSelectionModel().addListSelectionListener(event -> { 154 | editButton.setEnabled(true); 155 | deleteButton.setEnabled(true); 156 | }); 157 | 158 | } 159 | } -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/list/CodeReviewDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.list; 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.ui.DialogWrapper; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import com.intellij.util.containers.Convertor; 8 | import com.ppolivka.gitlabprojects.comment.CommentsDialog; 9 | import com.ppolivka.gitlabprojects.comment.GitLabCommentsListWorker; 10 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 11 | import com.ppolivka.gitlabprojects.merge.info.BranchInfo; 12 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 13 | import org.gitlab.api.models.GitlabMergeRequest; 14 | import org.gitlab.api.models.GitlabUser; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import javax.swing.*; 19 | 20 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 21 | 22 | /** 23 | * Dialog to accept merge request 24 | * 25 | * @author ppolivka 26 | * @since 22.12.2015 27 | */ 28 | public class CodeReviewDialog extends DialogWrapper { 29 | 30 | final private GitlabMergeRequest mergeRequest; 31 | final private GitLabMergeRequestListWorker mergeRequestWorker; 32 | 33 | private Project project; 34 | private VirtualFile virtualFile; 35 | private BranchInfo sourceBranch; 36 | private BranchInfo targetBranch; 37 | 38 | private JPanel panel; 39 | private JButton diffButton; 40 | private JLabel sourceName; 41 | private JLabel targetName; 42 | private JLabel requestName; 43 | private JButton commentsButton; 44 | private JLabel assigneeName; 45 | private JButton assignMe; 46 | 47 | SettingsState settingsState = SettingsState.getInstance(); 48 | 49 | private boolean diffClicked = false; 50 | 51 | protected CodeReviewDialog(@Nullable Project project, 52 | @NotNull GitlabMergeRequest mergeRequest, 53 | @NotNull GitLabMergeRequestListWorker mergeRequestWorker, 54 | VirtualFile virtualFile 55 | ) { 56 | super(project); 57 | this.project = project; 58 | this.mergeRequest = mergeRequest; 59 | this.mergeRequestWorker = mergeRequestWorker; 60 | init(); 61 | } 62 | 63 | @Override 64 | protected void init() { 65 | super.init(); 66 | setTitle("Code Review"); 67 | setOKButtonText("Merge"); 68 | 69 | sourceName.setText(mergeRequest.getSourceBranch()); 70 | sourceBranch = createBranchInfo(mergeRequest.getSourceBranch()); 71 | 72 | targetName.setText(mergeRequest.getTargetBranch()); 73 | targetBranch = createBranchInfo(mergeRequest.getTargetBranch()); 74 | 75 | requestName.setText(mergeRequest.getTitle()); 76 | 77 | String assignee = ""; 78 | if (mergeRequest.getAssignee() != null) { 79 | assignee = mergeRequest.getAssignee().getName(); 80 | } 81 | assigneeName.setText(assignee); 82 | 83 | diffButton.addActionListener(e -> { 84 | diffClicked = true; 85 | mergeRequestWorker.getDiffViewWorker().showDiffDialog(sourceBranch, targetBranch); 86 | }); 87 | 88 | commentsButton.addActionListener(e -> { 89 | GitLabCommentsListWorker commentsListWorker = GitLabCommentsListWorker.create(project, mergeRequest, virtualFile); 90 | CommentsDialog commentsDialog = new CommentsDialog(project, commentsListWorker, virtualFile); 91 | commentsDialog.show(); 92 | }); 93 | 94 | assignMe.addActionListener(event -> { 95 | GitLabUtil.computeValueInModal(project, "Changing assignee...", (Convertor) o -> { 96 | try { 97 | SettingsState settingsState = SettingsState.getInstance(); 98 | GitlabUser currentUser = settingsState.api(mergeRequestWorker.getGitRepository()).getCurrentUser(); 99 | settingsState.api(mergeRequestWorker.getGitRepository()).changeAssignee( 100 | mergeRequestWorker.getGitlabProject(), 101 | mergeRequest, 102 | currentUser 103 | ); 104 | assigneeName.setText(currentUser.getName()); 105 | } catch (Exception e) { 106 | showErrorDialog(project, "Cannot change assignee of this merge request.", "Cannot Change Assignee"); 107 | } 108 | return null; 109 | }); 110 | }); 111 | } 112 | 113 | 114 | private BranchInfo createBranchInfo(String name) { 115 | return new BranchInfo(name, mergeRequestWorker.getRemoteProjectName(), true); 116 | } 117 | 118 | @Override 119 | protected void doOKAction() { 120 | boolean canContinue = diffClicked; 121 | if (!diffClicked) { 122 | canContinue = GitLabUtil 123 | .showYesNoDialog(project, "Merging Without Review", "You are about to merge this merge request without looking at code differences. Are you sure?"); 124 | } 125 | if (canContinue) { 126 | mergeRequestWorker.mergeBranches(project, mergeRequest); 127 | super.doOKAction(); 128 | } 129 | } 130 | 131 | @Nullable 132 | @Override 133 | protected JComponent createCenterPanel() { 134 | return panel; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/ServerConfiguration.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |
111 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/request/CreateMergeRequestDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.request; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.intellij.openapi.ui.ValidationInfo; 6 | import com.intellij.openapi.util.text.StringUtil; 7 | import com.intellij.ui.SortedComboBoxModel; 8 | import com.ppolivka.gitlabprojects.component.SearchBoxModel; 9 | import com.ppolivka.gitlabprojects.configuration.ProjectState; 10 | import com.ppolivka.gitlabprojects.merge.info.BranchInfo; 11 | import org.apache.commons.lang.StringUtils; 12 | import org.gitlab.api.models.GitlabUser; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import javax.swing.*; 17 | 18 | /** 19 | * Dialog fore creating merge requests 20 | * 21 | * @author ppolivka 22 | * @since 30.10.2015 23 | */ 24 | public class CreateMergeRequestDialog extends DialogWrapper { 25 | 26 | private Project project; 27 | 28 | private JPanel mainView; 29 | private JComboBox targetBranch; 30 | private JLabel currentBranch; 31 | private JTextField mergeTitle; 32 | private JTextArea mergeDescription; 33 | private JButton diffButton; 34 | private JComboBox assigneeBox; 35 | private JCheckBox removeSourceBranch; 36 | private JCheckBox wip; 37 | 38 | private SortedComboBoxModel myBranchModel; 39 | private BranchInfo lastSelectedBranch; 40 | 41 | final ProjectState projectState; 42 | 43 | @NotNull 44 | final GitLabCreateMergeRequestWorker mergeRequestWorker; 45 | 46 | public CreateMergeRequestDialog(@Nullable Project project, @NotNull GitLabCreateMergeRequestWorker gitLabMergeRequestWorker) { 47 | super(project); 48 | this.project = project; 49 | projectState = ProjectState.getInstance(project); 50 | mergeRequestWorker = gitLabMergeRequestWorker; 51 | init(); 52 | 53 | } 54 | 55 | @Override 56 | protected void init() { 57 | super.init(); 58 | setTitle("Create Merge Request"); 59 | setVerticalStretch(2f); 60 | 61 | SearchBoxModel searchBoxModel = new SearchBoxModel(assigneeBox, mergeRequestWorker.getSearchableUsers()); 62 | assigneeBox.setModel(searchBoxModel); 63 | assigneeBox.setEditable(true); 64 | assigneeBox.addItemListener(searchBoxModel); 65 | assigneeBox.setBounds(140, 170, 180, 20); 66 | 67 | currentBranch.setText(mergeRequestWorker.getGitLocalBranch().getName()); 68 | 69 | myBranchModel = new SortedComboBoxModel<>((o1, o2) -> StringUtil.naturalCompare(o1.getName(), o2.getName())); 70 | myBranchModel.setAll(mergeRequestWorker.getBranches()); 71 | targetBranch.setModel(myBranchModel); 72 | targetBranch.setSelectedIndex(0); 73 | if (mergeRequestWorker.getLastUsedBranch() != null) { 74 | targetBranch.setSelectedItem(mergeRequestWorker.getLastUsedBranch()); 75 | } 76 | lastSelectedBranch = getSelectedBranch(); 77 | 78 | targetBranch.addActionListener(e -> { 79 | prepareTitle(); 80 | lastSelectedBranch = getSelectedBranch(); 81 | projectState.setLastMergedBranch(getSelectedBranch().getName()); 82 | mergeRequestWorker.getDiffViewWorker().launchLoadDiffInfo(mergeRequestWorker.getLocalBranchInfo(), getSelectedBranch()); 83 | }); 84 | 85 | prepareTitle(); 86 | 87 | Boolean deleteMergedBranch = projectState.getDeleteMergedBranch(); 88 | if(deleteMergedBranch != null && deleteMergedBranch) { 89 | this.removeSourceBranch.setSelected(true); 90 | } 91 | 92 | Boolean mergeAsWorkInProgress = projectState.getMergeAsWorkInProgress(); 93 | if(mergeAsWorkInProgress != null && mergeAsWorkInProgress) { 94 | this.wip.setSelected(true); 95 | } 96 | 97 | diffButton.addActionListener(e -> mergeRequestWorker.getDiffViewWorker().showDiffDialog(mergeRequestWorker.getLocalBranchInfo(), getSelectedBranch())); 98 | } 99 | 100 | @Override 101 | protected void doOKAction() { 102 | BranchInfo branch = getSelectedBranch(); 103 | if (mergeRequestWorker.checkAction(branch)) { 104 | String title = mergeTitle.getText(); 105 | if(wip.isSelected()) { 106 | title = "WIP:"+title; 107 | } 108 | mergeRequestWorker.createMergeRequest(branch, getAssignee(), title, mergeDescription.getText(), removeSourceBranch.isSelected()); 109 | super.doOKAction(); 110 | } 111 | } 112 | 113 | @Nullable 114 | @Override 115 | protected ValidationInfo doValidate() { 116 | if (StringUtils.isBlank(mergeTitle.getText())) { 117 | return new ValidationInfo("Merge title cannot be empty", mergeTitle); 118 | } 119 | if (getSelectedBranch().getName().equals(currentBranch.getText())) { 120 | return new ValidationInfo("Target branch must be different from current branch.", targetBranch); 121 | } 122 | return null; 123 | } 124 | 125 | private BranchInfo getSelectedBranch() { 126 | return (BranchInfo) targetBranch.getSelectedItem(); 127 | } 128 | 129 | @Nullable 130 | private GitlabUser getAssignee() { 131 | SearchableUser searchableUser = (SearchableUser) this.assigneeBox.getSelectedItem(); 132 | if(searchableUser != null) { 133 | return searchableUser.getGitLabUser(); 134 | } 135 | return null; 136 | } 137 | 138 | private void prepareTitle() { 139 | if (StringUtils.isBlank(mergeTitle.getText()) || mergeTitleGenerator(lastSelectedBranch).equals(mergeTitle.getText())) { 140 | mergeTitle.setText(mergeTitleGenerator(getSelectedBranch())); 141 | } 142 | } 143 | 144 | private String mergeTitleGenerator(BranchInfo branchInfo) { 145 | return "Merge of " + currentBranch.getText() + " to " + branchInfo; 146 | } 147 | 148 | @Nullable 149 | @Override 150 | protected JComponent createCenterPanel() { 151 | return mainView; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/GitLabDiffViewWorker.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge; 2 | 3 | import com.intellij.openapi.progress.EmptyProgressIndicator; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.Couple; 7 | import com.intellij.openapi.vcs.VcsException; 8 | import com.intellij.openapi.vcs.changes.Change; 9 | import com.intellij.util.ThrowableConvertor; 10 | import com.ppolivka.gitlabprojects.merge.info.BranchInfo; 11 | import com.ppolivka.gitlabprojects.merge.info.DiffInfo; 12 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 13 | import git4idea.GitCommit; 14 | import git4idea.changes.GitChangeUtils; 15 | import git4idea.history.GitHistoryUtils; 16 | import git4idea.repo.GitRepository; 17 | import git4idea.ui.branch.GitCompareBranchesDialog; 18 | import git4idea.update.GitFetchResult; 19 | import git4idea.update.GitFetcher; 20 | import git4idea.util.GitCommitCompareInfo; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | import org.jetbrains.ide.PooledThreadExecutor; 24 | 25 | import java.io.IOException; 26 | import java.util.Collection; 27 | import java.util.List; 28 | import java.util.concurrent.CompletableFuture; 29 | import java.util.concurrent.ExecutionException; 30 | 31 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 32 | 33 | /** 34 | * Worker class that helps to calculate diff between two branches 35 | * 36 | * @author ppolivka 37 | * @since 31.10.2015 38 | */ 39 | public class GitLabDiffViewWorker { 40 | 41 | private Project project; 42 | private GitRepository gitRepository; 43 | 44 | GitLabDiffViewWorker(Project project, GitRepository gitRepository) { 45 | this.project = project; 46 | this.gitRepository = gitRepository; 47 | } 48 | 49 | private static String CANNOT_SHOW_DIFF_INFO = "Cannot Show Diff Info"; 50 | 51 | 52 | public void showDiffDialog(@NotNull final BranchInfo from, @NotNull final BranchInfo branch) { 53 | DiffInfo info; 54 | try { 55 | info = GitLabUtil 56 | .computeValueInModal(project, "Collecting diff data...", new ThrowableConvertor() { 57 | @Override 58 | public DiffInfo convert(ProgressIndicator indicator) throws IOException { 59 | return GitLabUtil.runInterruptable(indicator, () -> getDiffInfo(from, branch)); 60 | } 61 | }); 62 | } catch (IOException e) { 63 | showErrorDialog(project, "Can't collect diff data", CANNOT_SHOW_DIFF_INFO); 64 | return; 65 | } 66 | if (info == null) { 67 | showErrorDialog(project, "Can't collect diff data", CANNOT_SHOW_DIFF_INFO); 68 | return; 69 | } 70 | 71 | GitCompareBranchesDialog dialog = new GitCompareBranchesDialog(project, info.getTo(), info.getFrom(), info.getInfo(), gitRepository, true); 72 | dialog.show(); 73 | 74 | } 75 | 76 | @Nullable 77 | public DiffInfo getDiffInfo(@NotNull final BranchInfo from, @NotNull final BranchInfo branch) throws IOException { 78 | if (branch.getName() == null) { 79 | return null; 80 | } 81 | 82 | try { 83 | return launchLoadDiffInfo(from, branch).get(); 84 | } catch (InterruptedException e) { 85 | throw new IOException(e); 86 | } catch (ExecutionException e) { 87 | Throwable wrapEx = e.getCause(); 88 | if (wrapEx.getCause() instanceof VcsException) { 89 | throw new IOException(wrapEx.getCause()); 90 | } 91 | return null; 92 | } 93 | } 94 | 95 | public CompletableFuture launchLoadDiffInfo(@NotNull final BranchInfo from, @NotNull final BranchInfo branch) { 96 | if (branch.getName() == null) { 97 | return CompletableFuture.completedFuture(null); 98 | } 99 | 100 | CompletableFuture fetchFuture = launchFetchRemote(branch); 101 | return fetchFuture.thenApply(t -> { 102 | try { 103 | return doLoadDiffInfo(from, branch); 104 | } catch (VcsException e) { 105 | throw new RuntimeException(e); 106 | } 107 | }); 108 | } 109 | 110 | private CompletableFuture launchFetchRemote(@NotNull final BranchInfo branch) { 111 | if (branch.getName() == null) { 112 | return CompletableFuture.completedFuture(false); 113 | } 114 | 115 | return CompletableFuture.supplyAsync(() -> doFetchRemote(branch), PooledThreadExecutor.INSTANCE); 116 | } 117 | 118 | private boolean doFetchRemote(@NotNull BranchInfo branch) { 119 | if (branch.getName() == null) { 120 | return false; 121 | } 122 | 123 | GitFetchResult result = 124 | new GitFetcher(project, new EmptyProgressIndicator(), false).fetch(gitRepository.getRoot(), branch.getRemoteName(), null); 125 | if (!result.isSuccess()) { 126 | GitFetcher.displayFetchResult(project, result, null, result.getErrors()); 127 | return false; 128 | } 129 | return true; 130 | } 131 | 132 | @NotNull 133 | private DiffInfo doLoadDiffInfo(@NotNull final BranchInfo from, @NotNull final BranchInfo to) throws VcsException { 134 | String currentBranch = from.getFullName(); 135 | String targetBranch = to.getFullRemoteName(); 136 | 137 | List commits1 = GitHistoryUtils.history(project, gitRepository.getRoot(), ".." + targetBranch); 138 | List commits2 = GitHistoryUtils.history(project, gitRepository.getRoot(), targetBranch + ".."); 139 | Collection diff = GitChangeUtils.getDiff(project, gitRepository.getRoot(), targetBranch, currentBranch, null); 140 | GitCommitCompareInfo info = new GitCommitCompareInfo(GitCommitCompareInfo.InfoType.BRANCH_TO_HEAD); 141 | info.put(gitRepository, diff); 142 | info.put(gitRepository, Couple.of(commits1, commits2)); 143 | 144 | return new DiffInfo(info, currentBranch, targetBranch); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/api/ApiFacade.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.api; 2 | 3 | import com.ppolivka.gitlabprojects.api.dto.NamespaceDto; 4 | import org.gitlab.api.AuthMethod; 5 | import org.gitlab.api.GitlabAPI; 6 | import org.gitlab.api.TokenType; 7 | import org.gitlab.api.http.GitlabHTTPRequestor; 8 | import org.gitlab.api.models.*; 9 | 10 | import java.io.IOException; 11 | import java.net.URLEncoder; 12 | import java.util.*; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * Facade aroud GitLab REST API 17 | * 18 | * @author ppolivka 19 | * @since 9.10.2015 20 | */ 21 | public class ApiFacade { 22 | 23 | GitlabAPI api; 24 | 25 | public ApiFacade() { 26 | } 27 | 28 | public ApiFacade(String host, String key) { 29 | reload(host, key); 30 | } 31 | 32 | public boolean reload(String host, String key) { 33 | if (host != null && key != null && !host.isEmpty() && !key.isEmpty()) { 34 | api = GitlabAPI.connect(host, key, TokenType.PRIVATE_TOKEN, AuthMethod.URL_PARAMETER); 35 | api.ignoreCertificateErrors(true); 36 | return true; 37 | } 38 | return false; 39 | } 40 | 41 | public GitlabSession getSession() throws IOException { 42 | return api.getCurrentSession(); 43 | } 44 | 45 | private void checkApi() throws IOException { 46 | if (api == null) { 47 | throw new IOException("please, configure plugin settings"); 48 | } 49 | } 50 | 51 | public List getNamespaces() throws IOException { 52 | return api.retrieve().getAll("/namespaces", NamespaceDto[].class); 53 | } 54 | 55 | public List getMergeRequests(GitlabProject project) throws IOException { 56 | return api.getOpenMergeRequests(project); 57 | } 58 | 59 | public List getMergeRequestComments(GitlabMergeRequest mergeRequest) throws IOException { 60 | return api.getNotes(mergeRequest); 61 | } 62 | 63 | public void addComment(GitlabMergeRequest mergeRequest, String body) throws IOException { 64 | api.createNote(mergeRequest, body); 65 | } 66 | 67 | public GitlabMergeRequest createMergeRequest(GitlabProject project, GitlabUser assignee, String from, String to, String title, String description, boolean removeSourceBranch) throws IOException { 68 | String tailUrl = "/projects/" + project.getId() + "/merge_requests"; 69 | GitlabHTTPRequestor requestor = api.dispatch() 70 | .with("source_branch", from) 71 | .with("target_branch", to) 72 | .with("title", title) 73 | .with("description", description); 74 | if(removeSourceBranch) { 75 | requestor.with("remove_source_branch", true); 76 | } 77 | if (assignee != null) { 78 | requestor.with("assignee_id", assignee.getId()); 79 | } 80 | 81 | return requestor.to(tailUrl, GitlabMergeRequest.class); 82 | } 83 | 84 | public void acceptMergeRequest(GitlabProject project, GitlabMergeRequest mergeRequest) throws IOException { 85 | api.acceptMergeRequest(project, mergeRequest.getIid(), null); 86 | } 87 | 88 | public void changeAssignee(GitlabProject project, GitlabMergeRequest mergeRequest, GitlabUser user) throws IOException { 89 | api.updateMergeRequest(project.getId(), mergeRequest.getIid(), null, user.getId(), null, null, null, null); 90 | } 91 | 92 | public GitlabProject createProject(String name, String visibilityLevel, boolean isPublic, NamespaceDto namespace, String description) throws IOException { 93 | return api.createProject( 94 | name, 95 | namespace != null && namespace.getId() != 0 ? namespace.getId() : null, 96 | description, 97 | null, 98 | null, 99 | null, 100 | null, 101 | null, 102 | isPublic, 103 | visibilityLevel, 104 | null 105 | ); 106 | } 107 | 108 | public GitlabProject getProject(Integer id) throws IOException { 109 | return api.getProject(id); 110 | } 111 | 112 | public List loadProjectBranches(GitlabProject gitlabProject) throws IOException { 113 | return api.getBranches(gitlabProject); 114 | } 115 | 116 | public Collection getProjects() throws Throwable { 117 | checkApi(); 118 | 119 | SortedSet result = new TreeSet<>(new Comparator() { 120 | @Override 121 | public int compare(GitlabProject o1, GitlabProject o2) { 122 | GitlabNamespace namespace1 = o1.getNamespace(); 123 | String n1 = namespace1 != null ? namespace1.getName().toLowerCase() : "Default"; 124 | GitlabNamespace namespace2 = o2.getNamespace(); 125 | String n2 = namespace2 != null ? namespace2.getName().toLowerCase() : "Default"; 126 | 127 | int compareNamespace = n1.compareTo(n2); 128 | return compareNamespace != 0 ? compareNamespace : o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); 129 | } 130 | }); 131 | 132 | List projects; 133 | try { 134 | projects = api.getMembershipProjects(); 135 | } catch (Throwable e) { 136 | projects = Collections.emptyList(); 137 | } 138 | projects = projects.stream().filter(project -> !Boolean.TRUE.equals(project.isArchived())).collect(Collectors.toList()); 139 | result.addAll(projects); 140 | 141 | return result; 142 | } 143 | 144 | public Collection searchUsers(GitlabProject project, String text) throws IOException { 145 | checkApi(); 146 | List users = new ArrayList<>(); 147 | if (text != null) { 148 | String tailUrl = GitlabProject.URL + "/" + project.getId() + "/users" + "?search=" + URLEncoder.encode(text, "UTF-8"); 149 | GitlabUser[] response = api.retrieve().to(tailUrl, GitlabUser[].class); 150 | users = Arrays.asList(response); 151 | } 152 | return users; 153 | } 154 | 155 | public GitlabUser getCurrentUser() throws IOException { 156 | checkApi(); 157 | return api.getUser(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/list/GitLabMergeRequestListWorker.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.merge.list; 2 | 3 | import com.intellij.notification.NotificationListener; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import com.intellij.openapi.progress.Task; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vcs.VcsNotifier; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.util.containers.Convertor; 10 | import com.ppolivka.gitlabprojects.configuration.ProjectState; 11 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 12 | import com.ppolivka.gitlabprojects.exception.MergeRequestException; 13 | import com.ppolivka.gitlabprojects.merge.GitLabDiffViewWorker; 14 | import com.ppolivka.gitlabprojects.merge.GitLabMergeRequestWorker; 15 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 16 | import git4idea.commands.Git; 17 | import git4idea.repo.GitRepository; 18 | import org.gitlab.api.models.GitlabMergeRequest; 19 | import org.gitlab.api.models.GitlabProject; 20 | import org.jetbrains.annotations.NotNull; 21 | import org.jetbrains.annotations.Nullable; 22 | 23 | import java.io.IOException; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | import static com.ppolivka.gitlabprojects.merge.GitLabMergeRequestWorker.Util.fillRequiredInfo; 28 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 29 | 30 | /** 31 | * Worker for listing and accepting merge request 32 | * 33 | * @author ppolivka 34 | * @since 31.10.2015 35 | */ 36 | public class GitLabMergeRequestListWorker implements GitLabMergeRequestWorker { 37 | 38 | static SettingsState settingsState = SettingsState.getInstance(); 39 | 40 | private Git git; 41 | private Project project; 42 | private ProjectState projectState; 43 | private GitRepository gitRepository; 44 | private String remoteUrl; 45 | private GitlabProject gitlabProject; 46 | private String remoteProjectName; 47 | private GitLabDiffViewWorker diffViewWorker; 48 | 49 | private List mergeRequests; 50 | 51 | public void mergeBranches(final Project project, final GitlabMergeRequest mergeRequest) { 52 | new Task.Backgroundable(project, "Merging Branches...") { 53 | @Override 54 | public void run(@NotNull ProgressIndicator indicator) { 55 | try { 56 | settingsState.api(gitRepository).acceptMergeRequest(gitlabProject, mergeRequest); 57 | VcsNotifier.getInstance(project) 58 | .notifyImportantInfo("Merged", "Merge request is merged.", NotificationListener.URL_OPENING_LISTENER); 59 | } catch (IOException e) { 60 | showErrorDialog(project, "Cannot create merge this request", "Cannot Merge"); 61 | } 62 | } 63 | }.queue(); 64 | } 65 | 66 | 67 | public static GitLabMergeRequestListWorker create(@NotNull final Project project, @Nullable final VirtualFile file) { 68 | return GitLabUtil.computeValueInModal(project, "Loading data...", new Convertor() { 69 | @Override 70 | public GitLabMergeRequestListWorker convert(ProgressIndicator indicator) { 71 | GitLabMergeRequestListWorker mergeRequestListWorker = new GitLabMergeRequestListWorker(); 72 | 73 | try { 74 | fillRequiredInfo(mergeRequestListWorker, project, file); 75 | } catch (MergeRequestException e) { 76 | return null; 77 | } 78 | 79 | try { 80 | mergeRequestListWorker.setMergeRequests(settingsState.api(project, file).getMergeRequests(mergeRequestListWorker.getGitlabProject())); 81 | } catch (IOException e) { 82 | mergeRequestListWorker.setMergeRequests(Collections.emptyList()); 83 | showErrorDialog(project, "Cannot load merge requests from GitLab API", "Cannot Load Merge Requests"); 84 | } 85 | 86 | return mergeRequestListWorker; 87 | } 88 | }); 89 | } 90 | 91 | //region Getters & Setters 92 | @Override 93 | public Git getGit() { 94 | return git; 95 | } 96 | 97 | @Override 98 | public void setGit(Git git) { 99 | this.git = git; 100 | } 101 | 102 | @Override 103 | public Project getProject() { 104 | return project; 105 | } 106 | 107 | @Override 108 | public void setProject(Project project) { 109 | this.project = project; 110 | } 111 | 112 | @Override 113 | public ProjectState getProjectState() { 114 | return projectState; 115 | } 116 | 117 | @Override 118 | public void setProjectState(ProjectState projectState) { 119 | this.projectState = projectState; 120 | } 121 | 122 | @Override 123 | public GitRepository getGitRepository() { 124 | return gitRepository; 125 | } 126 | 127 | @Override 128 | public void setGitRepository(GitRepository gitRepository) { 129 | this.gitRepository = gitRepository; 130 | } 131 | 132 | @Override 133 | public String getRemoteUrl() { 134 | return remoteUrl; 135 | } 136 | 137 | @Override 138 | public void setRemoteUrl(String remoteUrl) { 139 | this.remoteUrl = remoteUrl; 140 | } 141 | 142 | @Override 143 | public GitlabProject getGitlabProject() { 144 | return gitlabProject; 145 | } 146 | 147 | @Override 148 | public void setGitlabProject(GitlabProject gitlabProject) { 149 | this.gitlabProject = gitlabProject; 150 | } 151 | 152 | @Override 153 | public String getRemoteProjectName() { 154 | return remoteProjectName; 155 | } 156 | 157 | @Override 158 | public void setRemoteProjectName(String remoteProjectName) { 159 | this.remoteProjectName = remoteProjectName; 160 | } 161 | 162 | @Override 163 | public GitLabDiffViewWorker getDiffViewWorker() { 164 | return diffViewWorker; 165 | } 166 | 167 | @Override 168 | public void setDiffViewWorker(GitLabDiffViewWorker diffViewWorker) { 169 | this.diffViewWorker = diffViewWorker; 170 | } 171 | 172 | public List getMergeRequests() { 173 | return mergeRequests; 174 | } 175 | 176 | public void setMergeRequests(List mergeRequests) { 177 | this.mergeRequests = mergeRequests; 178 | } 179 | //endregion 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/merge/request/CreateMergeRequestDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 |
135 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/share/GitLabShareDialog.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.share; 2 | 3 | import com.intellij.openapi.progress.ProgressIndicator; 4 | import com.intellij.openapi.progress.ProgressManager; 5 | import com.intellij.openapi.progress.Task; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.ui.DialogWrapper; 8 | import com.intellij.openapi.ui.ValidationInfo; 9 | import com.intellij.ui.CollectionComboBoxModel; 10 | import com.ppolivka.gitlabprojects.api.dto.NamespaceDto; 11 | import com.ppolivka.gitlabprojects.api.dto.ServerDto; 12 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 13 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 14 | import org.apache.commons.lang.StringUtils; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import javax.swing.*; 18 | import javax.swing.border.Border; 19 | import java.awt.event.ActionEvent; 20 | import java.awt.event.ActionListener; 21 | import java.io.IOException; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 26 | 27 | /** 28 | * Dialog that is displayed when sharing project to git lab 29 | * 30 | * @author ppolivka 31 | * @since 28.10.2015 32 | */ 33 | public class GitLabShareDialog extends DialogWrapper { 34 | 35 | private static SettingsState settingsState = SettingsState.getInstance(); 36 | 37 | private JPanel mainView; 38 | private JRadioButton isPrivate; 39 | private JRadioButton isPublic; 40 | private JTextField projectName; 41 | private JTextArea commitMessage; 42 | private JRadioButton isInternal; 43 | private JComboBox groupList; 44 | private JButton refreshButton; 45 | private JRadioButton isSSHAuth; 46 | private JRadioButton isHTTPAuth; 47 | private JComboBox serverList; 48 | private final Project project; 49 | 50 | public GitLabShareDialog(@Nullable Project project) { 51 | super(project); 52 | this.project = project; 53 | init(); 54 | } 55 | 56 | @Override 57 | protected void init() { 58 | super.init(); 59 | setTitle("Share on GitLab"); 60 | setOKButtonText("Share"); 61 | 62 | ArrayList servers = new ArrayList<>(settingsState.getGitlabServers()); 63 | CollectionComboBoxModel collectionComboBoxModel = new CollectionComboBoxModel(servers, servers.get(0)); 64 | serverList.setModel(collectionComboBoxModel); 65 | 66 | Border emptyBorder = BorderFactory.createCompoundBorder(); 67 | refreshButton.setBorder(emptyBorder); 68 | 69 | commitMessage.setText("Initial commit"); 70 | 71 | isInternal.setSelected(true); 72 | 73 | ButtonGroup visibilityGroup = new ButtonGroup(); 74 | visibilityGroup.add(isPrivate); 75 | visibilityGroup.add(isInternal); 76 | visibilityGroup.add(isPublic); 77 | 78 | isSSHAuth.setSelected(true); 79 | 80 | ButtonGroup authGroup = new ButtonGroup(); 81 | authGroup.add(isHTTPAuth); 82 | authGroup.add(isSSHAuth); 83 | 84 | reloadGroupList(); 85 | 86 | refreshButton.addActionListener(new ActionListener() { 87 | @Override 88 | public void actionPerformed(ActionEvent e) { 89 | reloadGroupList(); 90 | } 91 | }); 92 | 93 | serverList.addActionListener(new ActionListener() { 94 | @Override 95 | public void actionPerformed(ActionEvent e) { 96 | reloadGroupList(); 97 | } 98 | }); 99 | } 100 | 101 | @Nullable 102 | @Override 103 | protected ValidationInfo doValidate() { 104 | if(StringUtils.isBlank(projectName.getText())) { 105 | return new ValidationInfo("Project name cannot be empty", projectName); 106 | } 107 | if(StringUtils.isBlank(commitMessage.getText())) { 108 | return new ValidationInfo("Initial commit message cannot be empty", commitMessage); 109 | } 110 | return null; 111 | } 112 | 113 | private void reloadGroupList() { 114 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "Refreshing group list..") { 115 | boolean isError = false; 116 | @Override 117 | public void run(ProgressIndicator progressIndicator) { 118 | try { 119 | List namespaces = new ArrayList<>(); 120 | namespaces.add(new NamespaceDto() {{ 121 | setId(0); 122 | setPath("Default"); 123 | }}); 124 | List remoteNamespaces = settingsState.api((GitlabServer) serverList.getSelectedItem()).getNamespaces(); 125 | if(remoteNamespaces != null) { 126 | namespaces.addAll(remoteNamespaces); 127 | } 128 | CollectionComboBoxModel collectionComboBoxModel = new CollectionComboBoxModel(namespaces, namespaces.get(0)); 129 | groupList.setModel(collectionComboBoxModel); 130 | 131 | } catch (IOException e) { 132 | isError = true; 133 | } 134 | } 135 | 136 | @Override 137 | public void onSuccess() { 138 | super.onSuccess(); 139 | if(isError) { 140 | showErrorDialog(project, "Groups cannot be refreshed", "Error Loading Groups"); 141 | close(CLOSE_EXIT_CODE); 142 | } 143 | } 144 | }); 145 | 146 | 147 | } 148 | 149 | @Nullable 150 | @Override 151 | protected JComponent createCenterPanel() { 152 | return mainView; 153 | } 154 | 155 | public JRadioButton getIsPrivate() { 156 | return isPrivate; 157 | } 158 | 159 | public JRadioButton getIsPublic() { 160 | return isPublic; 161 | } 162 | 163 | public JTextField getProjectName() { 164 | return projectName; 165 | } 166 | 167 | public JTextArea getCommitMessage() { 168 | return commitMessage; 169 | } 170 | 171 | public JRadioButton getIsInternal() { 172 | return isInternal; 173 | } 174 | 175 | public JComboBox getGroupList() { 176 | return groupList; 177 | } 178 | 179 | public JRadioButton getIsSSHAuth() { 180 | return isSSHAuth; 181 | } 182 | 183 | public JRadioButton getIsHTTPAuth() { 184 | return isHTTPAuth; 185 | } 186 | 187 | public JComboBox getServerList() { 188 | return serverList; 189 | } 190 | 191 | public void setServerList(JComboBox serverList) { 192 | this.serverList = serverList; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/ServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import com.intellij.openapi.ui.ValidationInfo; 6 | import com.intellij.ui.EnumComboBoxModel; 7 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 8 | import org.apache.commons.lang.StringUtils; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.*; 12 | import javax.swing.event.DocumentEvent; 13 | import javax.swing.event.DocumentListener; 14 | import java.awt.*; 15 | import java.io.IOException; 16 | import java.net.URI; 17 | import java.net.UnknownHostException; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.Future; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | 24 | import static org.apache.commons.lang.StringUtils.isNotBlank; 25 | 26 | public class ServerConfiguration extends DialogWrapper { 27 | 28 | private GitlabServer gitlabServer; 29 | private SettingsState settingsState = SettingsState.getInstance(); 30 | private ExecutorService executor = Executors.newSingleThreadExecutor(); 31 | 32 | 33 | private JPanel panel; 34 | private JTextField apiURl; 35 | private JTextField repositoryUrl; 36 | private JTextField token; 37 | private JButton tokenPage; 38 | private JComboBox checkoutMethod; 39 | private JCheckBox removeOnMerge; 40 | 41 | protected ServerConfiguration(@Nullable GitlabServer gitlabServer) { 42 | super(false); 43 | if(gitlabServer == null) { 44 | this.gitlabServer = new GitlabServer(); 45 | } else { 46 | this.gitlabServer = gitlabServer; 47 | } 48 | init(); 49 | setTitle("GitLab Server Details"); 50 | } 51 | 52 | @Override 53 | protected void init() { 54 | super.init(); 55 | 56 | setupModel(); 57 | fillFormFromDto(); 58 | setupListeners(); 59 | 60 | } 61 | 62 | @Nullable 63 | @Override 64 | protected ValidationInfo doValidate() { 65 | final String apiUrl = apiURl.getText(); 66 | final String tokenString = token.getText(); 67 | if(StringUtils.isBlank(apiUrl) && StringUtils.isBlank(tokenString)) { 68 | return null; 69 | } 70 | try { 71 | if (isNotBlank(apiUrl) && isNotBlank(tokenString)) { 72 | if (!isValidUrl(apiUrl)) { 73 | return new ValidationInfo(SettingError.NOT_A_URL.message(), apiURl); 74 | } else { 75 | 76 | Future infoFuture = executor.submit(() -> { 77 | try { 78 | settingsState.isApiValid(apiUrl, tokenString); 79 | return null; 80 | } catch (UnknownHostException e) { 81 | return new ValidationInfo(SettingError.SERVER_CANNOT_BE_REACHED.message(), apiURl); 82 | } catch (IOException e) { 83 | return new ValidationInfo(SettingError.INVALID_API_TOKEN.message(), apiURl); 84 | } 85 | }); 86 | try { 87 | ValidationInfo info = infoFuture.get(5000, TimeUnit.MILLISECONDS); 88 | return info; 89 | } catch (Exception e) { 90 | return new ValidationInfo(SettingError.GENERAL_ERROR.message()); 91 | } 92 | } 93 | } 94 | } catch (Exception e) { 95 | return new ValidationInfo(SettingError.GENERAL_ERROR.message()); 96 | } 97 | return null; 98 | } 99 | 100 | @Override 101 | protected void doOKAction() { 102 | super.doOKAction(); 103 | gitlabServer.setApiUrl(apiURl.getText()); 104 | gitlabServer.setApiToken(token.getText()); 105 | if(StringUtils.isNotBlank(repositoryUrl.getText())) { 106 | gitlabServer.setRepositoryUrl(repositoryUrl.getText()); 107 | } else { 108 | gitlabServer.setRepositoryUrl(ApiToRepoUrlConverter.convertApiUrlToRepoUrl(apiURl.getText())); 109 | } 110 | gitlabServer.setPreferredConnection(GitlabServer.CheckoutType.values()[checkoutMethod.getSelectedIndex()]); 111 | gitlabServer.setRemoveSourceBranch(removeOnMerge.isSelected()); 112 | settingsState.addServer(gitlabServer); 113 | } 114 | 115 | private static boolean isValidUrl(String s) { 116 | try { 117 | URI uri = new URI(s); 118 | return true; 119 | } catch (Exception e) { 120 | return false; 121 | } 122 | } 123 | 124 | private void setupListeners() { 125 | tokenPage.addActionListener(e -> openWebPage(generateHelpUrl())); 126 | onServerChange(); 127 | apiURl.getDocument().addDocumentListener(new DocumentListener() { 128 | @Override 129 | public void insertUpdate(DocumentEvent e) { 130 | onServerChange(); 131 | } 132 | 133 | @Override 134 | public void removeUpdate(DocumentEvent e) { 135 | onServerChange(); 136 | } 137 | 138 | @Override 139 | public void changedUpdate(DocumentEvent e) { 140 | onServerChange(); 141 | } 142 | }); 143 | } 144 | 145 | private void setupModel() { 146 | checkoutMethod.setModel(new EnumComboBoxModel(GitlabServer.CheckoutType.class)); 147 | } 148 | 149 | private void fillFormFromDto() { 150 | checkoutMethod.setSelectedIndex(gitlabServer.getPreferredConnection().ordinal()); 151 | removeOnMerge.setSelected(gitlabServer.isRemoveSourceBranch()); 152 | apiURl.setText(gitlabServer.getApiUrl()); 153 | repositoryUrl.setText(gitlabServer.getRepositoryUrl()); 154 | token.setText(gitlabServer.getApiToken()); 155 | } 156 | 157 | private void openWebPage(String uri) { 158 | Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; 159 | if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) { 160 | try { 161 | desktop.browse(new URI(uri)); 162 | } catch (Exception ignored) { 163 | } 164 | } 165 | } 166 | 167 | private String generateHelpUrl() { 168 | final String hostText = apiURl.getText(); 169 | StringBuilder helpUrl = new StringBuilder(); 170 | helpUrl.append(hostText); 171 | if (!hostText.endsWith("/")) { 172 | helpUrl.append("/"); 173 | } 174 | helpUrl.append("profile/personal_access_tokens"); 175 | return helpUrl.toString(); 176 | } 177 | 178 | private void onServerChange() { 179 | ValidationInfo validationInfo = doValidate(); 180 | if (validationInfo == null || (!validationInfo.message.equals(SettingError.NOT_A_URL.message))) { 181 | tokenPage.setEnabled(true); 182 | tokenPage.setToolTipText("API Key can be find in your profile setting inside GitLab Server: \n" + generateHelpUrl()); 183 | } else { 184 | tokenPage.setEnabled(false); 185 | } 186 | } 187 | 188 | @Nullable 189 | @Override 190 | protected JComponent createCenterPanel() { 191 | return panel; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/configuration/SettingsState.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.configuration; 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent; 4 | import com.intellij.openapi.components.ServiceManager; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.util.xmlb.XmlSerializerUtil; 10 | import com.ppolivka.gitlabprojects.api.ApiFacade; 11 | import com.ppolivka.gitlabprojects.api.dto.ProjectDto; 12 | import com.ppolivka.gitlabprojects.api.dto.ServerDto; 13 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 14 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 15 | import git4idea.repo.GitRemote; 16 | import git4idea.repo.GitRepository; 17 | import lombok.SneakyThrows; 18 | import org.apache.commons.lang.StringUtils; 19 | import org.gitlab.api.models.GitlabProject; 20 | import org.jetbrains.annotations.Nullable; 21 | 22 | import java.io.IOException; 23 | import java.util.*; 24 | 25 | import static com.ppolivka.gitlabprojects.util.GitLabUtil.isGitLabUrl; 26 | 27 | 28 | /** 29 | * Settings State for GitLab Projects plugin 30 | * 31 | * @author ppolivka 32 | * @since 9.10.2015 33 | */ 34 | @State( 35 | name = "SettingsState", 36 | storages = { 37 | @Storage("$APP_CONFIG$/gitlab-project-settings-new-format.xml") 38 | } 39 | ) 40 | public class SettingsState implements PersistentStateComponent { 41 | 42 | public String host; 43 | 44 | public String token; 45 | 46 | public boolean defaultRemoveBranch; 47 | 48 | public Collection projects = new ArrayList<>(); 49 | 50 | public Collection gitlabServers = new ArrayList<>(); 51 | 52 | public static SettingsState getInstance() { 53 | return ServiceManager.getService(SettingsState.class); 54 | } 55 | 56 | @Nullable 57 | @Override 58 | public SettingsState getState() { 59 | return this; 60 | } 61 | 62 | @Override 63 | public void loadState(SettingsState settingsState) { 64 | XmlSerializerUtil.copyBean(settingsState, this); 65 | } 66 | 67 | public void isApiValid(Project project, VirtualFile file) throws IOException { 68 | api(project, file).getSession(); 69 | } 70 | public void isApiValid(String host, String key) throws IOException { 71 | ApiFacade apiFacade = new ApiFacade(); 72 | apiFacade.reload(host, key); 73 | apiFacade.getSession(); 74 | } 75 | 76 | public void reloadProjects(Collection servers) throws Throwable { 77 | setProjects(new ArrayList<>()); 78 | for(GitlabServer server : servers) { 79 | reloadProjects(server); 80 | } 81 | } 82 | 83 | @SneakyThrows 84 | public Map> loadMapOfServersAndProjects(Collection servers) { 85 | Map> map = new HashMap<>(); 86 | for(GitlabServer server : servers) { 87 | Collection projects = loadProjects(server); 88 | map.put(server, projects); 89 | } 90 | return map; 91 | } 92 | 93 | public void reloadProjects(GitlabServer server) throws Throwable { 94 | this.setProjects(loadProjects(server)); 95 | 96 | } 97 | 98 | public Collection loadProjects(GitlabServer server) throws Throwable { 99 | ApiFacade apiFacade = api(server); 100 | 101 | Collection projects = getProjects(); 102 | if(projects == null) { 103 | projects = new ArrayList<>(); 104 | } 105 | 106 | for (GitlabProject gitlabProject : apiFacade.getProjects()) { 107 | ProjectDto projectDto = new ProjectDto(); 108 | projectDto.setName(gitlabProject.getName()); 109 | projectDto.setNamespace(gitlabProject.getNamespace().getName()); 110 | projectDto.setHttpUrl(gitlabProject.getHttpUrl()); 111 | projectDto.setSshUrl(gitlabProject.getSshUrl()); 112 | projects.add(projectDto); 113 | } 114 | this.setProjects(projects); 115 | return projects; 116 | 117 | } 118 | 119 | public ApiFacade api(Project project, VirtualFile file) { 120 | return api(currentGitlabServer(project, file)); 121 | } 122 | 123 | public ApiFacade api(GitRepository gitRepository) { 124 | return api(currentGitlabServer(gitRepository)); 125 | } 126 | 127 | public ApiFacade api(GitlabServer serverDto) { 128 | return new ApiFacade(serverDto.getApiUrl(), serverDto.getApiToken()); 129 | } 130 | 131 | //region Getters & Setters 132 | 133 | public String getHost() { 134 | return host; 135 | } 136 | 137 | public void setHost(String host) { 138 | this.host = host; 139 | } 140 | 141 | public String getToken() { 142 | return token; 143 | } 144 | 145 | public void setToken(String token) { 146 | this.token = token; 147 | } 148 | 149 | public boolean isDefaultRemoveBranch() { 150 | return defaultRemoveBranch; 151 | } 152 | 153 | public void setDefaultRemoveBranch(boolean defaultRemoveBranch) { 154 | this.defaultRemoveBranch = defaultRemoveBranch; 155 | } 156 | 157 | public Collection getProjects() { 158 | return projects; 159 | } 160 | 161 | public void setProjects(Collection projects) { 162 | this.projects = projects; 163 | } 164 | 165 | public Collection getGitlabServers() { 166 | return gitlabServers; 167 | } 168 | 169 | public void setGitlabServers(Collection gitlabServers) { 170 | this.gitlabServers = gitlabServers; 171 | } 172 | 173 | public void addServer(GitlabServer server) { 174 | if(getGitlabServers().stream().noneMatch(server1 -> server.getApiUrl().equals(server1.getApiUrl()))) { 175 | getGitlabServers().add(server); 176 | } else { 177 | getGitlabServers().stream().filter(server1 -> server.getApiUrl().equals(server1.getApiUrl())).forEach(changedServer -> { 178 | changedServer.setApiUrl(server.getApiUrl()); 179 | changedServer.setRepositoryUrl(server.getRepositoryUrl()); 180 | changedServer.setApiToken(server.getApiToken()); 181 | changedServer.setPreferredConnection(server.getPreferredConnection()); 182 | changedServer.setRemoveSourceBranch(server.isRemoveSourceBranch()); 183 | }); 184 | } 185 | } 186 | 187 | public void deleteServer(GitlabServer server) { 188 | getGitlabServers().stream().filter(server1 -> server.getApiUrl().equals(server1.getApiUrl())).forEach(removedServer -> getGitlabServers().remove(removedServer)); 189 | } 190 | public GitlabServer currentGitlabServer(Project project, VirtualFile file) { 191 | GitRepository gitRepository = GitLabUtil.getGitRepository(project, file); 192 | return currentGitlabServer(gitRepository); 193 | } 194 | 195 | public GitlabServer currentGitlabServer(GitRepository gitRepository) { 196 | for (GitRemote gitRemote : gitRepository.getRemotes()) { 197 | for (String remoteUrl : gitRemote.getUrls()) { 198 | for(GitlabServer server : getGitlabServers()) { 199 | if(remoteUrl.contains(server.getRepositoryUrl())) 200 | return server; 201 | } 202 | } 203 | } 204 | return null; 205 | } 206 | 207 | public boolean isEnabled() { 208 | return getGitlabServers() != null && getGitlabServers().size() > 0; 209 | } 210 | 211 | //endregion 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/share/GitLabShareDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 |
161 | -------------------------------------------------------------------------------- /src/test/java/com/ppolivka/gitlabprojects/util/DummyApplication.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.util; 2 | 3 | import com.intellij.openapi.Disposable; 4 | import com.intellij.openapi.application.*; 5 | import com.intellij.openapi.components.BaseComponent; 6 | import com.intellij.openapi.extensions.ExtensionPointName; 7 | import com.intellij.openapi.progress.ProcessCanceledException; 8 | import com.intellij.openapi.util.Computable; 9 | import com.intellij.openapi.util.Condition; 10 | import com.intellij.openapi.util.Key; 11 | import com.intellij.openapi.util.ThrowableComputable; 12 | import com.intellij.util.messages.MessageBus; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | import org.picocontainer.ComponentAdapter; 16 | import org.picocontainer.PicoContainer; 17 | import org.picocontainer.PicoVerificationException; 18 | import org.picocontainer.PicoVisitor; 19 | 20 | import java.awt.*; 21 | import java.util.*; 22 | import java.util.List; 23 | import java.util.concurrent.Callable; 24 | import java.util.concurrent.Future; 25 | 26 | /** 27 | * Dummy implementation of intellij Application to mock Service Manager 28 | * 29 | * @author ppolivka 30 | * @since 1.3.6 31 | */ 32 | public class DummyApplication implements Application { 33 | 34 | private Object service; 35 | 36 | public DummyApplication(Object service) { 37 | this.service = service; 38 | } 39 | 40 | @Override 41 | public void runReadAction(@NotNull Runnable runnable) { 42 | 43 | } 44 | 45 | @Override 46 | public T runReadAction(@NotNull Computable computable) { 47 | return null; 48 | } 49 | 50 | @Override 51 | public T runReadAction(@NotNull ThrowableComputable throwableComputable) throws E { 52 | return null; 53 | } 54 | 55 | @Override 56 | public void runWriteAction(@NotNull Runnable runnable) { 57 | 58 | } 59 | 60 | @Override 61 | public T runWriteAction(@NotNull Computable computable) { 62 | return null; 63 | } 64 | 65 | @Override 66 | public T runWriteAction(@NotNull ThrowableComputable throwableComputable) throws E { 67 | return null; 68 | } 69 | 70 | @Override 71 | public boolean hasWriteAction(@NotNull Class aClass) { 72 | return false; 73 | } 74 | 75 | @Override 76 | public void assertReadAccessAllowed() { 77 | 78 | } 79 | 80 | @Override 81 | public void assertWriteAccessAllowed() { 82 | 83 | } 84 | 85 | @Override 86 | public void assertIsDispatchThread() { 87 | 88 | } 89 | 90 | @Override 91 | public void addApplicationListener(@NotNull ApplicationListener applicationListener) { 92 | 93 | } 94 | 95 | @Override 96 | public void addApplicationListener(@NotNull ApplicationListener applicationListener, @NotNull Disposable disposable) { 97 | 98 | } 99 | 100 | @Override 101 | public void removeApplicationListener(@NotNull ApplicationListener applicationListener) { 102 | 103 | } 104 | 105 | @Override 106 | public void saveAll() { 107 | 108 | } 109 | 110 | @Override 111 | public void saveAll(boolean isForce) { 112 | 113 | } 114 | 115 | @Override 116 | public void saveSettings() { 117 | 118 | } 119 | 120 | @Override 121 | public void saveSettings(boolean isForce) { 122 | 123 | } 124 | 125 | @Override 126 | public void exit() { 127 | 128 | } 129 | 130 | @Override 131 | public boolean isWriteAccessAllowed() { 132 | return false; 133 | } 134 | 135 | @Override 136 | public boolean isReadAccessAllowed() { 137 | return false; 138 | } 139 | 140 | @Override 141 | public boolean isDispatchThread() { 142 | return false; 143 | } 144 | 145 | @NotNull 146 | @Override 147 | public ModalityInvokator getInvokator() { 148 | return null; 149 | } 150 | 151 | @Override 152 | public void invokeLater(@NotNull Runnable runnable) { 153 | 154 | } 155 | 156 | @Override 157 | public void invokeLater(@NotNull Runnable runnable, @NotNull Condition condition) { 158 | 159 | } 160 | 161 | @Override 162 | public void invokeLater(@NotNull Runnable runnable, @NotNull ModalityState modalityState) { 163 | 164 | } 165 | 166 | @Override 167 | public void invokeLater(@NotNull Runnable runnable, @NotNull ModalityState modalityState, @NotNull Condition condition) { 168 | 169 | } 170 | 171 | @Override 172 | public void invokeAndWait(@NotNull Runnable runnable, @NotNull ModalityState modalityState) throws ProcessCanceledException { 173 | 174 | } 175 | 176 | @Override 177 | public void invokeAndWait(@NotNull Runnable runnable) throws ProcessCanceledException { 178 | 179 | } 180 | 181 | @NotNull 182 | @Override 183 | public ModalityState getCurrentModalityState() { 184 | return null; 185 | } 186 | 187 | @NotNull 188 | @Override 189 | public ModalityState getModalityStateForComponent(@NotNull Component component) { 190 | return null; 191 | } 192 | 193 | @NotNull 194 | @Override 195 | public ModalityState getDefaultModalityState() { 196 | return null; 197 | } 198 | 199 | @NotNull 200 | @Override 201 | public ModalityState getNoneModalityState() { 202 | return null; 203 | } 204 | 205 | @NotNull 206 | @Override 207 | public ModalityState getAnyModalityState() { 208 | return null; 209 | } 210 | 211 | @Override 212 | public long getStartTime() { 213 | return 0; 214 | } 215 | 216 | @Override 217 | public long getIdleTime() { 218 | return 0; 219 | } 220 | 221 | @Override 222 | public boolean isUnitTestMode() { 223 | return false; 224 | } 225 | 226 | @Override 227 | public boolean isHeadlessEnvironment() { 228 | return false; 229 | } 230 | 231 | @Override 232 | public boolean isCommandLine() { 233 | return false; 234 | } 235 | 236 | @NotNull 237 | @Override 238 | public Future executeOnPooledThread(@NotNull Runnable runnable) { 239 | return null; 240 | } 241 | 242 | @NotNull 243 | @Override 244 | public Future executeOnPooledThread(@NotNull Callable callable) { 245 | return null; 246 | } 247 | 248 | @Override 249 | public boolean isDisposeInProgress() { 250 | return false; 251 | } 252 | 253 | @Override 254 | public boolean isRestartCapable() { 255 | return false; 256 | } 257 | 258 | @Override 259 | public void restart() { 260 | 261 | } 262 | 263 | @Override 264 | public boolean isActive() { 265 | return false; 266 | } 267 | 268 | @NotNull 269 | @Override 270 | public AccessToken acquireReadActionLock() { 271 | return null; 272 | } 273 | 274 | @NotNull 275 | @Override 276 | public AccessToken acquireWriteActionLock(@NotNull Class aClass) { 277 | return null; 278 | } 279 | 280 | @Override 281 | public boolean isInternal() { 282 | return false; 283 | } 284 | 285 | @Override 286 | public boolean isEAP() { 287 | return false; 288 | } 289 | 290 | @Override 291 | public BaseComponent getComponent(@NotNull String s) { 292 | return null; 293 | } 294 | 295 | @Override 296 | public T getComponent(@NotNull Class aClass) { 297 | return null; 298 | } 299 | 300 | @Override 301 | public T getComponent(@NotNull Class aClass, T t) { 302 | return null; 303 | } 304 | 305 | @Override 306 | public boolean hasComponent(@NotNull Class aClass) { 307 | return false; 308 | } 309 | 310 | @NotNull 311 | @Override 312 | public T[] getComponents(@NotNull Class aClass) { 313 | return null; 314 | } 315 | 316 | @NotNull 317 | @Override 318 | public PicoContainer getPicoContainer() { 319 | return new PicoContainer() { 320 | @Override 321 | public Object getComponentInstance(Object o) { 322 | return service; 323 | } 324 | 325 | @Override 326 | public Object getComponentInstanceOfType(Class aClass) { 327 | return null; 328 | } 329 | 330 | @Override 331 | public List getComponentInstances() { 332 | return null; 333 | } 334 | 335 | @Override 336 | public PicoContainer getParent() { 337 | return null; 338 | } 339 | 340 | @Override 341 | public ComponentAdapter getComponentAdapter(Object o) { 342 | return null; 343 | } 344 | 345 | @Override 346 | public ComponentAdapter getComponentAdapterOfType(Class aClass) { 347 | return null; 348 | } 349 | 350 | @Override 351 | public Collection getComponentAdapters() { 352 | return null; 353 | } 354 | 355 | @Override 356 | public List getComponentAdaptersOfType(Class aClass) { 357 | return null; 358 | } 359 | 360 | @Override 361 | public void verify() throws PicoVerificationException { 362 | 363 | } 364 | 365 | @Override 366 | public List getComponentInstancesOfType(Class aClass) { 367 | return null; 368 | } 369 | 370 | @Override 371 | public void accept(PicoVisitor picoVisitor) { 372 | 373 | } 374 | 375 | @Override 376 | public void dispose() { 377 | 378 | } 379 | 380 | @Override 381 | public void start() { 382 | 383 | } 384 | 385 | @Override 386 | public void stop() { 387 | 388 | } 389 | }; 390 | } 391 | 392 | @NotNull 393 | @Override 394 | public MessageBus getMessageBus() { 395 | return null; 396 | } 397 | 398 | @Override 399 | public boolean isDisposed() { 400 | return false; 401 | } 402 | 403 | @NotNull 404 | @Override 405 | public T[] getExtensions(@NotNull ExtensionPointName extensionPointName) { 406 | return null; 407 | } 408 | 409 | @NotNull 410 | @Override 411 | public Condition getDisposed() { 412 | return null; 413 | } 414 | 415 | @Override 416 | public void dispose() { 417 | 418 | } 419 | 420 | @Nullable 421 | @Override 422 | public T getUserData(@NotNull Key key) { 423 | return null; 424 | } 425 | 426 | @Override 427 | public void putUserData(@NotNull Key key, @Nullable T t) { 428 | 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/util/GitLabUtil.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.util; 2 | 3 | import com.intellij.concurrency.JobScheduler; 4 | import com.intellij.openapi.progress.ProgressIndicator; 5 | import com.intellij.openapi.progress.ProgressManager; 6 | import com.intellij.openapi.progress.Task; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.ui.Messages; 9 | import com.intellij.openapi.util.Pair; 10 | import com.intellij.openapi.util.Ref; 11 | import com.intellij.openapi.util.ThrowableComputable; 12 | import com.intellij.openapi.vcs.VcsException; 13 | import com.intellij.openapi.vfs.VirtualFile; 14 | import com.intellij.util.ThrowableConvertor; 15 | import com.intellij.util.containers.Convertor; 16 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 17 | import git4idea.GitUtil; 18 | import git4idea.commands.GitCommand; 19 | import git4idea.commands.GitSimpleHandler; 20 | import git4idea.config.GitVcsApplicationSettings; 21 | import git4idea.config.GitVersion; 22 | import git4idea.repo.GitRemote; 23 | import git4idea.repo.GitRepository; 24 | import git4idea.repo.GitRepositoryManager; 25 | import org.apache.commons.lang.StringUtils; 26 | import org.jetbrains.annotations.NotNull; 27 | import org.jetbrains.annotations.Nullable; 28 | 29 | import java.io.IOException; 30 | import java.net.URI; 31 | import java.util.List; 32 | import java.util.concurrent.ScheduledFuture; 33 | import java.util.concurrent.TimeUnit; 34 | import java.util.regex.Matcher; 35 | import java.util.regex.Pattern; 36 | 37 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 38 | 39 | /** 40 | * GitLab specific untils 41 | * 42 | * @author ppolivka 43 | * @since 28.10.2015 44 | */ 45 | @SuppressWarnings("Duplicates") 46 | public class GitLabUtil { 47 | 48 | private static SettingsState settingsState = SettingsState.getInstance(); 49 | 50 | @Nullable 51 | public static GitRepository getGitRepository(@NotNull Project project, @Nullable VirtualFile file) { 52 | GitRepositoryManager manager = GitUtil.getRepositoryManager(project); 53 | List repositories = manager.getRepositories(); 54 | if (repositories.size() == 0) { 55 | return null; 56 | } 57 | if (repositories.size() == 1) { 58 | return repositories.get(0); 59 | } 60 | if (file != null) { 61 | GitRepository repository = manager.getRepositoryForFile(file); 62 | if (repository != null) { 63 | return repository; 64 | } 65 | } 66 | return manager.getRepositoryForFile(project.getBaseDir()); 67 | } 68 | 69 | @Nullable 70 | public static String findGitLabRemoteUrl(@NotNull GitRepository repository) { 71 | Pair remote = findGitLabRemote(repository); 72 | if (remote == null) { 73 | return null; 74 | } 75 | return remote.getSecond(); 76 | } 77 | 78 | @Nullable 79 | public static Pair findGitLabRemote(@NotNull GitRepository repository) { 80 | for (GitRemote gitRemote : repository.getRemotes()) { 81 | for (String remoteUrl : gitRemote.getUrls()) { 82 | if (remoteUrl.contains(settingsState.currentGitlabServer(repository).getRepositoryUrl())) { 83 | return Pair.create(gitRemote, gitRemote.getName()); 84 | } 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | 91 | public static boolean isGitLabUrl(String testUrl, String url) { 92 | try { 93 | URI fromSettings = new URI(testUrl); 94 | String fromSettingsHost = fromSettings.getHost(); 95 | 96 | String patternString = "(\\w+://)(.+@)*([\\w\\d\\.\\-]+)(:[\\d]+){0,1}/*(.*)|(.+@)*([\\w\\d\\.\\-]+):(.*)"; 97 | Pattern pattern = Pattern.compile(patternString); 98 | Matcher matcher = pattern.matcher(url); 99 | String fromUrlHost = ""; 100 | if(matcher.matches()) { 101 | String group3 = matcher.group(3); 102 | String group7 = matcher.group(7); 103 | if(StringUtils.isNotEmpty(group3)) { 104 | fromUrlHost = group3; 105 | } else if (StringUtils.isNotEmpty(group7)) { 106 | fromUrlHost = group7; 107 | } 108 | } 109 | return fromSettingsHost != null && removeNotAlpha(fromSettingsHost).equals(removeNotAlpha(fromUrlHost)); 110 | } catch (Exception e) { 111 | return false; 112 | } 113 | } 114 | 115 | public static String removeNotAlpha(String input) { 116 | input = input.replaceAll("[^a-zA-Z0-9]", ""); 117 | input = input.toLowerCase(); 118 | return input; 119 | } 120 | 121 | public static boolean addGitLabRemote(@NotNull Project project, 122 | @NotNull GitRepository repository, 123 | @NotNull String remote, 124 | @NotNull String url) { 125 | final GitSimpleHandler handler = new GitSimpleHandler(project, repository.getRoot(), GitCommand.REMOTE); 126 | handler.setSilent(true); 127 | 128 | try { 129 | handler.addParameters("add", remote, url); 130 | handler.run(); 131 | if (handler.getExitCode() != 0) { 132 | showErrorDialog(project, "New remote origin cannot be added to this project.", "Cannot Add New Remote"); 133 | return false; 134 | } 135 | // catch newly added remote 136 | repository.update(); 137 | return true; 138 | } catch (VcsException e) { 139 | showErrorDialog(project, "New remote origin cannot be added to this project.", "Cannot Add New Remote"); 140 | return false; 141 | } 142 | } 143 | 144 | public static boolean testGitExecutable(final Project project) { 145 | final GitVcsApplicationSettings settings = GitVcsApplicationSettings.getInstance(); 146 | final String executable = settings.getPathToGit(); 147 | final GitVersion version; 148 | try { 149 | version = GitVersion.identifyVersion(executable); 150 | } catch (Exception e) { 151 | showErrorDialog(project, "Cannot find git executable.", "Cannot Find Git"); 152 | return false; 153 | } 154 | 155 | if (!version.isSupported()) { 156 | showErrorDialog(project, "Your version of git is not supported.", "Cannot Find Git"); 157 | return false; 158 | } 159 | return true; 160 | } 161 | 162 | public static T computeValueInModal(@NotNull Project project, 163 | @NotNull String caption, 164 | @NotNull final ThrowableConvertor task) throws IOException { 165 | final Ref dataRef = new Ref(); 166 | final Ref exceptionRef = new Ref(); 167 | ProgressManager.getInstance().run(new Task.Modal(project, caption, true) { 168 | public void run(@NotNull ProgressIndicator indicator) { 169 | try { 170 | dataRef.set(task.convert(indicator)); 171 | } catch (Throwable e) { 172 | exceptionRef.set(e); 173 | } 174 | } 175 | }); 176 | if (!exceptionRef.isNull()) { 177 | Throwable e = exceptionRef.get(); 178 | if (e instanceof IOException) { 179 | throw ((IOException) e); 180 | } 181 | if (e instanceof RuntimeException) { 182 | throw ((RuntimeException) e); 183 | } 184 | if (e instanceof Error) { 185 | throw ((Error) e); 186 | } 187 | throw new RuntimeException(e); 188 | } 189 | return dataRef.get(); 190 | } 191 | 192 | public static T computeValueInModal(@NotNull Project project, 193 | @NotNull String caption, 194 | @NotNull final Convertor task) { 195 | return computeValueInModal(project, caption, true, task); 196 | } 197 | 198 | public static T computeValueInModal(@NotNull Project project, 199 | @NotNull String caption, 200 | boolean canBeCancelled, 201 | @NotNull final Convertor task) { 202 | final Ref dataRef = new Ref(); 203 | final Ref exceptionRef = new Ref(); 204 | ProgressManager.getInstance().run(new Task.Modal(project, caption, canBeCancelled) { 205 | public void run(@NotNull ProgressIndicator indicator) { 206 | try { 207 | dataRef.set(task.convert(indicator)); 208 | } catch (Throwable e) { 209 | exceptionRef.set(e); 210 | } 211 | } 212 | }); 213 | if (!exceptionRef.isNull()) { 214 | Throwable e = exceptionRef.get(); 215 | if (e instanceof RuntimeException) { 216 | throw ((RuntimeException) e); 217 | } 218 | if (e instanceof Error) { 219 | throw ((Error) e); 220 | } 221 | throw new RuntimeException(e); 222 | } 223 | return dataRef.get(); 224 | } 225 | 226 | public static T runInterruptable(@NotNull final ProgressIndicator indicator, 227 | @NotNull ThrowableComputable task) throws IOException { 228 | ScheduledFuture future = null; 229 | try { 230 | final Thread thread = Thread.currentThread(); 231 | future = addCancellationListener(indicator, thread); 232 | 233 | return task.compute(); 234 | } finally { 235 | if (future != null) { 236 | future.cancel(true); 237 | } 238 | Thread.interrupted(); 239 | } 240 | } 241 | 242 | @NotNull 243 | private static ScheduledFuture addCancellationListener(@NotNull final ProgressIndicator indicator, 244 | @NotNull final Thread thread) { 245 | return addCancellationListener(new Runnable() { 246 | @Override 247 | public void run() { 248 | if (indicator.isCanceled()) { 249 | thread.interrupt(); 250 | } 251 | } 252 | }); 253 | } 254 | 255 | @NotNull 256 | private static ScheduledFuture addCancellationListener(@NotNull Runnable run) { 257 | return JobScheduler.getScheduler().scheduleWithFixedDelay(run, 1000, 300, TimeUnit.MILLISECONDS); 258 | } 259 | 260 | @Messages.YesNoResult 261 | public static boolean showYesNoDialog(@Nullable Project project, @NotNull String title, @NotNull String message) { 262 | return Messages.YES == Messages.showYesNoDialog(project, message, title, Messages.getQuestionIcon()); 263 | } 264 | 265 | } 266 | -------------------------------------------------------------------------------- /src/main/java/com/ppolivka/gitlabprojects/share/GitLabShareAction.java: -------------------------------------------------------------------------------- 1 | package com.ppolivka.gitlabprojects.share; 2 | 3 | import com.intellij.openapi.actionSystem.AnActionEvent; 4 | import com.intellij.openapi.components.ServiceManager; 5 | import com.intellij.openapi.progress.ProgressIndicator; 6 | import com.intellij.openapi.progress.ProgressManager; 7 | import com.intellij.openapi.progress.Task; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.util.Condition; 10 | import com.intellij.openapi.vcs.ProjectLevelVcsManager; 11 | import com.intellij.openapi.vcs.VcsException; 12 | import com.intellij.openapi.vcs.changes.ChangeListManager; 13 | import com.intellij.openapi.vfs.VirtualFile; 14 | import com.intellij.util.containers.ContainerUtil; 15 | import com.ppolivka.gitlabprojects.api.dto.NamespaceDto; 16 | import com.ppolivka.gitlabprojects.api.dto.ServerDto; 17 | import com.ppolivka.gitlabprojects.common.GitLabIcons; 18 | import com.ppolivka.gitlabprojects.common.NoGitLabApiAction; 19 | import com.ppolivka.gitlabprojects.configuration.SettingsState; 20 | import com.ppolivka.gitlabprojects.dto.GitlabServer; 21 | import com.ppolivka.gitlabprojects.util.GitLabUtil; 22 | import git4idea.GitLocalBranch; 23 | import git4idea.GitUtil; 24 | import git4idea.actions.BasicAction; 25 | import git4idea.actions.GitInit; 26 | import git4idea.commands.*; 27 | import git4idea.i18n.GitBundle; 28 | import git4idea.repo.GitRepository; 29 | import git4idea.repo.GitRepositoryManager; 30 | import git4idea.util.GitFileUtils; 31 | import git4idea.util.GitUIUtil; 32 | import org.gitlab.api.models.GitlabProject; 33 | import org.jetbrains.annotations.NotNull; 34 | import org.jetbrains.annotations.Nullable; 35 | 36 | import java.io.IOException; 37 | import java.util.Collection; 38 | import java.util.List; 39 | 40 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showErrorDialog; 41 | import static com.ppolivka.gitlabprojects.util.MessageUtil.showInfoMessage; 42 | 43 | /** 44 | * Import to VCS project to gitlab 45 | * 46 | * @author ppolivka 47 | * @since 28.10.2015 48 | */ 49 | public class GitLabShareAction extends NoGitLabApiAction { 50 | 51 | private static SettingsState settingsState = SettingsState.getInstance(); 52 | 53 | public GitLabShareAction() { 54 | super("Share Project on GitLab...", "Easy share on your GitLab server", GitLabIcons.gitLabIcon); 55 | } 56 | 57 | @Override 58 | public void apiValidAction(AnActionEvent anActionEvent) { 59 | 60 | if (project.isDisposed()) { 61 | return; 62 | } 63 | 64 | shareProjectOnGitLab(project, file); 65 | } 66 | 67 | public void shareProjectOnGitLab(@NotNull final Project project, @Nullable final VirtualFile file) { 68 | BasicAction.saveAll(); 69 | 70 | // get gitRepository 71 | final GitRepository gitRepository = GitLabUtil.getGitRepository(project, file); 72 | final boolean gitDetected = gitRepository != null; 73 | final VirtualFile root = gitDetected ? gitRepository.getRoot() : project.getBaseDir(); 74 | 75 | if (gitDetected) { 76 | final String gitLabRemoteUrl = GitLabUtil.findGitLabRemoteUrl(gitRepository); 77 | if (gitLabRemoteUrl != null) { 78 | showInfoMessage(project, "This project already has remote to your GitLab Server", "Already Git Lab Project"); 79 | } 80 | } 81 | GitLabShareDialog gitLabShareDialog = new GitLabShareDialog(project); 82 | gitLabShareDialog.show(); 83 | if (!gitLabShareDialog.isOK()) { 84 | return; 85 | } 86 | final String name = gitLabShareDialog.getProjectName().getText(); 87 | final String commitMessage = gitLabShareDialog.getCommitMessage().getText(); 88 | final NamespaceDto namespace = (NamespaceDto) gitLabShareDialog.getGroupList().getSelectedItem(); 89 | String visibility_level = "internal"; 90 | boolean isPublic = false; 91 | if (gitLabShareDialog.getIsPrivate().isSelected()) { 92 | visibility_level = "private"; 93 | } 94 | if (gitLabShareDialog.getIsPublic().isSelected()) { 95 | visibility_level = "public"; 96 | isPublic = true; 97 | } 98 | final String visibility = visibility_level; 99 | final boolean publicity = isPublic; 100 | 101 | boolean isSsh = true; 102 | if (gitLabShareDialog.getIsHTTPAuth().isSelected()) { 103 | isSsh = false; 104 | } 105 | final boolean authSsh = isSsh; 106 | 107 | ProgressManager.getInstance().run(new Task.Backgroundable(project, "Sharing to GitLab...") { 108 | 109 | @Override 110 | public void run(@NotNull ProgressIndicator indicator) { 111 | GitlabProject gitlabProject; 112 | try { 113 | indicator.setText("Creating GitLab Repository"); 114 | gitlabProject = settingsState 115 | .api((GitlabServer) gitLabShareDialog.getServerList().getSelectedItem()) 116 | .createProject(name, visibility, publicity, namespace, ""); 117 | } catch (IOException e) { 118 | return; 119 | } 120 | 121 | if (!gitDetected) { 122 | indicator.setText("Creating empty git repo..."); 123 | if (!createEmptyGitRepository(project, root, indicator)) { 124 | return; 125 | } 126 | } 127 | 128 | GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); 129 | final GitRepository repository = repositoryManager.getRepositoryForRoot(root); 130 | if (repository == null) { 131 | showErrorDialog(project, "Remote server was not found.", "Remote Not Found"); 132 | return; 133 | } 134 | 135 | final String remoteUrl = authSsh ? gitlabProject.getSshUrl() : gitlabProject.getHttpUrl(); 136 | 137 | indicator.setText("Adding GitLAb as a remote host..."); 138 | if (!GitLabUtil.addGitLabRemote(project, repository, name, remoteUrl)) { 139 | return; 140 | } 141 | 142 | if (!performFirstCommitIfRequired(project, root, repository, indicator, remoteUrl, commitMessage)) { 143 | return; 144 | } 145 | 146 | indicator.setText("Pushing to gitlab master..."); 147 | if (!pushCurrentBranch(project, repository, name, remoteUrl, name, remoteUrl)) { 148 | return; 149 | } 150 | 151 | showInfoMessage(project, "Project was shared to your GitLab server", "Project Shared"); 152 | 153 | } 154 | }); 155 | 156 | 157 | } 158 | 159 | private static boolean performFirstCommitIfRequired(@NotNull final Project project, 160 | @NotNull VirtualFile root, 161 | @NotNull GitRepository repository, 162 | @NotNull ProgressIndicator indicator, 163 | @NotNull String url, 164 | @NotNull String commitMessage) { 165 | // check if there is no commits 166 | if (!repository.isFresh()) { 167 | return true; 168 | } 169 | 170 | try { 171 | indicator.setText("Adding files to git..."); 172 | 173 | // ask for files to add 174 | final List trackedFiles = ChangeListManager.getInstance(project).getAffectedFiles(); 175 | final Collection untrackedFiles = 176 | filterOutIgnored(project, repository.getUntrackedFilesHolder().retrieveUntrackedFiles()); 177 | untrackedFiles.removeAll(trackedFiles); 178 | 179 | GitFileUtils.addFiles(project, root, untrackedFiles); 180 | 181 | indicator.setText("Performing commit..."); 182 | GitSimpleHandler handler = new GitSimpleHandler(project, root, GitCommand.COMMIT); 183 | handler.setStdoutSuppressed(false); 184 | handler.addParameters("-m", commitMessage); 185 | handler.endOptions(); 186 | handler.run(); 187 | } catch (VcsException e) { 188 | showErrorDialog(project, "Project was create on GitLab server, but files cannot be commited to it.", "Initial Commit Failure"); 189 | return false; 190 | } 191 | return true; 192 | } 193 | 194 | @NotNull 195 | private static Collection filterOutIgnored(@NotNull Project project, @NotNull Collection files) { 196 | final ChangeListManager changeListManager = ChangeListManager.getInstance(project); 197 | final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(project); 198 | return ContainerUtil.filter(files, new Condition() { 199 | @Override 200 | public boolean value(VirtualFile file) { 201 | return !changeListManager.isIgnoredFile(file) && !vcsManager.isIgnored(file); 202 | } 203 | }); 204 | } 205 | 206 | private static boolean createEmptyGitRepository(@NotNull Project project, 207 | @NotNull VirtualFile root, 208 | @NotNull ProgressIndicator indicator) { 209 | final GitLineHandler h = new GitLineHandler(project, root, GitCommand.INIT); 210 | h.setStdoutSuppressed(false); 211 | GitHandlerUtil.runInCurrentThread(h, indicator, true, GitBundle.getString("initializing.title")); 212 | if (!h.errors().isEmpty()) { 213 | GitUIUtil.showOperationErrors(project, h.errors(), "git init"); 214 | return false; 215 | } 216 | GitInit.refreshAndConfigureVcsMappings(project, root, root.getPath()); 217 | return true; 218 | } 219 | 220 | private static boolean pushCurrentBranch(@NotNull Project project, 221 | @NotNull GitRepository repository, 222 | @NotNull String remoteName, 223 | @NotNull String remoteUrl, 224 | @NotNull String name, 225 | @NotNull String url) { 226 | Git git = ServiceManager.getService(Git.class); 227 | 228 | GitLocalBranch currentBranch = repository.getCurrentBranch(); 229 | if (currentBranch == null) { 230 | showErrorDialog(project, "Project was create on GitLAb server, but cannot be pushed.", "Cannot Be Pushed"); 231 | return false; 232 | } 233 | GitCommandResult result = git.push(repository, remoteName, remoteUrl, currentBranch.getName(), true); 234 | if (!result.success()) { 235 | showErrorDialog(project, "Project was create on GitLab server, but cannot be pushed.", "Cannot Be Pushed"); 236 | return false; 237 | } 238 | return true; 239 | } 240 | } 241 | --------------------------------------------------------------------------------