diff --git a/src/main/java/ch/repnik/intellij/CommitPrefixCheckinHandler.java b/src/main/java/ch/repnik/intellij/CommitPrefixCheckinHandler.java index 84cb314..22ae908 100644 --- a/src/main/java/ch/repnik/intellij/CommitPrefixCheckinHandler.java +++ b/src/main/java/ch/repnik/intellij/CommitPrefixCheckinHandler.java @@ -13,10 +13,12 @@ import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.checkin.CheckinHandler; import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl; +import com.intellij.openapi.vcs.ui.CommitMessage; import com.intellij.openapi.vcs.ui.RefreshableOnComponent; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.impl.PsiDocumentManagerImpl; import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.UIUtil; import git4idea.GitLocalBranch; import git4idea.branch.GitBranchUtil; import git4idea.repo.GitRepository; @@ -53,7 +55,7 @@ private void updateCommitMessage() { PsiDocumentManager psiInstance = PsiDocumentManager.getInstance(this.panel.getProject()); if (psiInstance instanceof PsiDocumentManagerImpl) { if (!((PsiDocumentManagerImpl) psiInstance).isCommitInProgress()) { - panel.setCommitMessage(getNewCommitMessage()); + getNewCommitMessage().ifPresent(this::setCommitMessageWithoutFocus); } else { log.info("PsiDocumentManager reported commit in progress. Skipping Git Auto Prefix"); } @@ -63,6 +65,22 @@ private void updateCommitMessage() { }); } + /** + * Sets the commit message without requesting focus. + * panel.setCommitMessage() would call CommitMessageUi.focus() (see CommitProjectPanelAdapter) + * which steals the focus e.g. from the terminal when switching branches. + */ + private void setCommitMessageWithoutFocus(String newMessage) { + CommitMessage commitMessageComponent = + UIUtil.findComponentOfType(panel.getComponent(), CommitMessage.class); + if (commitMessageComponent != null) { + commitMessageComponent.setCommitMessage(newMessage); + } else { + // Fallback for unknown CheckinProjectPanel implementations (may move focus) + panel.setCommitMessage(newMessage); + } + } + @Nullable @Override public RefreshableOnComponent getBeforeCheckinConfigurationPanel() { @@ -71,18 +89,30 @@ public RefreshableOnComponent getBeforeCheckinConfigurationPanel() { return super.getBeforeCheckinConfigurationPanel(); } - private String getNewCommitMessage() { + private Optional getNewCommitMessage() { String branchName = extractBranchName(); // log.warn("BranchName: " + branchName); - Optional ticketName = getTicket(getTicketSystem(), branchName); + return calculateNewCommitMessage(getTicketSystem(), branchName, panel.getCommitMessage(), + getWrapLeft(), getWrapRight(), getIssueKeyPosition()); + } + + static Optional calculateNewCommitMessage(TicketSystem ticketSystem, String branchName, + String currentMessage, String wrapLeft, String wrapRight, Position issueKeyPosition) { + Optional ticketName = getTicket(ticketSystem, branchName); - if (ticketName.isPresent()) { - // Sets the value for the new Panel UI - return updatePrefix(ticketName.get(), panel.getCommitMessage(), getTicketSystem(), getWrapLeft(), getWrapRight(), getIssueKeyPosition()); + if (!ticketName.isPresent()) { + return Optional.empty(); + } + + String newMessage = updatePrefix(ticketName.get(), currentMessage, ticketSystem, wrapLeft, wrapRight, issueKeyPosition); + + // CommitMessage.getText() trims trailing whitespace, so compare rTrimmed to detect real changes + if (rTrim(newMessage).equals(currentMessage == null ? "" : rTrim(currentMessage))) { + return Optional.empty(); } - return panel.getCommitMessage(); + return Optional.of(newMessage); } static Optional getTicket(TicketSystem ticketSystem, String branchName) { diff --git a/src/test/java/ch/repnik/intellij/CommitPrefixCheckinHandlerTest.java b/src/test/java/ch/repnik/intellij/CommitPrefixCheckinHandlerTest.java index 41912ba..9c9ff16 100644 --- a/src/test/java/ch/repnik/intellij/CommitPrefixCheckinHandlerTest.java +++ b/src/test/java/ch/repnik/intellij/CommitPrefixCheckinHandlerTest.java @@ -504,6 +504,45 @@ public void getTicketName_branchNameStartingWithDigit_returnsTicket(TicketNameTe } + static Stream calculateNewCommitMessage_messageUnchanged_returnsEmpty() { + CalculateMessageTester startTemplate = new CalculateMessageTester().withWrapLeft("").withWrapRight(": ").withIssueKeyPosition(Position.START); + CalculateMessageTester endTemplate = new CalculateMessageTester().withWrapLeft(": ").withWrapRight("").withIssueKeyPosition(Position.END); + + return Stream.of( + Arguments.of(startTemplate.withTicketSystem(JIRA).withBranchName("main").withCurrentMessage("Some message")), + Arguments.of(startTemplate.withTicketSystem(JIRA).withBranchName("develop").withCurrentMessage("")), + Arguments.of(startTemplate.withTicketSystem(JIRA).withBranchName("feature/ABC-1234-app-not-working").withCurrentMessage("ABC-1234: This is my text")), + Arguments.of(startTemplate.withTicketSystem(JIRA).withBranchName("feature/ABC-1234-app-not-working").withCurrentMessage("ABC-1234:")), + Arguments.of(endTemplate.withTicketSystem(JIRA).withBranchName("feature/ABC-1234-app-not-working").withCurrentMessage("This is my text: ABC-1234")), + Arguments.of(startTemplate.withTicketSystem(OTHER).withBranchName("feature/5678-app-not-working").withCurrentMessage("5678: This is my text")) + ); + } + + @ParameterizedTest + @MethodSource + public void calculateNewCommitMessage_messageUnchanged_returnsEmpty(CalculateMessageTester tester) { + tester.calculate().assertEmpty(); + } + + static Stream calculateNewCommitMessage_messageChanged_returnsNewMessage() { + CalculateMessageTester startTemplate = new CalculateMessageTester().withWrapLeft("").withWrapRight(": ").withIssueKeyPosition(Position.START); + CalculateMessageTester endTemplate = new CalculateMessageTester().withWrapLeft(": ").withWrapRight("").withIssueKeyPosition(Position.END); + + return Stream.of( + Arguments.of(startTemplate.withTicketSystem(JIRA).withBranchName("feature/ABC-1234-app-not-working").withCurrentMessage(null), "ABC-1234: "), + Arguments.of(startTemplate.withTicketSystem(JIRA).withBranchName("feature/ABC-1234-app-not-working").withCurrentMessage("XYXY-837292: This is my text"), "ABC-1234: This is my text"), + Arguments.of(endTemplate.withTicketSystem(JIRA).withBranchName("feature/ABC-1234-app-not-working").withCurrentMessage("This is my text: XYXY-837292"), "This is my text: ABC-1234"), + Arguments.of(startTemplate.withTicketSystem(OTHER).withBranchName("feature/5678-app-not-working").withCurrentMessage("1111: This is my text"), "5678: This is my text") + ); + } + + @ParameterizedTest + @MethodSource + public void calculateNewCommitMessage_messageChanged_returnsNewMessage(CalculateMessageTester tester, String expectedMessage) { + tester.calculate().assertMessage(expectedMessage); + } + + @With @AllArgsConstructor @@ -572,4 +611,45 @@ void doAssertion(String expectedTicketName) { assertThat(actualTicketName.get(), is(expectedTicketName)); } } + + + @With + @AllArgsConstructor + @NoArgsConstructor + static class CalculateMessageTester { + + private TicketSystem ticketSystem; + private String branchName; + private String currentMessage; + private String wrapLeft; + private String wrapRight; + private Position issueKeyPosition; + + CalculateMessageAsserter calculate() { + Optional newMessage = + CommitPrefixCheckinHandler.calculateNewCommitMessage( + ticketSystem, branchName, currentMessage, wrapLeft, wrapRight, issueKeyPosition); + return new CalculateMessageAsserter(newMessage); + } + + @Override + public String toString() { + return String.format("%s, %s, %s", ticketSystem.toString(), branchName, currentMessage); + } + } + + @RequiredArgsConstructor + static class CalculateMessageAsserter { + + private final Optional actualMessage; + + void assertEmpty() { + assertThat(actualMessage.isPresent(), is(false)); + } + + void assertMessage(String expectedMessage) { + assertThat(actualMessage.isPresent(), is(true)); + assertThat(actualMessage.get(), is(expectedMessage)); + } + } }