├── .gitignore ├── .idea ├── encodings.xml ├── modules.xml └── vcs.xml ├── CommitMessageChecker.iml ├── README.md ├── resources └── META-INF │ ├── plugin.xml │ └── pluginIcon.svg └── src └── org └── turbanov └── commits ├── CommitMessageCheckinHandlerFactory.java └── IssueReferenceChecker.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.jar 2 | .idea/workspace.xml 3 | out/ 4 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CommitMessageChecker.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commit message checker 2 | 3 | Plugins for Jetbrains IDEs. 4 | Checks that commit message contains reference to the same issue as branch name. 5 | 6 | ![warning_example](https://user-images.githubusercontent.com/741251/72476919-7e195900-37ff-11ea-8973-3618760c56d5.png) 7 | 8 | ## Setup 9 | Make sure you have configured VCS issue regexp 10 | `File | Settings | Version Control | Issue Navigation` 11 | 12 | [See](https://www.jetbrains.com/help/idea/settings-version-control-issue-navigation.html) for details 13 | 14 | ![vcs_issue_navigation](https://user-images.githubusercontent.com/741251/189093654-7b5e724f-0638-48b3-9be6-67d9f2e63144.png) 15 | -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | org.turbanov.commits.message.checker 3 | Commit Message Checker 4 | 1.2 5 | Turbanov Andrey 6 | 7 | 11 | 12 | 14 |

1.2

15 | 18 |

1.1

19 | 22 | 23 | ]]> 24 |
25 | 26 | 27 | 28 | 29 | 31 | com.intellij.modules.platform 32 | com.intellij.modules.vcs 33 | 34 | 35 | 36 | 37 | 38 |
-------------------------------------------------------------------------------- /resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/org/turbanov/commits/CommitMessageCheckinHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package org.turbanov.commits; 2 | 3 | import com.intellij.openapi.vcs.CheckinProjectPanel; 4 | import com.intellij.openapi.vcs.changes.CommitContext; 5 | import com.intellij.openapi.vcs.checkin.CheckinHandler; 6 | import com.intellij.openapi.vcs.checkin.CheckinHandlerFactory; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * @author Andrey Turbanov 11 | * @since 11.02.2017 12 | */ 13 | public class CommitMessageCheckinHandlerFactory extends CheckinHandlerFactory { 14 | @NotNull 15 | @Override 16 | public CheckinHandler createHandler(@NotNull CheckinProjectPanel panel, @NotNull CommitContext commitContext) { 17 | return new IssueReferenceChecker(panel); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/org/turbanov/commits/IssueReferenceChecker.java: -------------------------------------------------------------------------------- 1 | package org.turbanov.commits; 2 | 3 | import javax.swing.JCheckBox; 4 | import javax.swing.JComponent; 5 | import javax.swing.JPanel; 6 | import java.awt.BorderLayout; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.Stream; 12 | 13 | import com.intellij.dvcs.repo.Repository; 14 | import com.intellij.dvcs.repo.VcsRepositoryManager; 15 | import com.intellij.ide.util.PropertiesComponent; 16 | import com.intellij.openapi.project.Project; 17 | import com.intellij.openapi.ui.Messages; 18 | import com.intellij.openapi.util.Trinity; 19 | import com.intellij.openapi.vcs.CheckinProjectPanel; 20 | import com.intellij.openapi.vcs.IssueNavigationConfiguration; 21 | import com.intellij.openapi.vcs.checkin.CheckinHandler; 22 | import com.intellij.openapi.vcs.ui.RefreshableOnComponent; 23 | import com.intellij.util.ui.UIUtil; 24 | import com.intellij.xml.util.XmlStringUtil; 25 | import static com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer.formatTextWithLinks; 26 | 27 | /** 28 | * @author Andrey Turbanov 29 | * @since 12.02.2017 30 | */ 31 | class IssueReferenceChecker extends CheckinHandler { 32 | private static final String CHECKER_STATE_KEY = "COMMIT_MESSAGE_ISSUE_CHECKER_STATE_KEY"; 33 | private final CheckinProjectPanel panel; 34 | 35 | public IssueReferenceChecker(CheckinProjectPanel panel) { 36 | this.panel = panel; 37 | } 38 | 39 | @Override 40 | public RefreshableOnComponent getBeforeCheckinConfigurationPanel() { 41 | final JCheckBox checkBox = new JCheckBox("Check reference to issue in message"); 42 | 43 | return new RefreshableOnComponent() { 44 | @Override 45 | public JComponent getComponent() { 46 | JPanel root = new JPanel(new BorderLayout()); 47 | root.add(checkBox, "West"); 48 | return root; 49 | } 50 | 51 | @Override 52 | public void refresh() { 53 | } 54 | 55 | @Override 56 | public void saveState() { 57 | PropertiesComponent.getInstance().setValue(CHECKER_STATE_KEY, checkBox.isSelected()); 58 | } 59 | 60 | @Override 61 | public void restoreState() { 62 | checkBox.setSelected(isCheckMessageEnabled()); 63 | } 64 | }; 65 | } 66 | 67 | public static boolean isCheckMessageEnabled() { 68 | return PropertiesComponent.getInstance().getBoolean(CHECKER_STATE_KEY, true); 69 | } 70 | 71 | @Override 72 | public ReturnResult beforeCheckin() { 73 | if (!isCheckMessageEnabled()) return super.beforeCheckin(); 74 | 75 | Project project = panel.getProject(); 76 | IssueNavigationConfiguration configuration = IssueNavigationConfiguration.getInstance(project); 77 | VcsRepositoryManager repoManager = VcsRepositoryManager.getInstance(project); 78 | Optional shouldCommit = repoManager.getRepositories().stream() 79 | .map(Repository::getCurrentBranchName) 80 | .filter(Objects::nonNull) 81 | .flatMap(branchName -> match(configuration, branchName)) 82 | .filter(trinity -> trinity.first.find()) 83 | .map(trinity -> Trinity.create(trinity.first.group(), trinity.second, trinity.third)) 84 | .findFirst() 85 | .map(branchMatch -> findReferenceInMessage(branchMatch, project)); 86 | return shouldCommit.orElse(true) ? ReturnResult.COMMIT : ReturnResult.CANCEL; 87 | } 88 | 89 | private Stream> match(IssueNavigationConfiguration configuration, String branch) { 90 | return configuration.getLinks().stream() 91 | .map(link -> { 92 | Pattern pattern = link.getIssuePattern(); 93 | Matcher matcher = pattern.matcher(branch); 94 | return Trinity.create(matcher, pattern, branch); 95 | }); 96 | } 97 | 98 | private boolean findReferenceInMessage(Trinity branchMatch, Project project) { 99 | String commitMessage = panel.getCommitMessage(); 100 | Pattern pattern = branchMatch.second; 101 | Matcher matcher = pattern.matcher(commitMessage); 102 | String issueReferenceFromBranchName = branchMatch.first; 103 | while (matcher.find()) { 104 | if (matcher.group().equals(issueReferenceFromBranchName)) return true; 105 | } 106 | 107 | String branchName = branchMatch.third; 108 | String html = "" + 109 | "Commit message doesn't contain reference to the issue " + formatTextWithLinks(project, issueReferenceFromBranchName) + "
" + 110 | "Current branch name: " + XmlStringUtil.escapeString(branchName) + "
" + 111 | "
" + 112 | "Are you sure to commit as is?" + 113 | ""; 114 | int yesNo = Messages.showYesNoDialog(html, 115 | "Missing Issue Reference", 116 | UIUtil.getWarningIcon()); 117 | return yesNo == Messages.YES; 118 | } 119 | } 120 | --------------------------------------------------------------------------------