blob: 47fae2b883399004e233cdb8ac5b0d3e3977a64b [file] [log] [blame]
/*
* Copyright 2000-2014 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.openapi.vcs.changes.ui;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.DataSink;
import com.intellij.openapi.actionSystem.TypeSafeDataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.*;
import com.intellij.openapi.vcs.changes.actions.DiffExtendUIFactory;
import com.intellij.openapi.vcs.checkin.*;
import com.intellij.openapi.vcs.impl.CheckinHandlersManager;
import com.intellij.openapi.vcs.impl.VcsGlobalMessageManager;
import com.intellij.openapi.vcs.ui.CommitMessage;
import com.intellij.openapi.vcs.ui.Refreshable;
import com.intellij.openapi.vcs.ui.RefreshableOnComponent;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.SplitterWithSecondHideable;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.OnOffListener;
import com.intellij.util.containers.ContainerUtilRt;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.*;
import java.util.List;
/**
* @author max
*/
public class CommitChangeListDialog extends DialogWrapper implements CheckinProjectPanel, TypeSafeDataProvider {
private final static String outCommitHelpId = "reference.dialogs.vcs.commit";
private static final int LAYOUT_VERSION = 2;
private final CommitContext myCommitContext;
private final CommitMessage myCommitMessageArea;
private Splitter mySplitter;
private final JPanel myAdditionalOptionsPanel;
private final ChangesBrowser myBrowser;
private final ChangesBrowserExtender myBrowserExtender;
private CommitLegendPanel myLegend;
private final ShortDiffDetails myDiffDetails;
private final List<RefreshableOnComponent> myAdditionalComponents = new ArrayList<RefreshableOnComponent>();
private final List<CheckinHandler> myHandlers = new ArrayList<CheckinHandler>();
private final String myActionName;
private final Project myProject;
private final List<CommitExecutor> myExecutors;
private final Alarm myOKButtonUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
private String myLastKnownComment = "";
private final boolean myAllOfDefaultChangeListChangesIncluded;
@NonNls private static final String SPLITTER_PROPORTION_OPTION = "CommitChangeListDialog.SPLITTER_PROPORTION_" + LAYOUT_VERSION;
private final CommitExecutorAction[] myExecutorActions;
private final boolean myShowVcsCommit;
private final Map<AbstractVcs, JPanel> myPerVcsOptionsPanels = new HashMap<AbstractVcs, JPanel>();
@Nullable
private final AbstractVcs myVcs;
private final boolean myIsAlien;
private boolean myDisposed = false;
private final JLabel myWarningLabel;
private final Map<String, CheckinChangeListSpecificComponent> myCheckinChangeListSpecificComponents;
private final Map<String, String> myListComments;
private String myLastSelectedListName;
private ChangeInfoCalculator myChangesInfoCalculator;
private final PseudoMap<Object, Object> myAdditionalData;
private String myHelpId;
private SplitterWithSecondHideable myDetailsSplitter;
private static final String DETAILS_SPLITTER_PROPORTION_OPTION = "CommitChangeListDialog.DETAILS_SPLITTER_PROPORTION_" + LAYOUT_VERSION;
private static final String DETAILS_SHOW_OPTION = "CommitChangeListDialog.DETAILS_SHOW_OPTION_";
private JPanel myDetailsPanel;
private final FileAndDocumentListenersForShortDiff myListenersForShortDiff;
private final String myOkActionText;
private final ZipperUpdater myZipperUpdater;
private final Runnable myRefreshDetails;
private CommitAction myCommitAction;
@Nullable private CommitResultHandler myResultHandler;
private static class MyUpdateButtonsRunnable implements Runnable {
private CommitChangeListDialog myDialog;
private MyUpdateButtonsRunnable(final CommitChangeListDialog dialog) {
myDialog = dialog;
}
public void cancel() {
myDialog = null;
}
@Override
public void run() {
if (myDialog != null) {
myDialog.updateButtons();
myDialog.updateLegend();
}
}
public void restart(final CommitChangeListDialog dialog) {
myDialog = dialog;
run();
}
}
private final MyUpdateButtonsRunnable myUpdateButtonsRunnable = new MyUpdateButtonsRunnable(this);
public static boolean commitChanges(final Project project, final List<Change> changes, final LocalChangeList initialSelection,
final List<CommitExecutor> executors, final boolean showVcsCommit, final String comment,
@Nullable CommitResultHandler customResultHandler, boolean cancelIfNoChanges) {
if (cancelIfNoChanges && changes.isEmpty() && !ApplicationManager.getApplication().isUnitTestMode()) {
Messages.showInfoMessage(project, VcsBundle.message("commit.dialog.no.changes.detected.text"),
VcsBundle.message("commit.dialog.no.changes.detected.title"));
return false;
}
for (BaseCheckinHandlerFactory factory : getCheckInFactories(project)) {
BeforeCheckinDialogHandler handler = factory.createSystemReadyHandler(project);
if (handler != null && !handler.beforeCommitDialogShown(project, changes, executors, showVcsCommit)) {
return false;
}
}
final ChangeListManager manager = ChangeListManager.getInstance(project);
CommitChangeListDialog dialog = new CommitChangeListDialog(project, changes, initialSelection, executors, showVcsCommit, manager.getDefaultChangeList(), manager.getChangeListsCopy(), null,
false, comment, customResultHandler);
if (!ApplicationManager.getApplication().isUnitTestMode()) {
dialog.show();
}
else {
dialog.doOKAction();
}
return dialog.isOK();
}
private static List<BaseCheckinHandlerFactory> getCheckInFactories(Project project) {
return CheckinHandlersManager.getInstance().getRegisteredCheckinHandlerFactories(
ProjectLevelVcsManager.getInstance(project).getAllActiveVcss());
}
@NotNull
public List<RefreshableOnComponent> getAdditionalComponents() {
return Collections.unmodifiableList(myAdditionalComponents);
}
public static void commitPaths(final Project project, Collection<FilePath> paths, final LocalChangeList initialSelection,
@Nullable final CommitExecutor executor, final String comment) {
final ChangeListManager manager = ChangeListManager.getInstance(project);
final Collection<Change> changes = new HashSet<Change>();
for (FilePath path : paths) {
changes.addAll(manager.getChangesIn(path));
}
commitChanges(project, changes, initialSelection, executor, comment);
}
public static boolean commitChanges(final Project project, final Collection<Change> changes, final LocalChangeList initialSelection,
@Nullable final CommitExecutor executor, final String comment) {
if (executor == null) {
return commitChanges(project, changes, initialSelection, collectExecutors(project, changes), true, comment, null);
}
else {
return commitChanges(project, changes, initialSelection, Collections.singletonList(executor), false, comment, null);
}
}
public static List<CommitExecutor> collectExecutors(Project project, Collection<Change> changes) {
List<CommitExecutor> result = new ArrayList<CommitExecutor>();
for (AbstractVcs<?> vcs : getAffectedVcses(project, changes)) {
result.addAll(vcs.getCommitExecutors());
}
result.addAll(ChangeListManager.getInstance(project).getRegisteredExecutors());
return result;
}
/**
* Shows the commit dialog, and performs the selected action: commit, commit & push, create patch, etc.
* @param customResultHandler If this is not null, after commit is completed, custom result handler is called instead of
* showing the default notification in case of commit or failure.
* @return true if user agreed to commit, false if he pressed "Cancel".
*/
public static boolean commitChanges(final Project project, final Collection<Change> changes, final LocalChangeList initialSelection,
final List<CommitExecutor> executors, final boolean showVcsCommit, final String comment,
@Nullable CommitResultHandler customResultHandler) {
return commitChanges(project, new ArrayList<Change>(changes), initialSelection, executors, showVcsCommit, comment,
customResultHandler, true);
}
public static void commitAlienChanges(final Project project, final List<Change> changes, final AbstractVcs vcs,
final String changelistName, final String comment) {
final LocalChangeList lcl = new AlienLocalChangeList(changes, changelistName);
new CommitChangeListDialog(project, changes, null, null, true, AlienLocalChangeList.DEFAULT_ALIEN, Collections.singletonList(lcl), vcs,
true, comment, null).show();
}
private CommitChangeListDialog(final Project project,
final List<Change> changes,
final LocalChangeList initialSelection,
final List<CommitExecutor> executors,
final boolean showVcsCommit, final LocalChangeList defaultChangeList,
final List<LocalChangeList> changeLists, @Nullable final AbstractVcs singleVcs, final boolean isAlien,
final String comment, @Nullable CommitResultHandler customResultHandler) {
super(project, true);
myCommitContext = new CommitContext();
myProject = project;
myExecutors = executors;
myShowVcsCommit = showVcsCommit;
myVcs = singleVcs;
myResultHandler = customResultHandler;
myListComments = new HashMap<String, String>();
myAdditionalData = new PseudoMap<Object, Object>();
myDiffDetails = new ShortDiffDetails(myProject, new Getter<Change[]>() {
@Override
public Change[] get() {
final List<Change> selectedChanges = myBrowser.getViewer().getSelectedChanges();
return selectedChanges.toArray(new Change[selectedChanges.size()]);
}
}, VcsChangeDetailsManager.getInstance(myProject));
if (!myShowVcsCommit && ((myExecutors == null) || myExecutors.size() == 0)) {
throw new IllegalArgumentException("nothing found to execute commit with");
}
myAllOfDefaultChangeListChangesIncluded = new HashSet<Change>(changes).containsAll(new HashSet<Change>(defaultChangeList.getChanges()));
myIsAlien = isAlien;
if (isAlien) {
AlienChangeListBrowser browser = new AlienChangeListBrowser(project, changeLists, changes, initialSelection, true, true, singleVcs);
myBrowser = browser;
myBrowserExtender = browser;
} else {
MultipleChangeListBrowser browser = new MultipleChangeListBrowser(project, changeLists, changes, getDisposable(), initialSelection, true, true,
new Runnable() {
@Override
public void run() {
updateWarning();
}
},
new Runnable() {
@Override
public void run() {
for (CheckinHandler handler : myHandlers) {
handler.includedChangesChanged();
}
}
}) {
@Override
protected void afterDiffRefresh() {
myBrowser.rebuildList();
myBrowser.setDataIsDirty(false);
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
IdeFocusManager.findInstance().requestFocus(myBrowser.getViewer().getPreferredFocusedComponent(), true);
}
});
}
};
myBrowser = browser;
myBrowser.setAlwayExpandList(false);
myBrowserExtender = browser.getExtender();
}
myDiffDetails.setParent(myBrowser);
myZipperUpdater = new ZipperUpdater(30, Alarm.ThreadToUse.SWING_THREAD, getDisposable());
myRefreshDetails = new Runnable() {
@Override
public void run() {
myDiffDetails.refresh();
}
};
myListenersForShortDiff = new FileAndDocumentListenersForShortDiff(myDiffDetails) {
@Override
protected void updateDetails() {
myZipperUpdater.queue(myRefreshDetails);
}
@Override
protected boolean updateSynchronously() {
return false;
}
};
myListenersForShortDiff.on();
myBrowser.getViewer().addSelectionListener(new Runnable() {
@Override
public void run() {
myZipperUpdater.queue(myRefreshDetails);
}
});
myBrowserExtender.addToolbarActions(this);
myBrowserExtender.addSelectedListChangeListener(new SelectedListChangeListener() {
@Override
public void selectedListChanged() {
updateOnListSelection();
}
});
myBrowser.setDiffExtendUIFactory(new DiffExtendUIFactory() {
@Override
public List<? extends AnAction> createActions(final Change change) {
return myBrowser.createDiffActions(change);
}
@Override
@Nullable
public JComponent createBottomComponent() {
return new DiffCommitMessageEditor(CommitChangeListDialog.this);
}
});
myCommitMessageArea = new CommitMessage(project);
if (!VcsConfiguration.getInstance(project).CLEAR_INITIAL_COMMIT_MESSAGE) {
setComment(project, initialSelection, comment);
}
myActionName = VcsBundle.message("commit.dialog.title");
myAdditionalOptionsPanel = new JPanel();
myAdditionalOptionsPanel.setLayout(new BorderLayout());
Box optionsBox = Box.createVerticalBox();
boolean hasVcsOptions = false;
Box vcsCommitOptions = Box.createVerticalBox();
final List<AbstractVcs> vcses = new ArrayList<AbstractVcs>(getAffectedVcses());
Collections.sort(vcses, new Comparator<AbstractVcs>() {
@Override
public int compare(AbstractVcs o1, AbstractVcs o2) {
return o1.getKeyInstanceMethod().getName().compareToIgnoreCase(o2.getKeyInstanceMethod().getName());
}
});
myCheckinChangeListSpecificComponents = new HashMap<String, CheckinChangeListSpecificComponent>();
for (AbstractVcs vcs : vcses) {
final CheckinEnvironment checkinEnvironment = vcs.getCheckinEnvironment();
if (checkinEnvironment != null) {
final RefreshableOnComponent options = checkinEnvironment.createAdditionalOptionsPanel(this, myAdditionalData);
if (options != null) {
JPanel vcsOptions = new JPanel(new BorderLayout());
vcsOptions.add(options.getComponent(), BorderLayout.CENTER);
vcsOptions.setBorder(IdeBorderFactory.createTitledBorder(vcs.getDisplayName(), true));
vcsCommitOptions.add(vcsOptions);
myPerVcsOptionsPanels.put(vcs, vcsOptions);
myAdditionalComponents.add(options);
if (options instanceof CheckinChangeListSpecificComponent) {
myCheckinChangeListSpecificComponents.put(vcs.getName(), (CheckinChangeListSpecificComponent) options);
}
hasVcsOptions = true;
}
}
}
if (hasVcsOptions) {
vcsCommitOptions.add(Box.createVerticalGlue());
optionsBox.add(vcsCommitOptions);
}
boolean beforeVisible = false;
boolean afterVisible = false;
Box beforeBox = Box.createVerticalBox();
Box afterBox = Box.createVerticalBox();
for (BaseCheckinHandlerFactory factory : getCheckInFactories(project)) {
final CheckinHandler handler = factory.createHandler(this, myCommitContext);
if (CheckinHandler.DUMMY.equals(handler)) continue;
myHandlers.add(handler);
final RefreshableOnComponent beforePanel = handler.getBeforeCheckinConfigurationPanel();
if (beforePanel != null) {
beforeBox.add(beforePanel.getComponent());
beforeVisible = true;
myAdditionalComponents.add(beforePanel);
}
final RefreshableOnComponent afterPanel = handler.getAfterCheckinConfigurationPanel(getDisposable());
if (afterPanel != null) {
afterBox.add(afterPanel.getComponent());
afterVisible = true;
myAdditionalComponents.add(afterPanel);
}
}
final String actionName = getCommitActionName();
final String borderTitleName = actionName.replace("_", "").replace("&", "");
if (beforeVisible) {
beforeBox.add(Box.createVerticalGlue());
JPanel beforePanel = new JPanel(new BorderLayout());
beforePanel.add(beforeBox);
beforePanel.setBorder(IdeBorderFactory.createTitledBorder(
VcsBundle.message("border.standard.checkin.options.group", borderTitleName), true));
optionsBox.add(beforePanel);
}
if (afterVisible) {
afterBox.add(Box.createVerticalGlue());
JPanel afterPanel = new JPanel(new BorderLayout());
afterPanel.add(afterBox);
afterPanel.setBorder(IdeBorderFactory.createTitledBorder(
VcsBundle.message("border.standard.after.checkin.options.group", borderTitleName), true));
optionsBox.add(afterPanel);
}
if (hasVcsOptions || beforeVisible || afterVisible) {
optionsBox.add(Box.createVerticalGlue());
myAdditionalOptionsPanel.add(optionsBox, BorderLayout.NORTH);
}
myOkActionText = actionName;
if (myShowVcsCommit) {
setTitle(myActionName);
}
else {
setTitle(trimEllipsis(myExecutors.get(0).getActionText()));
}
restoreState();
if (myExecutors != null) {
myExecutorActions = new CommitExecutorAction[myExecutors.size()];
for (int i = 0; i < myExecutors.size(); i++) {
final CommitExecutor commitExecutor = myExecutors.get(i);
myExecutorActions[i] = new CommitExecutorAction(commitExecutor, i == 0 && !myShowVcsCommit);
}
} else {
myExecutorActions = null;
}
myWarningLabel = new JLabel();
myWarningLabel.setUI(new MultiLineLabelUI());
myWarningLabel.setForeground(Color.red);
updateWarning();
init();
updateButtons();
updateVcsOptionsVisibility();
updateOnListSelection();
myCommitMessageArea.requestFocusInMessage();
for (EditChangelistSupport support : Extensions.getExtensions(EditChangelistSupport.EP_NAME, project)) {
support.installSearch(myCommitMessageArea.getEditorField(), myCommitMessageArea.getEditorField());
}
showDetailsIfSaved();
}
private void setComment(Project project, LocalChangeList initialSelection, String comment) {
if (comment != null) {
setCommitMessage(comment);
myLastKnownComment = comment;
myLastSelectedListName = initialSelection == null ? myBrowser.getSelectedChangeList().getName() : initialSelection.getName();
} else {
updateComment();
if (StringUtil.isEmptyOrSpaces(myCommitMessageArea.getComment())) {
setCommitMessage(VcsConfiguration.getInstance(project).LAST_COMMIT_MESSAGE);
final String messageFromVcs = getInitialMessageFromVcs();
if (messageFromVcs != null) {
myCommitMessageArea.setText(messageFromVcs);
}
}
}
}
private void showDetailsIfSaved() {
String value = PropertiesComponent.getInstance().getValue(DETAILS_SHOW_OPTION);
if (value != null) {
Boolean asBoolean = Boolean.valueOf(value);
if (Boolean.TRUE.equals(asBoolean)) {
myDetailsSplitter.initOn();
}
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
myZipperUpdater.queue(myRefreshDetails);
}
});
}
private void updateOnListSelection() {
updateComment();
updateVcsOptionsVisibility();
for (CheckinChangeListSpecificComponent component : myCheckinChangeListSpecificComponents.values()) {
component.onChangeListSelected((LocalChangeList) myBrowser.getSelectedChangeList());
}
}
private void updateWarning() {
// check for null since can be called from constructor before field initialization
if (myWarningLabel != null) {
myWarningLabel.setVisible(false);
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
final VcsException updateException = ((ChangeListManagerImpl)ChangeListManager.getInstance(myProject)).getUpdateException();
if (updateException != null) {
final String[] messages = updateException.getMessages();
if (messages != null && messages.length > 0) {
final String message = messages[0];
myWarningLabel.setText("Warning: not all local changes may be shown due to an error: " + message);
myWarningLabel.setVisible(true);
}
}
}
}
private void updateVcsOptionsVisibility() {
Collection<AbstractVcs> affectedVcses = getAffectedVcses(myProject, myBrowser.getSelectedChangeList().getChanges());
for (Map.Entry<AbstractVcs, JPanel> entry : myPerVcsOptionsPanels.entrySet()) {
entry.getValue().setVisible(affectedVcses.contains(entry.getKey()));
}
}
@Override
protected String getHelpId() {
return myHelpId;
}
private class CommitAction extends AbstractAction implements OptionAction {
private Action[] myOptions = new Action[0];
private CommitAction() {
super(myOkActionText);
putValue(DEFAULT_ACTION, Boolean.TRUE);
}
@Override
public void actionPerformed(ActionEvent e) {
doOKAction();
}
@NotNull
@Override
public Action[] getOptions() {
return myOptions;
}
public void setOptions(Action[] actions) {
myOptions = actions;
}
}
@Override
protected void doOKAction() {
if (!saveDialogState()) return;
saveComments(true);
final DefaultListCleaner defaultListCleaner = new DefaultListCleaner();
final Runnable callCommit = new Runnable() {
@Override
public void run() {
try {
CheckinHandler.ReturnResult result = runBeforeCommitHandlers(new Runnable() {
@Override
public void run() {
CommitChangeListDialog.super.doOKAction();
doCommit(myResultHandler);
}
}, null);
if (result == CheckinHandler.ReturnResult.COMMIT) {
defaultListCleaner.clean();
}
}
catch (InputException ex) {
ex.show();
}
}
};
if (myBrowser.isDataIsDirty()) {
ensureDataIsActual(callCommit);
} else {
callCommit.run();
}
}
@NotNull
@Override
protected Action getOKAction() {
return new CommitAction();
}
@Override
@NotNull
protected Action[] createActions() {
final List<Action> actions = new ArrayList<Action>();
myCommitAction = null;
if (myShowVcsCommit) {
myCommitAction = new CommitAction();
actions.add(myCommitAction);
myHelpId = outCommitHelpId;
}
if (myExecutors != null) {
if (myCommitAction != null) {
myCommitAction.setOptions(myExecutorActions);
} else {
actions.addAll(Arrays.asList(myExecutorActions));
}
for (CommitExecutor executor : myExecutors) {
if (myHelpId != null) break;
if (executor instanceof CommitExecutorWithHelp) {
myHelpId = ((CommitExecutorWithHelp) executor).getHelpId();
}
}
}
actions.add(getCancelAction());
if (myHelpId != null) {
actions.add(getHelpAction());
}
return actions.toArray(new Action[actions.size()]);
}
private void execute(final CommitExecutor commitExecutor) {
if (!saveDialogState()) return;
saveComments(true);
final CommitSession session = commitExecutor.createCommitSession();
if (session instanceof CommitSessionContextAware) {
((CommitSessionContextAware)session).setContext(myCommitContext);
}
if (session == CommitSession.VCS_COMMIT) {
doOKAction();
return;
}
boolean isOK = true;
if (SessionDialog.createConfigurationUI(session, getIncludedChanges(), getCommitMessage())!= null) {
DialogWrapper sessionDialog = new SessionDialog(commitExecutor.getActionText(),
getProject(),
session,
getIncludedChanges(),
getCommitMessage());
sessionDialog.show();
isOK = sessionDialog.isOK();
}
if (isOK) {
final DefaultListCleaner defaultListCleaner = new DefaultListCleaner();
runBeforeCommitHandlers(new Runnable() {
@Override
public void run() {
boolean success = false;
try {
final boolean completed = ProgressManager.getInstance().runProcessWithProgressSynchronously(
new Runnable() {
@Override
public void run() {
session.execute(getIncludedChanges(), getCommitMessage());
}
}, commitExecutor.getActionText(), true, getProject());
if (completed) {
for (CheckinHandler handler : myHandlers) {
handler.checkinSuccessful();
}
success = true;
defaultListCleaner.clean();
close(OK_EXIT_CODE);
}
else {
session.executionCanceled();
}
}
catch (Throwable e) {
Messages.showErrorDialog(VcsBundle.message("error.executing.commit", commitExecutor.getActionText(), e.getLocalizedMessage()),
commitExecutor.getActionText());
for (CheckinHandler handler : myHandlers) {
handler.checkinFailed(Arrays.asList(new VcsException(e)));
}
}
finally {
if (myResultHandler != null) {
if (success) {
myResultHandler.onSuccess(getCommitMessage());
}
else {
myResultHandler.onFailure();
}
}
}
}
}, commitExecutor);
}
else {
session.executionCanceled();
}
}
@Nullable
private String getInitialMessageFromVcs() {
final List<Change> list = getIncludedChanges();
final Ref<String> result = new Ref<String>();
ChangesUtil.processChangesByVcs(myProject, list, new ChangesUtil.PerVcsProcessor<Change>() {
@Override
public void process(final AbstractVcs vcs, final List<Change> items) {
if (result.isNull()) {
CheckinEnvironment checkinEnvironment = vcs.getCheckinEnvironment();
if (checkinEnvironment != null) {
final Collection<FilePath> paths = ChangesUtil.getPaths(items);
String defaultMessage = checkinEnvironment.getDefaultMessageFor(paths.toArray(new FilePath[paths.size()]));
if (defaultMessage != null) {
result.set(defaultMessage);
}
}
}
}
});
return result.get();
}
private void saveCommentIntoChangeList() {
if (myLastSelectedListName != null) {
final String actualCommentText = myCommitMessageArea.getComment();
final String saved = myListComments.get(myLastSelectedListName);
if (! Comparing.equal(saved, actualCommentText)) {
myListComments.put(myLastSelectedListName, actualCommentText);
}
}
}
private static boolean isDefaultList(final LocalChangeList list) {
return VcsBundle.message("changes.default.changelist.name").equals(list.getName());
}
private void updateComment() {
if (VcsConfiguration.getInstance(getProject()).CLEAR_INITIAL_COMMIT_MESSAGE) return;
final LocalChangeList list = (LocalChangeList) myBrowser.getSelectedChangeList();
if (list == null || (list.getName().equals(myLastSelectedListName))) {
return;
} else if (myLastSelectedListName != null) {
saveCommentIntoChangeList();
}
myLastSelectedListName = list.getName();
String listComment = list.getComment();
if (StringUtil.isEmptyOrSpaces(listComment)) {
final String listTitle = list.getName();
if (! isDefaultList(list)) {
listComment = listTitle;
}
else {
// use last know comment; it is already stored in list
listComment = myLastKnownComment;
}
}
myCommitMessageArea.setText(listComment);
}
@Override
public void dispose() {
myDisposed = true;
myBrowser.dispose();
Disposer.dispose(myCommitMessageArea);
Disposer.dispose(myOKButtonUpdateAlarm);
myUpdateButtonsRunnable.cancel();
myListenersForShortDiff.off();
super.dispose();
Disposer.dispose(myDiffDetails);
PropertiesComponent.getInstance().setValue(SPLITTER_PROPORTION_OPTION, String.valueOf(mySplitter.getProportion()));
float usedProportion = myDetailsSplitter.getUsedProportion();
if (usedProportion > 0) {
PropertiesComponent.getInstance().setValue(DETAILS_SPLITTER_PROPORTION_OPTION, String.valueOf(usedProportion));
}
PropertiesComponent.getInstance().setValue(DETAILS_SHOW_OPTION, String.valueOf(myDetailsSplitter.isOn()));
}
@Override
public String getCommitActionName() {
String name = null;
for (AbstractVcs vcs : getAffectedVcses()) {
final CheckinEnvironment checkinEnvironment = vcs.getCheckinEnvironment();
if (name == null && checkinEnvironment != null) {
name = checkinEnvironment.getCheckinOperationName();
}
else {
name = VcsBundle.getString("commit.dialog.default.commit.operation.name");
}
}
return name != null ? name : VcsBundle.getString("commit.dialog.default.commit.operation.name");
}
@Override
public boolean isCheckSpelling() {
VcsConfiguration configuration = VcsConfiguration.getInstance(myProject);
return configuration == null || configuration.CHECK_COMMIT_MESSAGE_SPELLING;
}
@Override
public void setCheckSpelling(boolean checkSpelling) {
VcsConfiguration configuration = VcsConfiguration.getInstance(myProject);
if (configuration != null) {
configuration.CHECK_COMMIT_MESSAGE_SPELLING = checkSpelling;
}
myCommitMessageArea.setCheckSpelling(checkSpelling);
}
private boolean checkComment() {
if (VcsConfiguration.getInstance(myProject).FORCE_NON_EMPTY_COMMENT && (getCommitMessage().length() == 0)) {
int requestForCheckin = Messages.showYesNoDialog(VcsBundle.message("confirmation.text.check.in.with.empty.comment"),
VcsBundle.message("confirmation.title.check.in.with.empty.comment"),
Messages.getWarningIcon());
return requestForCheckin == Messages.YES;
}
else {
return true;
}
}
private void stopUpdate() {
myDisposed = true;
myUpdateButtonsRunnable.cancel();
}
private void restartUpdate() {
myDisposed = false;
myUpdateButtonsRunnable.restart(this);
}
private CheckinHandler.ReturnResult runBeforeCommitHandlers(final Runnable okAction, final CommitExecutor executor) {
final Computable<CheckinHandler.ReturnResult> proceedRunnable = new Computable<CheckinHandler.ReturnResult>() {
@Override
public CheckinHandler.ReturnResult compute() {
FileDocumentManager.getInstance().saveAllDocuments();
for (CheckinHandler handler : myHandlers) {
if (!(handler.acceptExecutor(executor))) continue;
final CheckinHandler.ReturnResult result = handler.beforeCheckin(executor, myAdditionalData);
if (result == CheckinHandler.ReturnResult.COMMIT) continue;
if (result == CheckinHandler.ReturnResult.CANCEL) {
restartUpdate();
return CheckinHandler.ReturnResult.CANCEL;
}
if (result == CheckinHandler.ReturnResult.CLOSE_WINDOW) {
final ChangeList changeList = myBrowser.getSelectedChangeList();
CommitHelper.moveToFailedList(changeList,
getCommitMessage(),
getIncludedChanges(),
VcsBundle.message("commit.dialog.rejected.commit.template", changeList.getName()),
myProject);
doCancelAction();
return CheckinHandler.ReturnResult.CLOSE_WINDOW;
}
}
okAction.run();
return CheckinHandler.ReturnResult.COMMIT;
}
};
stopUpdate();
final Ref<CheckinHandler.ReturnResult> compoundResultRef = Ref.create();
Runnable runnable = new Runnable() {
@Override
public void run() {
compoundResultRef.set(proceedRunnable.compute());
}
};
for(final CheckinHandler handler: myHandlers) {
if (handler instanceof CheckinMetaHandler) {
final Runnable previousRunnable = runnable;
runnable = new Runnable() {
@Override
public void run() {
((CheckinMetaHandler)handler).runCheckinHandlers(previousRunnable);
}
};
}
}
runnable.run();
return compoundResultRef.get();
}
private boolean saveDialogState() {
if (!checkComment()) {
return false;
}
saveCommentIntoChangeList();
VcsConfiguration.getInstance(myProject).saveCommitMessage(getCommitMessage());
try {
saveState();
}
catch(InputException ex) {
ex.show();
return false;
}
return true;
}
private class DefaultListCleaner {
private final boolean myToClean;
private DefaultListCleaner() {
final int selectedSize = getIncludedChanges().size();
final ChangeList selectedList = myBrowser.getSelectedChangeList();
final int totalSize = selectedList.getChanges().size();
myToClean = (totalSize == selectedSize) && (isDefaultList((LocalChangeList) selectedList));
}
void clean() {
if (myToClean) {
final ChangeListManager clManager = ChangeListManager.getInstance(myProject);
clManager.editComment(VcsBundle.message("changes.default.changelist.name"), "");
}
}
}
private void saveComments(final boolean isOk) {
final ChangeListManager clManager = ChangeListManager.getInstance(myProject);
if (isOk) {
final int selectedSize = getIncludedChanges().size();
final ChangeList selectedList = myBrowser.getSelectedChangeList();
final int totalSize = selectedList.getChanges().size();
if (totalSize > selectedSize) {
myListComments.remove(myLastSelectedListName);
}
}
for (Map.Entry<String, String> entry : myListComments.entrySet()) {
final String name = entry.getKey();
final String value = entry.getValue();
clManager.editComment(name, value);
}
}
@Override
public void doCancelAction() {
for (CheckinChangeListSpecificComponent component : myCheckinChangeListSpecificComponents.values()) {
component.saveState();
}
saveCommentIntoChangeList();
saveComments(false);
//VcsConfiguration.getInstance(myProject).saveCommitMessage(getCommitMessage());
super.doCancelAction();
}
private void doCommit(@Nullable CommitResultHandler customResultHandler) {
final CommitHelper helper = new CommitHelper(
myProject,
myBrowser.getSelectedChangeList(),
getIncludedChanges(),
myActionName,
getCommitMessage(),
myHandlers,
myAllOfDefaultChangeListChangesIncluded, false, myAdditionalData, customResultHandler);
if (myIsAlien) {
helper.doAlienCommit(myVcs);
} else {
helper.doCommit();
}
}
@Nullable
@Override
protected JComponent createNorthPanel() {
final JComponent banner = VcsGlobalMessageManager.getInstance(myProject).getMessageBanner();
return banner != null ? banner : super.createNorthPanel();
}
@Override
@Nullable
protected JComponent createCenterPanel() {
JPanel rootPane = new JPanel(new BorderLayout());
mySplitter = new Splitter(true);
mySplitter.setHonorComponentsMinimumSize(true);
mySplitter.setFirstComponent(myBrowser);
mySplitter.setSecondComponent(myCommitMessageArea);
initMainSplitter();
rootPane.add(mySplitter, BorderLayout.CENTER);
myChangesInfoCalculator = new ChangeInfoCalculator();
myLegend = new CommitLegendPanel(myChangesInfoCalculator);
JPanel legendPanel = new JPanel(new BorderLayout());
legendPanel.add(myLegend.getComponent(), BorderLayout.EAST);
myBrowser.getBottomPanel().add(legendPanel, BorderLayout.SOUTH);
JPanel infoPanel = new JPanel(new BorderLayout());
infoPanel.add(myAdditionalOptionsPanel, BorderLayout.CENTER);
rootPane.add(infoPanel, BorderLayout.EAST);
infoPanel.setBorder(IdeBorderFactory.createEmptyBorder(0, 10, 0, 0));
final JPanel wrapper = new JPanel(new GridBagLayout());
final GridBagConstraints gb = new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0);
final JPanel panel = new JPanel(new BorderLayout());
panel.add(wrapper, BorderLayout.WEST);
rootPane.add(panel, BorderLayout.SOUTH);
myWarningLabel.setBorder(BorderFactory.createEmptyBorder(5,5,0,5));
wrapper.add(myWarningLabel, gb);
myDetailsSplitter = new SplitterWithSecondHideable(true, "Details", rootPane,
new OnOffListener<Integer>() {
@Override
public void on(Integer integer) {
if (integer == 0) return;
mySplitter.skipNextLayouting();
myDetailsSplitter.getComponent().skipNextLayouting();
final Dimension dialogSize = getSize();
setSize(dialogSize.width, dialogSize.height + integer);
repaint();
}
@Override
public void off(Integer integer) {
if (integer == 0) return;
mySplitter.skipNextLayouting();
myDetailsSplitter.getComponent().skipNextLayouting();
final Dimension dialogSize = getSize();
setSize(dialogSize.width, dialogSize.height - integer);
repaint();
}
}) {
@Override
protected RefreshablePanel createDetails() {
initDetails();
return myDiffDetails;
}
@Override
protected float getSplitterInitialProportion() {
float value = 0;
final String remembered = PropertiesComponent.getInstance().getValue(DETAILS_SPLITTER_PROPORTION_OPTION);
if (remembered != null) {
try {
value = Float.valueOf(remembered);
} catch (NumberFormatException e) {
//
}
}
if (value <= 0.05 || value >= 0.95) {
return 0.6f;
}
return value;
}
};
return myDetailsSplitter.getComponent();
}
private void initMainSplitter() {
final String s = PropertiesComponent.getInstance().getValue(SPLITTER_PROPORTION_OPTION);
if (s != null) {
try {
mySplitter.setProportion(Float.valueOf(s).floatValue());
} catch (NumberFormatException e) {
//
}
} else {
mySplitter.setProportion(0.5f);
}
}
private void initDetails() {
if (myDetailsPanel == null) {
myDetailsPanel = myDiffDetails.getPanel();
}
}
public Collection<AbstractVcs> getAffectedVcses() {
if (! myShowVcsCommit) {
return Collections.emptySet();
}
return myBrowserExtender.getAffectedVcses();
}
private static Collection<AbstractVcs> getAffectedVcses(Project project, final Collection<Change> changes) {
Set<AbstractVcs> result = new THashSet<AbstractVcs>();
for (Change change : changes) {
ContainerUtilRt.addIfNotNull(result, ChangesUtil.getVcsForChange(change, project));
}
return result;
}
@Override
public Collection<VirtualFile> getRoots() {
Set<VirtualFile> result = new HashSet<VirtualFile>();
for (Change change : myBrowser.getCurrentDisplayedChanges()) {
final FilePath filePath = ChangesUtil.getFilePath(change);
VirtualFile root = ProjectLevelVcsManager.getInstance(myProject).getVcsRootFor(filePath);
if (root != null) {
result.add(root);
}
}
return result;
}
@Override
public JComponent getComponent() {
return mySplitter;
}
@Override
public boolean hasDiffs() {
return !getIncludedChanges().isEmpty();
}
@Override
public Collection<VirtualFile> getVirtualFiles() {
List<VirtualFile> result = new ArrayList<VirtualFile>();
for (Change change: getIncludedChanges()) {
final FilePath path = ChangesUtil.getFilePath(change);
final VirtualFile vFile = path.getVirtualFile();
if (vFile != null) {
result.add(vFile);
}
}
return result;
}
@Override
public Collection<Change> getSelectedChanges() {
return new ArrayList<Change>(getIncludedChanges());
}
@Override
public Collection<File> getFiles() {
List<File> result = new ArrayList<File>();
for (Change change: getIncludedChanges()) {
final FilePath path = ChangesUtil.getFilePath(change);
final File file = path.getIOFile();
result.add(file);
}
return result;
}
@Override
public Project getProject() {
return myProject;
}
@Override
public boolean vcsIsAffected(String name) {
// tod +- performance?
if (! ProjectLevelVcsManager.getInstance(myProject).checkVcsIsActive(name)) return false;
final Collection<AbstractVcs> affected = myBrowserExtender.getAffectedVcses();
for (AbstractVcs vcs : affected) {
if (Comparing.equal(vcs.getName(), name)) return true;
}
return false;
}
@Override
public void setCommitMessage(final String currentDescription) {
setCommitMessageText(currentDescription);
myCommitMessageArea.requestFocusInMessage();
}
public Object getContextInfo(Object object) {
// todo
return null;
}
@Override
public void setWarning(String s) {
// todo
}
private void setCommitMessageText(final String currentDescription) {
myLastKnownComment = currentDescription;
myCommitMessageArea.setText(currentDescription);
}
@Override
public String getCommitMessage() {
return myCommitMessageArea.getComment();
}
@Override
public void refresh() {
ChangeListManager.getInstance(myProject).invokeAfterUpdate(new Runnable() {
@Override
public void run() {
myBrowser.rebuildList();
for (RefreshableOnComponent component : myAdditionalComponents) {
component.refresh();
}
}
}, InvokeAfterUpdateMode.SILENT, "commit dialog", ModalityState.current()); // title not shown for silently
}
@Override
public void saveState() {
for (RefreshableOnComponent component : myAdditionalComponents) {
component.saveState();
}
}
@Override
public void restoreState() {
for (RefreshableOnComponent component : myAdditionalComponents) {
component.restoreState();
}
}
private void updateButtons() {
if (myDisposed) return;
final boolean enabled = hasDiffs();
setOKActionEnabled(enabled);
if (myCommitAction != null) {
myCommitAction.setEnabled(enabled);
}
if (myExecutorActions != null) {
for (CommitExecutorAction executorAction : myExecutorActions) {
executorAction.updateEnabled(enabled);
}
}
myOKButtonUpdateAlarm.cancelAllRequests();
myOKButtonUpdateAlarm.addRequest(myUpdateButtonsRunnable, 300, ModalityState.stateForComponent(myBrowser));
}
private void updateLegend() {
if (myDisposed) return;
myChangesInfoCalculator.update(myBrowser.getCurrentDisplayedChanges(), myBrowserExtender.getCurrentIncludedChanges());
myLegend.update();
}
@NotNull
private List<Change> getIncludedChanges() {
return myBrowserExtender.getCurrentIncludedChanges();
}
@Override
@NonNls
protected String getDimensionServiceKey() {
return "CommitChangelistDialog" + LAYOUT_VERSION;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myCommitMessageArea.getEditorField();
}
@Override
public void calcData(DataKey key, DataSink sink) {
if (key == Refreshable.PANEL_KEY) {
sink.put(Refreshable.PANEL_KEY, this);
}
else {
myBrowser.calcData(key, sink);
}
}
static String trimEllipsis(final String title) {
if (title.endsWith("...")) {
return title.substring(0, title.length() - 3);
}
else {
return title;
}
}
private void ensureDataIsActual(final Runnable runnable) {
ChangeListManager.getInstance(myProject).invokeAfterUpdate(runnable, InvokeAfterUpdateMode.SYNCHRONOUS_CANCELLABLE,
"Refreshing changelists...", ModalityState.current());
}
private class CommitExecutorAction extends AbstractAction {
private final CommitExecutor myCommitExecutor;
public CommitExecutorAction(final CommitExecutor commitExecutor, final boolean isDefault) {
super(commitExecutor.getActionText());
myCommitExecutor = commitExecutor;
if (isDefault) {
putValue(DEFAULT_ACTION, Boolean.TRUE);
}
}
@Override
public void actionPerformed(ActionEvent e) {
final Runnable callExecutor = new Runnable() {
@Override
public void run() {
execute(myCommitExecutor);
}
};
if (myBrowser.isDataIsDirty()) {
ensureDataIsActual(callExecutor);
} else {
callExecutor.run();
}
}
public void updateEnabled(boolean hasDiffs) {
setEnabled(hasDiffs
|| (myCommitExecutor instanceof CommitExecutorBase) && !((CommitExecutorBase)myCommitExecutor).areChangesRequired());
}
}
private static class DiffCommitMessageEditor extends CommitMessage implements Disposable {
private CommitChangeListDialog myCommitDialog;
public DiffCommitMessageEditor(final CommitChangeListDialog dialog) {
super(dialog.getProject());
getEditorField().setText(dialog.getCommitMessage());
myCommitDialog = dialog;
myCommitDialog.setMessageConsumer(new Consumer<String>() {
@Override
public void consume(String s) {
getEditorField().setText(s);
}
});
}
@Override
public void dispose() {
if (myCommitDialog != null) {
myCommitDialog.setMessageConsumer(null);
final String text = getEditorField().getText();
if (! Comparing.equal(myCommitDialog.getCommitMessage(), text)) {
myCommitDialog.setCommitMessage(text);
}
myCommitDialog = null;
}
}
@Override
public Dimension getPreferredSize() {
// we don't want to be squeezed to one line
return new Dimension(400, 120);
}
}
public void setMessageConsumer(Consumer<String> messageConsumer) {
myCommitMessageArea.setMessageConsumer(messageConsumer);
}
}