diff --git a/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java b/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java index b45df4d672e5..feb48343b89e 100644 --- a/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java +++ b/plugins/git4idea/src/git4idea/checkin/GitCheckinHandlerFactory.java @@ -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); diff --git a/plugins/git4idea/src/git4idea/config/GitConfigUtil.java b/plugins/git4idea/src/git4idea/config/GitConfigUtil.java index b80cd889c5cd..b5dd005d38b0 100644 --- a/plugins/git4idea/src/git4idea/config/GitConfigUtil.java +++ b/plugins/git4idea/src/git4idea/config/GitConfigUtil.java @@ -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() { } diff --git a/plugins/git4idea/src/git4idea/config/GitVcsPanel.form b/plugins/git4idea/src/git4idea/config/GitVcsPanel.form index 4d894993c241..0f24aaf99d56 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsPanel.form +++ b/plugins/git4idea/src/git4idea/config/GitVcsPanel.form @@ -91,7 +91,7 @@ - + @@ -126,6 +126,14 @@ + + + + + + + + diff --git a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java index 977fcbead95a..4ba97f2620c2 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsPanel.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsPanel.java @@ -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()); } + } diff --git a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java index d5e90b261dc6..2a930a5dab2d 100644 --- a/plugins/git4idea/src/git4idea/config/GitVcsSettings.java +++ b/plugins/git4idea/src/git4idea/config/GitVcsSettings.java @@ -41,7 +41,7 @@ public class GitVcsSettings implements PersistentStateComponent RECENT_BRANCH_BY_REPOSITORY = new HashMap(); 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 PersistentStateComponentYou are about to commit CRLF line separators to the Git repository.
" + + "It is recommended to set core.autocrlf Git attribute to true to avoid line separator issues."); + + JLabel additionalDescription = new JBLabel( + "Fix and Commit: git config --global core.autocrlf true will be called,
" + + "Commit as Is: the config value won't be set.", 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(); + } + +} diff --git a/plugins/git4idea/src/git4idea/crlf/GitCrlfProblemsDetector.java b/plugins/git4idea/src/git4idea/crlf/GitCrlfProblemsDetector.java new file mode 100644 index 000000000000..a109ab4626ea --- /dev/null +++ b/plugins/git4idea/src/git4idea/crlf/GitCrlfProblemsDetector.java @@ -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: + *
    + *
  • Checks if {@code core.autocrlf} is set to {@code true} or {@code input}.
  • + *
  • If not, checks if files contain CRLFs.
  • + *
  • + * 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. + *
  • + *
+ * All checks are made only for Windows system. + * + * @author Kirill Likhodedov + */ +public class GitCrlfProblemsDetector { + + @NotNull + public static GitCrlfProblemsDetector detect(Collection files) { + return new GitCrlfProblemsDetector(); + } + + public boolean shouldWarn() { + return false; + } + +}