/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.dvcs; import com.intellij.dvcs.repo.Repository; import com.intellij.dvcs.repo.RepositoryManager; import com.intellij.dvcs.ui.DvcsBundle; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.CheckinProjectPanel; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsConfiguration; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.NonFocusableCheckBox; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.*; import java.util.List; import java.util.stream.Collectors; /** * Provides a checkbox to amend current commit to the previous commit. * Selecting a checkbox loads the previous commit message from the provider, and substitutes current message in the editor, * unless it was already modified by user. */ public abstract class AmendComponent { private static final Logger LOG = Logger.getInstance(AmendComponent.class); @NotNull private final RepositoryManager<? extends Repository> myRepoManager; @NotNull private final CheckinProjectPanel myCheckinPanel; @NotNull private final JCheckBox myAmend; @NotNull private final String myPreviousMessage; @Nullable private Map<VirtualFile, String> myMessagesForRoots; @Nullable private String myAmendedMessage; public AmendComponent(@NotNull Project project, @NotNull RepositoryManager<? extends Repository> repoManager, @NotNull CheckinProjectPanel panel) { this(project, repoManager, panel, DvcsBundle.message("commit.amend")); } public AmendComponent(@NotNull Project project, @NotNull RepositoryManager<? extends Repository> repoManager, @NotNull CheckinProjectPanel panel, @NotNull String title) { myRepoManager = repoManager; myCheckinPanel = panel; myAmend = new NonFocusableCheckBox(title); myAmend.setMnemonic('m'); myAmend.setToolTipText(DvcsBundle.message("commit.amend.tooltip")); myPreviousMessage = myCheckinPanel.getCommitMessage(); myAmend.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (myAmend.isSelected()) { if (myPreviousMessage.equals(myCheckinPanel.getCommitMessage())) { // if user has already typed something, don't revert it if (myMessagesForRoots == null) { loadMessagesInModalTask(project); // load all commit messages for all repositories } String message = constructAmendedMessage(); if (!StringUtil.isEmptyOrSpaces(message)) { myAmendedMessage = message; substituteCommitMessage(myAmendedMessage); } } } else { // there was the amended message, but user has changed it => not reverting if (myCheckinPanel.getCommitMessage().equals(myAmendedMessage)) { myCheckinPanel.setCommitMessage(myPreviousMessage); } } } }); } @Nullable private String constructAmendedMessage() { Set<VirtualFile> selectedRoots = getVcsRoots(getSelectedFilePaths()); // get only selected files LinkedHashSet<String> messages = ContainerUtil.newLinkedHashSet(); if (myMessagesForRoots != null) { for (VirtualFile root : selectedRoots) { String message = myMessagesForRoots.get(root); if (message != null) { messages.add(message); } } } return DvcsUtil.joinMessagesOrNull(messages); } public void refresh() { myAmend.setSelected(false); } @NotNull public Component getComponent() { return myAmend; } @NotNull public JCheckBox getCheckBox() { return myAmend; } private void loadMessagesInModalTask(@NotNull Project project) { try { myMessagesForRoots = ProgressManager.getInstance().runProcessWithProgressSynchronously(this::getLastCommitMessages, "Reading Commit Message...", true, project); } catch (VcsException e) { Messages.showErrorDialog(project, "Couldn't load commit message of the commit to amend.\n" + e.getMessage(), "Commit Message not Loaded"); LOG.info(e); } } private void substituteCommitMessage(@NotNull String newMessage) { if (!StringUtil.equalsIgnoreWhitespaces(myPreviousMessage, newMessage)) { VcsConfiguration.getInstance(myCheckinPanel.getProject()).saveCommitMessage(myPreviousMessage); myCheckinPanel.setCommitMessage(newMessage); } } @Nullable private Map<VirtualFile, String> getLastCommitMessages() throws VcsException { Map<VirtualFile, String> messagesForRoots = new HashMap<>(); // load all vcs roots visible in the commit dialog (not only selected ones), to avoid another loading task if selection changes for (VirtualFile root : getAffectedRoots()) { String message = getLastCommitMessage(root); messagesForRoots.put(root, message); } return messagesForRoots; } @NotNull protected Collection<VirtualFile> getAffectedRoots() { return myRepoManager.getRepositories().stream(). filter(repo -> !repo.isFresh()). map(Repository::getRoot). filter(root -> myCheckinPanel.getRoots().contains(root)). collect(Collectors.toList()); } @NotNull private List<FilePath> getSelectedFilePaths() { return ContainerUtil.map(myCheckinPanel.getFiles(), VcsUtil::getFilePath); } @NotNull protected abstract Set<VirtualFile> getVcsRoots(@NotNull Collection<FilePath> files); @Nullable protected abstract String getLastCommitMessage(@NotNull VirtualFile repo) throws VcsException; public boolean isAmend() { return myAmend.isSelected(); } }