git4idea: Added dialog that offers conversion to project's line separators
authorunknown <Constantin.Plotnikov@.Labs.IntelliJ.Net>
Fri, 16 Oct 2009 19:07:43 +0000 (23:07 +0400)
committerunknown <Constantin.Plotnikov@.Labs.IntelliJ.Net>
Fri, 16 Oct 2009 19:09:50 +0000 (23:09 +0400)
plugins/git4idea/src/git4idea/checkin/GitCheckinEnvironment.java
plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form [new file with mode: 0644]
plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/config/GitVcsPanel.form
plugins/git4idea/src/git4idea/config/GitVcsPanel.java
plugins/git4idea/src/git4idea/config/GitVcsSettings.java
plugins/git4idea/src/git4idea/i18n/GitBundle.properties

index 77d94a1e2d95f45fc13393dca79bfa0d4f5b3efc..12d805c271b4a3b9fe4eec6c310e2fb22f416844 100644 (file)
@@ -167,76 +167,77 @@ public class GitCheckinEnvironment implements CheckinEnvironment {
   /**
    * {@inheritDoc}
    */
-  @SuppressWarnings({"ConstantConditions"})
   public List<VcsException> commit(@NotNull List<Change> changes, @NotNull String message) {
     List<VcsException> exceptions = new ArrayList<VcsException>();
     Map<VirtualFile, List<Change>> sortedChanges = sortChangesByGitRoot(changes, exceptions);
-    for (Map.Entry<VirtualFile, List<Change>> entry : sortedChanges.entrySet()) {
-      Set<FilePath> files = new HashSet<FilePath>();
-      final VirtualFile root = entry.getKey();
-      try {
-        File messageFile = createMessageFile(root, message);
+    if (GitConvertFilesDialog.showDialogIfNeeded(myProject, mySettings, sortedChanges, exceptions)) {
+      for (Map.Entry<VirtualFile, List<Change>> entry : sortedChanges.entrySet()) {
+        Set<FilePath> files = new HashSet<FilePath>();
+        final VirtualFile root = entry.getKey();
         try {
-          final Set<FilePath> added = new HashSet<FilePath>();
-          final Set<FilePath> removed = new HashSet<FilePath>();
-          for (Change change : entry.getValue()) {
-            switch (change.getType()) {
-              case NEW:
-              case MODIFICATION:
-                added.add(change.getAfterRevision().getFile());
-                break;
-              case DELETED:
-                removed.add(change.getBeforeRevision().getFile());
-                break;
-              case MOVED:
-                added.add(change.getAfterRevision().getFile());
-                removed.add(change.getBeforeRevision().getFile());
-                break;
-              default:
-                throw new IllegalStateException("Unknown change type: " + change.getType());
-            }
-          }
+          File messageFile = createMessageFile(root, message);
           try {
-            if (updateIndex(myProject, root, added, removed, exceptions)) {
-              try {
-                files.addAll(added);
-                files.addAll(removed);
-                commit(myProject, root, files, messageFile, myNextCommitAuthor).run();
+            final Set<FilePath> added = new HashSet<FilePath>();
+            final Set<FilePath> removed = new HashSet<FilePath>();
+            for (Change change : entry.getValue()) {
+              switch (change.getType()) {
+                case NEW:
+                case MODIFICATION:
+                  added.add(change.getAfterRevision().getFile());
+                  break;
+                case DELETED:
+                  removed.add(change.getBeforeRevision().getFile());
+                  break;
+                case MOVED:
+                  added.add(change.getAfterRevision().getFile());
+                  removed.add(change.getBeforeRevision().getFile());
+                  break;
+                default:
+                  throw new IllegalStateException("Unknown change type: " + change.getType());
               }
-              catch (VcsException ex) {
-                if (!isMergeCommit(ex)) {
-                  throw ex;
+            }
+            try {
+              if (updateIndex(myProject, root, added, removed, exceptions)) {
+                try {
+                  files.addAll(added);
+                  files.addAll(removed);
+                  commit(myProject, root, files, messageFile, myNextCommitAuthor).run();
                 }
-                if (!mergeCommit(myProject, root, added, removed, messageFile, myNextCommitAuthor, exceptions)) {
-                  throw ex;
+                catch (VcsException ex) {
+                  if (!isMergeCommit(ex)) {
+                    throw ex;
+                  }
+                  if (!mergeCommit(myProject, root, added, removed, messageFile, myNextCommitAuthor, exceptions)) {
+                    throw ex;
+                  }
                 }
               }
-            }
-            if (myNextCommitIsPushed != null && myNextCommitIsPushed.booleanValue()) {
-              // push
-              Collection<VcsException> problems = GitHandlerUtil.doSynchronouslyWithExceptions(GitPushUtils.preparePush(myProject, root));
-              for (VcsException e : problems) {
-                if (!isNoOrigin(e)) {
-                  // no origin exception just means that push was not applicable to the repository
-                  exceptions.add(e);
+              if (myNextCommitIsPushed != null && myNextCommitIsPushed.booleanValue()) {
+                // push
+                Collection<VcsException> problems = GitHandlerUtil.doSynchronouslyWithExceptions(GitPushUtils.preparePush(myProject, root));
+                for (VcsException e : problems) {
+                  if (!isNoOrigin(e)) {
+                    // no origin exception just means that push was not applicable to the repository
+                    exceptions.add(e);
+                  }
                 }
               }
             }
-          }
-          finally {
-            if (!messageFile.delete()) {
-              log.warn("Failed to remove temporary file: " + messageFile);
+            finally {
+              if (!messageFile.delete()) {
+                log.warn("Failed to remove temporary file: " + messageFile);
+              }
             }
           }
+          catch (VcsException e) {
+            exceptions.add(e);
+          }
         }
-        catch (VcsException e) {
-          exceptions.add(e);
+        catch (IOException ex) {
+          //noinspection ThrowableInstanceNeverThrown
+          exceptions.add(new VcsException("Creation of commit message file failed", ex));
         }
       }
-      catch (IOException ex) {
-        //noinspection ThrowableInstanceNeverThrown
-        exceptions.add(new VcsException("Creation of commit message file failed", ex));
-      }
     }
     return exceptions;
   }
diff --git a/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form b/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.form
new file mode 100644 (file)
index 0000000..fe2f202
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.checkin.GitConvertFilesDialog">
+  <grid id="27dc6" binding="myRootPanel" 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>
+      <xy x="20" y="20" width="514" height="273"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <component id="9016b" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="0" 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>
+          <labelFor value="9bc84"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="crlf.convert.label"/>
+        </properties>
+      </component>
+      <scrollpane id="9bc84">
+        <constraints>
+          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="c05a" class="com.intellij.ui.CheckboxTreeBase" binding="myFilesToConvert" custom-create="true">
+            <constraints/>
+            <properties>
+              <showsRootHandles value="false"/>
+            </properties>
+          </component>
+        </children>
+      </scrollpane>
+      <component id="4471d" class="javax.swing.JCheckBox" binding="myDoNotShowCheckBox" default-binding="true">
+        <constraints>
+          <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="common.do.not.show"/>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="common.do.not.show.tooltip"/>
+        </properties>
+      </component>
+      <component id="8c724" class="javax.swing.JCheckBox" binding="myDoNotConvertFilesCheckBox" default-binding="true">
+        <constraints>
+          <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="crlf.convert.none"/>
+        </properties>
+      </component>
+    </children>
+  </grid>
+</form>
diff --git a/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java b/plugins/git4idea/src/git4idea/checkin/GitConvertFilesDialog.java
new file mode 100644 (file)
index 0000000..2c70b61
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2000-2009 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.checkin;
+
+import com.intellij.codeStyle.CodeStyleFacade;
+import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vcs.changes.Change;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.*;
+import com.intellij.util.Processor;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.ui.UIUtil;
+import com.intellij.util.ui.tree.TreeUtil;
+import git4idea.GitUtil;
+import git4idea.commands.GitHandler;
+import git4idea.commands.GitSimpleHandler;
+import git4idea.commands.StringScanner;
+import git4idea.config.GitVcsSettings;
+import git4idea.i18n.GitBundle;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.*;
+
+/**
+ * This dialog allows converting the specified files before committing them.
+ */
+public class GitConvertFilesDialog extends DialogWrapper {
+  /**
+   * The checkbox used to indicate that dialog should not be shown
+   */
+  private JCheckBox myDoNotShowCheckBox;
+  /**
+   * The root panel of the dialog
+   */
+  private JPanel myRootPanel;
+  /**
+   * The checkbox that disables conversion of files
+   */
+  private JCheckBox myDoNotConvertFilesCheckBox;
+  /**
+   * The tree of files to convert
+   */
+  private CheckboxTreeBase myFilesToConvert;
+  /**
+   * The root node in the tree
+   */
+  private CheckedTreeNode myRootNode;
+
+  /**
+   * The constructor
+   *
+   * @param project the project to which this dialog is related
+   */
+  GitConvertFilesDialog(Project project, GitVcsSettings settings, Map<VirtualFile, Set<VirtualFile>> filesToShow) {
+    super(project, true);
+    ArrayList<VirtualFile> roots = new ArrayList<VirtualFile>(filesToShow.keySet());
+    Collections.sort(roots, GitUtil.VIRTUAL_FILE_COMPARATOR);
+    for (VirtualFile root : roots) {
+      CheckedTreeNode vcsRoot = new CheckedTreeNode(root);
+      myRootNode.add(vcsRoot);
+      ArrayList<VirtualFile> files = new ArrayList<VirtualFile>(filesToShow.get(root));
+      Collections.sort(files, GitUtil.VIRTUAL_FILE_COMPARATOR);
+      for (VirtualFile file : files) {
+        vcsRoot.add(new CheckedTreeNode(file));
+      }
+    }
+    myDoNotConvertFilesCheckBox.setSelected(settings.LINE_SEPARATORS_CONVERSION == GitVcsSettings.ConversionPolicy.NONE);
+    updateFields();
+    TreeUtil.expandAll(myFilesToConvert);
+    myDoNotConvertFilesCheckBox.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        updateFields();
+      }
+    });
+    setTitle(GitBundle.getString("crlf.convert.title"));
+    init();
+  }
+
+
+  /**
+   * Update fields basing on selection state
+   */
+  private void updateFields() {
+    if (myDoNotConvertFilesCheckBox.isSelected()) {
+      myRootNode.setChecked(false);
+      myFilesToConvert.setEnabled(false);
+      setOKButtonText(GitBundle.getString("crlf.convert.leave"));
+    }
+    else {
+      myFilesToConvert.setEnabled(true);
+      myRootNode.setChecked(true);
+      setOKButtonText(GitBundle.getString("crlf.convert.convert"));
+    }
+  }
+
+
+  /**
+   * Create custom UI components
+   */
+  private void createUIComponents() {
+    myRootNode = new CheckedTreeNode("ROOT");
+    myFilesToConvert = new CheckboxTree(new FileTreeCellRenderer(), myRootNode);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected JComponent createCenterPanel() {
+    return myRootPanel;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected String getDimensionServiceKey() {
+    return getClass().getName();
+  }
+
+  /**
+   * Check if files need to be converted to other line separator
+   *
+   * @param project       the project to use
+   * @param settings      the vcs settings
+   * @param sortedChanges sorted changes
+   * @param exceptions    the collection with exceptions
+   * @return true if conversion completed successfully, false if process was cancelled or there were errors
+   */
+  static boolean showDialogIfNeeded(final Project project,
+                                    final GitVcsSettings settings,
+                                    Map<VirtualFile, List<Change>> sortedChanges,
+                                    final List<VcsException> exceptions) {
+    try {
+      if (settings.LINE_SEPARATORS_CONVERSION_ASK ||
+          settings.LINE_SEPARATORS_CONVERSION == GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS) {
+        LocalFileSystem lfs = LocalFileSystem.getInstance();
+        final String nl = CodeStyleFacade.getInstance(project).getLineSeparator();
+        final Map<VirtualFile, Set<VirtualFile>> files = new HashMap<VirtualFile, Set<VirtualFile>>();
+        // preliminary screening of files
+        for (Map.Entry<VirtualFile, List<Change>> entry : sortedChanges.entrySet()) {
+          final VirtualFile root = entry.getKey();
+          final Set<VirtualFile> added = new HashSet<VirtualFile>();
+          for (Change change : entry.getValue()) {
+            switch (change.getType()) {
+              case NEW:
+              case MODIFICATION:
+              case MOVED:
+                VirtualFile f = lfs.findFileByPath(change.getAfterRevision().getFile().getPath());
+                if (f != null && !f.getFileType().isBinary() && !nl.equals(LoadTextUtil.detectLineSeparator(f, false))) {
+                  added.add(f);
+                }
+                break;
+              case DELETED:
+            }
+          }
+          if (!added.isEmpty()) {
+            files.put(root, added);
+          }
+        }
+        // ignore files with CRLF unset
+        ignoreFilesWithCrlfUnset(project, files);
+        // check crlf for real
+        for (Iterator<Map.Entry<VirtualFile, Set<VirtualFile>>> i = files.entrySet().iterator(); i.hasNext();) {
+          Map.Entry<VirtualFile, Set<VirtualFile>> e = i.next();
+          Set<VirtualFile> fs = e.getValue();
+          for (Iterator<VirtualFile> j = fs.iterator(); j.hasNext();) {
+            VirtualFile f = j.next();
+            if (nl.equals(LoadTextUtil.detectLineSeparator(f, true))) {
+              j.remove();
+            }
+          }
+          if (fs.isEmpty()) {
+            i.remove();
+          }
+        }
+        if (files.isEmpty()) {
+          return true;
+        }
+        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+          public void run() {
+            GitConvertFilesDialog d = new GitConvertFilesDialog(project, settings, files);
+            d.show();
+            if (d.isOK()) {
+              settings.LINE_SEPARATORS_CONVERSION_ASK = d.myDoNotShowCheckBox.isSelected();
+              if (d.myDoNotConvertFilesCheckBox.isSelected()) {
+                settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.NONE;
+              }
+              else {
+                settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS;
+                for (VirtualFile f : d.myFilesToConvert.getCheckedNodes(VirtualFile.class, null)) {
+                  try {
+                    LoadTextUtil.changeLineSeparator(project, d, f, nl);
+                  }
+                  catch (IOException e) {
+                    //noinspection ThrowableInstanceNeverThrown
+                    exceptions.add(new VcsException("Failed to change line separators for the file: " + f.getPresentableUrl(), e));
+                  }
+                }
+              }
+            }
+            else {
+              //noinspection ThrowableInstanceNeverThrown
+              exceptions.add(new VcsException("Commit was cancelled in file conversion dialog"));
+            }
+          }
+        });
+      }
+    }
+    catch (VcsException e) {
+      exceptions.add(e);
+    }
+    return exceptions.isEmpty();
+  }
+
+  /**
+   * Remove files that have -crlf attribute specified
+   *
+   * @param project the context project
+   * @param files   the files to check (map from vcs roots to the set of files under root)
+   * @throws VcsException if there is problem with running git
+   */
+  private static void ignoreFilesWithCrlfUnset(Project project, Map<VirtualFile, Set<VirtualFile>> files) throws VcsException {
+    for (final Map.Entry<VirtualFile, Set<VirtualFile>> e : files.entrySet()) {
+      final VirtualFile r = e.getKey();
+      GitSimpleHandler h = new GitSimpleHandler(project, r, GitHandler.CHECK_ATTR);
+      h.addParameters("--stdin", "-z", "crlf");
+      h.setSilent(true);
+      h.setNoSSH(true);
+      final HashMap<String, VirtualFile> filesToCheck = new HashMap<String, VirtualFile>();
+      Set<VirtualFile> fileSet = e.getValue();
+      for (VirtualFile file : fileSet) {
+        filesToCheck.put(GitUtil.relativePath(r, file), file);
+      }
+      h.setInputProcessor(new Processor<OutputStream>() {
+        public boolean process(OutputStream outputStream) {
+          try {
+            OutputStreamWriter out = new OutputStreamWriter(outputStream, GitUtil.UTF8_CHARSET);
+            try {
+              for (String file : filesToCheck.keySet()) {
+                out.write(file);
+                out.write("\u0000");
+              }
+            }
+            finally {
+              out.close();
+            }
+          }
+          catch (IOException ex) {
+            try {
+              outputStream.close();
+            }
+            catch (IOException ioe) {
+              // ignore exception
+            }
+          }
+          return true;
+        }
+      });
+      StringScanner output = new StringScanner(h.run());
+      String unsetIndicator = ": crlf unset";
+      while (output.hasMoreData()) {
+        String l = output.line();
+        if (l.endsWith(unsetIndicator)) {
+          fileSet.remove(filesToCheck.get(GitUtil.unescapePath(l.substring(0, l.length() - unsetIndicator.length()))));
+        }
+      }
+    }
+  }
+
+
+  /**
+   * The cell renderer for the tree
+   */
+  static class FileTreeCellRenderer extends CheckboxTree.CheckboxTreeCellRenderer {
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+      ColoredTreeCellRenderer r = getTextRenderer();
+      if (!(value instanceof CheckedTreeNode)) {
+        // unknown node type
+        renderUnknown(r, value);
+        return;
+      }
+      CheckedTreeNode node = (CheckedTreeNode)value;
+      if (!(node.getUserObject() instanceof VirtualFile)) {
+        // unknown node type
+        renderUnknown(r, node.getUserObject());
+        return;
+      }
+      VirtualFile file = (VirtualFile)node.getUserObject();
+      if (leaf) {
+        VirtualFile parent = (VirtualFile)((CheckedTreeNode)node.getParent()).getUserObject();
+        // the real file
+        Icon i = file.getIcon();
+        if (i != null) {
+          r.setIcon(i);
+        }
+        r.append(GitUtil.getRelativeFilePath(file, parent), SimpleTextAttributes.REGULAR_ATTRIBUTES, true);
+      }
+      else {
+        // the vcs root node
+        r.append(file.getPresentableUrl(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES, true);
+      }
+    }
+
+    /**
+     * Render unknown node
+     * @param r a renderer to use
+     * @param value the unknown value
+     */
+    private static void renderUnknown(ColoredTreeCellRenderer r, Object value) {
+      r.append("UNSUPPORTED NODE TYPE: "+(value == null?"null":value.getClass().getName()), SimpleTextAttributes.ERROR_ATTRIBUTES);
+    }
+  }
+}
index 1298dc1b5573630b84f7656d46ff289df90e3a36..b315460f7e6bb057fa8bf18e695556bf28b9fb54 100644 (file)
@@ -1,11 +1,13 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.config.GitVcsPanel">
-  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
     <margin top="0" left="0" bottom="0" right="0"/>
     <constraints>
       <xy x="20" y="20" width="500" height="400"/>
     </constraints>
-    <properties/>
+    <properties>
+      <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="git.vcs.config.convert.ask"/>
+    </properties>
     <border type="none"/>
     <children>
       <component id="b1a9d" class="javax.swing.JLabel">
@@ -25,7 +27,7 @@
       </component>
       <vspacer id="4c83d">
         <constraints>
-          <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+          <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
         </constraints>
       </vspacer>
       <component id="563b2" class="javax.swing.JButton" binding="myTestButton">
           </hspacer>
         </children>
       </grid>
+      <component id="a6d8e" class="javax.swing.JLabel">
+        <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>
+          <labelFor value="7a626"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="git.vcs.config.convert.crlf"/>
+        </properties>
+      </component>
+      <grid id="a4472" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="7a626" class="javax.swing.JComboBox" binding="myConvertTextFilesComboBox" default-binding="true">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="git.vcs.config.convert.tooltip"/>
+            </properties>
+          </component>
+          <hspacer id="3c6b8">
+            <constraints>
+              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+          </hspacer>
+        </children>
+      </grid>
+      <component id="a8818" class="javax.swing.JCheckBox" binding="myAskBeforeConversionsCheckBox" default-binding="true">
+        <constraints>
+          <grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <selected value="true"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="git.vcs.config.convert.ask"/>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="git.vcs.config.convert.ask.tooltip"/>
+        </properties>
+      </component>
     </children>
   </grid>
 </form>
index d0c71c9b46ee1d6ef657e171494f18f3a7b379c8..b0852aa013b90b44863429cad5f2490081dd883f 100644 (file)
@@ -48,6 +48,14 @@ public class GitVcsPanel {
    * Type of SSH executable to use
    */
   private JComboBox mySSHExecutableComboBox;
+  /**
+   * The conversion policy
+   */
+  private JComboBox myConvertTextFilesComboBox;
+  /**
+   * The confirmation checkbox
+   */
+  private JCheckBox myAskBeforeConversionsCheckBox;
   /**
    * The project
    */
@@ -64,6 +72,14 @@ public class GitVcsPanel {
    * Native SSH value
    */
   private static final String NATIVE_SSH = GitBundle.getString("git.vcs.config.ssh.mode.native");
+  /**
+   * IDEA ssh value
+   */
+  private static final String CRLF_CONVERT_TO_PROJECT = GitBundle.getString("git.vcs.config.convert.project");
+  /**
+   * Native SSH value
+   */
+  private static final String CRLF_DO_NOT_CONVERT = GitBundle.getString("git.vcs.config.convert.do.not.convert");
 
   /**
    * The constructor
@@ -81,7 +97,9 @@ public class GitVcsPanel {
         testConnection();
       }
     });
-
+    myConvertTextFilesComboBox.addItem(CRLF_DO_NOT_CONVERT);
+    myConvertTextFilesComboBox.addItem(CRLF_CONVERT_TO_PROJECT);
+    myConvertTextFilesComboBox.setSelectedItem(CRLF_CONVERT_TO_PROJECT);
     myGitField.addBrowseFolderListener(GitBundle.getString("find.git.title"), GitBundle.getString("find.git.description"), project,
                                        new FileChooserDescriptor(true, false, false, false, false, false));
   }
@@ -123,6 +141,30 @@ public class GitVcsPanel {
   public void load(@NotNull GitVcsSettings settings) {
     myGitField.setText(settings.GIT_EXECUTABLE);
     mySSHExecutableComboBox.setSelectedItem(settings.isIdeaSsh() ? IDEA_SSH : NATIVE_SSH);
+    myAskBeforeConversionsCheckBox.setSelected(settings.LINE_SEPARATORS_CONVERSION_ASK);
+    myConvertTextFilesComboBox.setSelectedItem(crlfPolicyItem(settings));
+  }
+
+  /**
+   * Get crlf policy item from settings
+   *
+   * @param settings the settings object
+   * @return the item in crlf combobox
+   */
+  static private String crlfPolicyItem(GitVcsSettings settings) {
+    String crlf;
+    switch (settings.LINE_SEPARATORS_CONVERSION) {
+      case NONE:
+        crlf = CRLF_DO_NOT_CONVERT;
+        break;
+      case PROJECT_LINE_SEPARATORS:
+        crlf = CRLF_CONVERT_TO_PROJECT;
+        break;
+      default:
+        assert false : "Unknown crlf policy: " + settings.LINE_SEPARATORS_CONVERSION;
+        crlf = null;
+    }
+    return crlf;
   }
 
   /**
@@ -132,7 +174,8 @@ public class GitVcsPanel {
    */
   public boolean isModified(@NotNull GitVcsSettings settings) {
     return !settings.GIT_EXECUTABLE.equals(myGitField.getText()) ||
-           (settings.isIdeaSsh() != IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem()));
+           (settings.isIdeaSsh() != IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem())) ||
+           !crlfPolicyItem(settings).equals(myConvertTextFilesComboBox.getSelectedItem());
   }
 
   /**
@@ -143,5 +186,15 @@ public class GitVcsPanel {
   public void save(@NotNull GitVcsSettings settings) {
     settings.GIT_EXECUTABLE = myGitField.getText();
     settings.setIdeaSsh(IDEA_SSH.equals(mySSHExecutableComboBox.getSelectedItem()));
+    Object policyItem = myConvertTextFilesComboBox.getSelectedItem();
+    GitVcsSettings.ConversionPolicy conversionPolicy;
+    if(CRLF_DO_NOT_CONVERT.equals(policyItem)) {
+      conversionPolicy = GitVcsSettings.ConversionPolicy.NONE;
+    } else if (CRLF_CONVERT_TO_PROJECT.equals(policyItem)) {
+      conversionPolicy = GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS;
+    } else {
+      throw new IllegalStateException("Unknown selected CRLF policy: "+policyItem);
+    }
+    settings.LINE_SEPARATORS_CONVERSION = conversionPolicy;
   }
 }
index ac07f689e38df282edb4de718eb945bb6196c9b6..cd05fe91b9ed674542a997465c1aaafaa3e1fbcd 100644 (file)
@@ -90,6 +90,14 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings>
    * The type of update operation to perform
    */
   public UpdateType UPDATE_TYPE = UpdateType.BRANCH_DEFAULT;
+  /**
+   * The crlf conversion policy
+   */
+  public ConversionPolicy LINE_SEPARATORS_CONVERSION = ConversionPolicy.PROJECT_LINE_SEPARATORS;
+  /**
+   * If true, the dialog is shown with conversion options
+   */
+  public boolean LINE_SEPARATORS_CONVERSION_ASK = true;
 
   /**
    * Save an author of the commit and make it the first one. If amount of authors exceeds the limit, remove least recently selected author.
@@ -215,4 +223,18 @@ public class GitVcsSettings implements PersistentStateComponent<GitVcsSettings>
     /** Rebase local commits upon the fetched branch*/
     REBASE
   }
+
+  /**
+   * The CRLF conversion policy
+   */
+  public enum ConversionPolicy {
+    /**
+     * No conversion is performed
+     */
+    NONE,
+    /**
+     * The files are converted to project line separators
+     */
+    PROJECT_LINE_SEPARATORS
+  }
 }
index e9c325a9d99894b4dfa581b5bbe8cebe2a2376b7..d1240b8ff96d257a0bef811c1197e0366469dcda 100644 (file)
@@ -64,10 +64,18 @@ commit.push.changes.tooltip=When this option is enabled, changes are pushed to t
 commit.push.changes=&Push Changes
 common.current.branch.tooltip=The currently checked out branch.
 common.current.branch=Current Branch:
+common.do.not.show.tooltip=If this option is selected, the choice will be remembered, and the dialog will not be shown again.
+common.do.not.show=&Do not show this dialog again
 common.git.root.tooltip=Select git vcs root
 common.git.root=Git &Root:
 common.no.active.branch=<no active branch>
 computing.annotation=Computing annotation for {0}
+crlf.convert.convert=Convert
+crlf.convert.label=<html>&The following text files have line separator that do not match the project line separator.<br/>Select files you wish to covert to project default line separators before commit.</html>
+crlf.convert.leave=Leave Unchanged
+crlf.convert.none.tooltip=If this option is selected, the file are not attempted to be converted before commit.
+crlf.convert.none=Do &not convert files
+crlf.convert.title=Invalid Line Separators
 current.branch.action.name=CurrentBranch
 current.branch.change.tracked=Change Tracked Branch
 current.branch.message=Checked out branch: {0}
@@ -115,6 +123,12 @@ getting.history=Getting history for {0}
 git.default.commit.message=\n# Brief commit description here\n\n# Full commit description here (comment lines starting with '#' will not be included)\n\n
 git.error.exit=The git process exited with the code {0}
 git.running=<html>Running: {0}</html>
+git.vcs.config.convert.ask.tooltip=If this checkbox is specified, the user will be show a list of file to be converted.
+git.vcs.config.convert.ask=Ask before con&versions
+git.vcs.config.convert.crlf=&Convert Text Files:
+git.vcs.config.convert.do.not.convert=Do not convert
+git.vcs.config.convert.project=Convert to project's line separators
+git.vcs.config.convert.tooltip=<html>Convert text files on commit:<ul><li><b>Do not convert</b> - do not covert sources</li><li><b>Convert to project line separators</b> - the sourcers are converted to line separators that are specified in the project's code style.</li></ul></html>
 git.vcs.config.path.label=Path to &git executable:
 git.vcs.config.ssh.mode.idea=IDEA ssh
 git.vcs.config.ssh.mode.native=Native