IDEA-119238 Git and Hg Amend Commit: amend message updated for multi-root selection changes

*last messaged for all committed repositories stored to map;
*amended message reconstructed using current selected repositories
This commit is contained in:
Nadya Zabrodina
2014-02-28 12:20:43 +04:00
parent e6d6bbb05a
commit 00e2508e86
4 changed files with 80 additions and 42 deletions
@@ -24,10 +24,13 @@ import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.CheckinProjectPanel;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FilePathImpl;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.ui.RefreshableOnComponent;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.NonFocusableCheckBox;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,8 +39,9 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.io.File;
import java.util.*;
import java.util.List;
/**
* @author Nadya Zabrodina
@@ -51,6 +55,7 @@ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComp
@Nullable private String myPreviousMessage;
@Nullable private String myAmendedMessage;
@NotNull protected final CheckinProjectPanel myCheckinPanel;
@Nullable private Map<VirtualFile, String> myMessagesForRoots;
public DvcsCommitAdditionalComponent(@NotNull final Project project, @NotNull CheckinProjectPanel panel) {
myCheckinPanel = panel;
@@ -76,14 +81,16 @@ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComp
@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 (myAmendedMessage == null) {
loadMessageInModalTask(project);
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 { // checkbox is selected not the first time
substituteCommitMessage(myAmendedMessage);
}
}
}
else {
// there was the amended message, but user has changed it => not reverting
@@ -96,6 +103,20 @@ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComp
myPanel.add(myAmend, c);
}
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 JComponent getComponent() {
return myPanel;
}
@@ -104,19 +125,15 @@ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComp
myAmend.setSelected(false);
}
private void loadMessageInModalTask(@NotNull Project project) {
private void loadMessagesInModalTask(@NotNull Project project) {
try {
String messageFromVcs =
ProgressManager.getInstance().runProcessWithProgressSynchronously(new ThrowableComputable<String, VcsException>() {
myMessagesForRoots =
ProgressManager.getInstance().runProcessWithProgressSynchronously(new ThrowableComputable<Map<VirtualFile,String>, VcsException>() {
@Override
public String compute() throws VcsException {
return getLastCommitMessage();
public Map<VirtualFile, String> compute() throws VcsException {
return getLastCommitMessages();
}
}, "Reading commit message...", false, project);
if (!StringUtil.isEmptyOrSpaces(messageFromVcs)) {
substituteCommitMessage(messageFromVcs);
myAmendedMessage = messageFromVcs;
}
}
catch (VcsException e) {
Messages.showErrorDialog(getComponent(), "Couldn't load commit message of the commit to amend.\n" + e.getMessage(),
@@ -133,24 +150,32 @@ public abstract class DvcsCommitAdditionalComponent implements RefreshableOnComp
}
@Nullable
private String getLastCommitMessage() throws VcsException {
Collection<VirtualFile> roots = getRoots();
private Map<VirtualFile, String> getLastCommitMessages() throws VcsException {
Map<VirtualFile, String> messagesForRoots = new HashMap<VirtualFile, String>();
Collection<VirtualFile> roots = myCheckinPanel.getRoots(); //all committed vcs roots, not only selected
final Ref<VcsException> exception = Ref.create();
LinkedHashSet<String> messages = ContainerUtil.newLinkedHashSet();
for (VirtualFile root : roots) {
String message = getLastCommitMessage(root);
if (message != null) {
messages.add(message);
}
messagesForRoots.put(root, message);
}
if (!exception.isNull()) {
throw exception.get();
}
return DvcsUtil.joinMessagesOrNull(messages);
return messagesForRoots;
}
@NotNull
protected abstract Collection<VirtualFile> getRoots();
private List<FilePath> getSelectedFilePaths() {
return ContainerUtil.map(myCheckinPanel.getFiles(), new Function<File, FilePath>() {
@Override
public FilePath fun(File file) {
return new FilePathImpl(file, file.isDirectory());
}
});
}
@NotNull
protected abstract Set<VirtualFile> getVcsRoots(Collection<FilePath> files);
@Nullable
protected abstract String getLastCommitMessage(@NotNull VirtualFile repo) throws VcsException;
@@ -25,7 +25,10 @@ import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.CheckinProjectPanel;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.ObjectsConvertor;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.ui.SelectFilePathsDialog;
import com.intellij.openapi.vcs.checkin.CheckinChangeListSpecificComponent;
@@ -33,7 +36,10 @@ import com.intellij.openapi.vcs.checkin.CheckinEnvironment;
import com.intellij.openapi.vcs.ui.RefreshableOnComponent;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.GuiUtils;
import com.intellij.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.FunctionUtil;
import com.intellij.util.NullableFunction;
import com.intellij.util.PairConsumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.UIUtil;
@@ -643,8 +649,8 @@ public class GitCheckinEnvironment implements CheckinEnvironment {
@Override
@NotNull
protected Set<VirtualFile> getRoots() {
return GitUtil.gitRoots(getSelectedFilePaths());
protected Set<VirtualFile> getVcsRoots(Collection<FilePath> filePaths) {
return GitUtil.gitRoots(filePaths);
}
@Nullable
@@ -665,16 +671,6 @@ public class GitCheckinEnvironment implements CheckinEnvironment {
return h.run();
}
@NotNull
private List<FilePath> getSelectedFilePaths() {
return ContainerUtil.map(myCheckinPanel.getFiles(), new Function<File, FilePath>() {
@Override
public FilePath fun(File file) {
return new FilePathImpl(file, file.isDirectory());
}
});
}
private List<String> getUsersList(final Project project) {
return NewGitUsersComponent.getInstance(project).get();
}
@@ -284,8 +284,8 @@ public class HgCheckinEnvironment implements CheckinEnvironment {
@NotNull
@Override
protected Collection<VirtualFile> getRoots() {
return HgUtil.getHgRepositories(myProject);
protected Set<VirtualFile> getVcsRoots(Collection<FilePath> filePaths) {
return HgUtil.hgRoots(myProject, filePaths);
}
@Nullable
@@ -240,6 +240,23 @@ public abstract class HgUtil {
return getNearestHgRoot(VcsUtil.getVcsRootFor(project, filePath));
}
/**
* Get hg roots for paths
*
* @param filePaths the context paths
* @return a set of git roots
*/
public static Set<VirtualFile> hgRoots(@NotNull Project project, final Collection<FilePath> filePaths) {
HashSet<VirtualFile> rc = new HashSet<VirtualFile>();
for (FilePath path : filePaths) {
final VirtualFile root = getHgRootOrNull(project, path);
if (root != null) {
rc.add(root);
}
}
return rc;
}
/**
* Gets the Mercurial root for the given file path or null if non exists:
* the root should not only be in directory mappings, but also the .hg repository folder should exist.