IDEA-89754 Add a pre-commit check on core.autocrlf option and warn if CRLF is about to be committed to the repository. Step #1.

* Add another check to the GitCheckinHandler (windows only).
* Add the option to disable the check to the settings (visible for windows only).
* Add the dialog warning about CRLFs: GitCrlfDialog
* Declare the actual GitCrlfProblemsDetector - just as a stub now.
This commit is contained in:
Kirill Likhodedov
2012-08-08 14:10:20 +04:00
parent d2603f8fe5
commit eec045aa61
7 changed files with 244 additions and 14 deletions
@@ -19,6 +19,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.CheckinProjectPanel;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
@@ -28,11 +29,15 @@ import com.intellij.openapi.vcs.checkin.CheckinHandler;
import com.intellij.openapi.vcs.checkin.VcsCheckinHandlerFactory;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PairConsumer;
import com.intellij.util.ui.UIUtil;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.config.GitConfigUtil;
import git4idea.config.GitVcsSettings;
import git4idea.config.GitVersion;
import git4idea.config.GitVersionSpecialty;
import git4idea.crlf.GitCrlfDialog;
import git4idea.crlf.GitCrlfProblemsDetector;
import git4idea.i18n.GitBundle;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
@@ -60,10 +65,13 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory {
}
private class MyCheckinHandler extends CheckinHandler {
private CheckinProjectPanel myPanel;
@NotNull private final CheckinProjectPanel myPanel;
@NotNull private final Project myProject;
public MyCheckinHandler(CheckinProjectPanel panel) {
public MyCheckinHandler(@NotNull CheckinProjectPanel panel) {
myPanel = panel;
myProject = myPanel.getProject();
}
@Override
@@ -77,11 +85,61 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory {
if (result != ReturnResult.COMMIT) {
return result;
}
result = warnAboutCrlfIfNeeded();
if (result != ReturnResult.COMMIT) {
return result;
}
return warnAboutDetachedHeadIfNeeded();
}
return ReturnResult.COMMIT;
}
@NotNull
private ReturnResult warnAboutCrlfIfNeeded() {
GitVcsSettings settings = GitVcsSettings.getInstance(myProject);
if (!SystemInfo.isWindows || !settings.warnAboutCrlf()) {
return ReturnResult.COMMIT;
}
GitCrlfProblemsDetector crlfHelper = GitCrlfProblemsDetector.detect(myPanel.getVirtualFiles());
if (crlfHelper.shouldWarn()) {
final GitCrlfDialog dialog = new GitCrlfDialog(myProject);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
dialog.show();
}
});
int decision = dialog.getExitCode();
if (decision == GitCrlfDialog.CANCEL) {
return ReturnResult.CANCEL;
}
else {
if (decision == GitCrlfDialog.SET) {
VirtualFile anyRoot = myPanel.getRoots().iterator().next(); // config will be set globally => any root will do.
setCoreAutoCrlfAttribute(anyRoot);
}
else {
if (dialog.dontWarnAgain()) {
settings.setWarnAboutCrlf(false);
}
}
return ReturnResult.COMMIT;
}
}
return ReturnResult.COMMIT;
}
private void setCoreAutoCrlfAttribute(@NotNull VirtualFile aRoot) {
try {
GitConfigUtil.setValue(myProject, aRoot, GitConfigUtil.CORE_AUTOCRLF, "true", "--global");
}
catch (VcsException e) {
// it is not critical: the user just will get the dialog again next time
LOG.warn("Couldn't globally set core.autocrlf in " + aRoot, e);
}
}
private ReturnResult checkUserName() {
Project project = myPanel.getProject();
GitVcs vcs = GitVcs.getInstance(project);
@@ -34,13 +34,12 @@ import java.util.Map;
* Git utilities for working with configuration
*/
public class GitConfigUtil {
public static final String USER_NAME = "user.name";
public static final String USER_EMAIL = "user.email";
public static final String BRANCH_AUTOSETUP_REBASE = "branch.autosetuprebase";
public static final String CORE_AUTOCRLF = "core.autocrlf";
/**
* A private constructor for utility class
*/
private GitConfigUtil() {
}
@@ -91,7 +91,7 @@
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="4d15a" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="4d15a" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -126,6 +126,14 @@
<text value="Commit automatically on cherry-pick"/>
</properties>
</component>
<component id="1ad55" class="com.intellij.ui.components.JBCheckBox" binding="myWarnAboutCrlf" default-binding="true">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Warn if &amp;CRLF line separators are about to be committed"/>
</properties>
</component>
</children>
</grid>
</children>
@@ -21,6 +21,7 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.components.JBCheckBox;
import git4idea.GitVcs;
import git4idea.i18n.GitBundle;
@@ -39,11 +40,7 @@ public class GitVcsPanel {
private static final String IDEA_SSH = GitBundle.getString("git.vcs.config.ssh.mode.idea"); // IDEA ssh value
private static final String NATIVE_SSH = GitBundle.getString("git.vcs.config.ssh.mode.native"); // Native SSH value
private static final String CRLF_CONVERT_TO_PROJECT = GitBundle.getString("git.vcs.config.convert.project");
private static final String CRLF_DO_NOT_CONVERT = GitBundle.getString("git.vcs.config.convert.do.not.convert");
private static final String CRLF_ASK = GitBundle.getString("git.vcs.config.convert.ask");
private final Project myProject;
private final GitVcsApplicationSettings myAppSettings;
private final GitVcs myVcs;
@@ -54,11 +51,11 @@ public class GitVcsPanel {
private JCheckBox myAutoUpdateIfPushRejected;
private JBCheckBox mySyncBranchControl;
private JCheckBox myAutoCommitOnCherryPick;
private JBCheckBox myWarnAboutCrlf;
public GitVcsPanel(@NotNull Project project) {
myVcs = GitVcs.getInstance(project);
myAppSettings = GitVcsApplicationSettings.getInstance();
myProject = project;
mySSHExecutableComboBox.addItem(IDEA_SSH);
mySSHExecutableComboBox.addItem(NATIVE_SSH);
mySSHExecutableComboBox.setSelectedItem(IDEA_SSH);
@@ -71,8 +68,9 @@ public class GitVcsPanel {
});
myGitField.addBrowseFolderListener(GitBundle.getString("find.git.title"), GitBundle.getString("find.git.description"), project,
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor());
final GitRepositoryManager repositoryManager = ServiceManager.getService(myProject, GitRepositoryManager.class);
final GitRepositoryManager repositoryManager = ServiceManager.getService(project, GitRepositoryManager.class);
mySyncBranchControl.setVisible(repositoryManager != null && repositoryManager.moreThanOneRoot());
myWarnAboutCrlf.setVisible(SystemInfo.isWindows);
}
/**
@@ -124,6 +122,7 @@ public class GitVcsPanel {
myAutoUpdateIfPushRejected.setSelected(settings.autoUpdateIfPushRejected());
mySyncBranchControl.setSelected(settings.getSyncSetting() == GitBranchSyncSetting.SYNC);
myAutoCommitOnCherryPick.setSelected(settings.isAutoCommitOnCherryPick());
myWarnAboutCrlf.setSelected(settings.warnAboutCrlf());
}
/**
@@ -136,7 +135,8 @@ public class GitVcsPanel {
(settings.isIdeaSsh() != IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem())) ||
!settings.autoUpdateIfPushRejected() == myAutoUpdateIfPushRejected.isSelected() ||
((settings.getSyncSetting() == GitBranchSyncSetting.SYNC) != mySyncBranchControl.isSelected() ||
settings.isAutoCommitOnCherryPick() != myAutoCommitOnCherryPick.isSelected());
settings.isAutoCommitOnCherryPick() != myAutoCommitOnCherryPick.isSelected() ||
settings.warnAboutCrlf() != myWarnAboutCrlf.isSelected());
}
/**
@@ -154,5 +154,7 @@ public class GitVcsPanel {
settings.setSyncSetting(mySyncBranchControl.isSelected() ? GitBranchSyncSetting.SYNC : GitBranchSyncSetting.DONT);
settings.setAutoCommitOnCherryPick(myAutoCommitOnCherryPick.isSelected());
settings.setWarnAboutCrlf(myWarnAboutCrlf.isSelected());
}
}
@@ -41,7 +41,7 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S
private final GitVcsApplicationSettings myAppSettings;
private State myState = new State();
/**
* The way the local changes are saved before update if user has selected auto-stash
*/
@@ -63,6 +63,7 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S
public Map<String, String> RECENT_BRANCH_BY_REPOSITORY = new HashMap<String, String>();
public String RECENT_COMMON_BRANCH = null;
public boolean AUTO_COMMIT_ON_CHERRY_PICK = false;
public boolean WARN_ABOUT_CRLF = true;
}
public GitVcsSettings(GitVcsApplicationSettings appSettings) {
@@ -171,6 +172,14 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings.S
return myState.AUTO_COMMIT_ON_CHERRY_PICK;
}
public boolean warnAboutCrlf() {
return myState.WARN_ABOUT_CRLF;
}
public void setWarnAboutCrlf(boolean warn) {
myState.WARN_ABOUT_CRLF = warn;
}
/**
* Provides migration from project settings.
* This method is to be removed in IDEA 13: it should be moved to {@link GitVcsApplicationSettings}
@@ -0,0 +1,106 @@
/*
* Copyright 2000-2012 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 git4idea.crlf;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.labels.LinkListener;
import com.intellij.util.ui.GridBag;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import static com.intellij.util.ui.UIUtil.DEFAULT_HGAP;
import static com.intellij.util.ui.UIUtil.DEFAULT_VGAP;
/**
* Warns the user that CRLF line separators are about to be committed to the repository.
* Provides some additional information and proposes to set {@code git config --global core.autocrlf true}.
*
* @author Kirill Likhodedov
* @see GitCrlfProblemsDetector
*/
public class GitCrlfDialog extends DialogWrapper {
public static final int SET = DialogWrapper.OK_EXIT_CODE;
public static final int DONT_SET = DialogWrapper.NEXT_USER_EXIT_CODE;
public static final int CANCEL = DialogWrapper.CANCEL_EXIT_CODE;
private JBCheckBox myDontWarn;
public GitCrlfDialog(@Nullable Project project) {
super(project, false);
setOKButtonText("Fix and Commit");
setCancelButtonText("Cancel");
setTitle("Line separators warning");
getCancelAction().putValue(DialogWrapper.FOCUSED_ACTION, true);
init();
}
@Override
protected Action[] createActions() {
return new Action[] { getOKAction(), getCancelAction(), new DialogWrapperExitAction("Commit as Is", DONT_SET) };
}
@Override
protected JComponent createCenterPanel() {
JLabel description = new JBLabel(
"<html>You are about to commit CRLF line separators to the Git repository.<br/>" +
"It is recommended to set core.autocrlf Git attribute to <code>true</code> to avoid line separator issues.</html>");
JLabel additionalDescription = new JBLabel(
"<html>Fix and Commit: <code>git config --global core.autocrlf true</code> will be called,<br/>" +
"Commit as Is: the config value won't be set.</html>", UIUtil.ComponentStyle.SMALL);
JLabel readMore = new LinkLabel("Read more", null, new LinkListener() {
@Override
public void linkSelected(LinkLabel aSource, Object aLinkData) {
BrowserUtil.launchBrowser("https://help.github.com/articles/dealing-with-line-endings");
}
});
JLabel icon = new JLabel(UIUtil.getWarningIcon(), SwingConstants.LEFT);
myDontWarn = new JBCheckBox("Don't warn again");
myDontWarn.setMnemonic('w');
JPanel rootPanel = new JPanel(new GridBagLayout());
GridBag g = new GridBag()
.setDefaultInsets(new Insets(0, 6, DEFAULT_VGAP, DEFAULT_HGAP))
.setDefaultAnchor(GridBagConstraints.LINE_START)
.setDefaultFill(GridBagConstraints.HORIZONTAL);
rootPanel.add(icon, g.nextLine().next().coverColumn(4));
rootPanel.add(description, g.next());
rootPanel.add(readMore, g.nextLine().next().next());
rootPanel.add(additionalDescription, g.nextLine().next().next().pady(DEFAULT_HGAP));
rootPanel.add(myDontWarn, g.nextLine().next().next().insets(0, 0, 0, 0));
return rootPanel;
}
public boolean dontWarnAgain() {
return myDontWarn.isSelected();
}
}
@@ -0,0 +1,48 @@
/*
* Copyright 2000-2012 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 git4idea.crlf;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Given a number of files, detects if CRLF line separators in them are about to be committed to Git. That is:
* <ul>
* <li>Checks if {@code core.autocrlf} is set to {@code true} or {@code input}.</li>
* <li>If not, checks if files contain CRLFs.</li>
* <li>
* For files with CRLFs checks if there are gitattributes set on them, such that would either force CRLF conversion on checkin,
* either indicate that these CRLFs are here intentionally.
* </li>
* </ul>
* All checks are made only for Windows system.
*
* @author Kirill Likhodedov
*/
public class GitCrlfProblemsDetector {
@NotNull
public static GitCrlfProblemsDetector detect(Collection<VirtualFile> files) {
return new GitCrlfProblemsDetector();
}
public boolean shouldWarn() {
return false;
}
}