git4idea: IDEADEV-41015, IDEADEV-40952: added support for text files without new...
[fedora-idea.git] / plugins / git4idea / src / git4idea / checkin / GitConvertFilesDialog.java
blob0f68f3d8325638aeca934aaf091f13b831a4dc44
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package git4idea.checkin;
18 import com.intellij.codeStyle.CodeStyleFacade;
19 import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.ui.DialogWrapper;
22 import com.intellij.openapi.vcs.VcsException;
23 import com.intellij.openapi.vcs.changes.Change;
24 import com.intellij.openapi.vfs.LocalFileSystem;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.ui.*;
27 import com.intellij.util.Processor;
28 import com.intellij.util.containers.HashMap;
29 import com.intellij.util.ui.UIUtil;
30 import com.intellij.util.ui.tree.TreeUtil;
31 import git4idea.GitUtil;
32 import git4idea.GitVcs;
33 import git4idea.commands.GitHandler;
34 import git4idea.commands.GitSimpleHandler;
35 import git4idea.commands.StringScanner;
36 import git4idea.config.GitVcsSettings;
37 import git4idea.config.GitVersion;
38 import git4idea.i18n.GitBundle;
40 import javax.swing.*;
41 import java.awt.event.ActionEvent;
42 import java.io.IOException;
43 import java.io.OutputStream;
44 import java.io.OutputStreamWriter;
45 import java.util.*;
47 /**
48 * This dialog allows converting the specified files before committing them.
50 public class GitConvertFilesDialog extends DialogWrapper {
51 /**
52 * The version when option --stdin was added
54 private static final GitVersion CHECK_ATTR_STDIN_SUPPORTED = new GitVersion(1, 6, 1, 0);
55 /**
56 * Do not convert exit code
58 public static final int DO_NOT_CONVERT = NEXT_USER_EXIT_CODE;
59 /**
60 * The checkbox used to indicate that dialog should not be shown
62 private JCheckBox myDoNotShowCheckBox;
63 /**
64 * The root panel of the dialog
66 private JPanel myRootPanel;
67 /**
68 * The tree of files to convert
70 private CheckboxTreeBase myFilesToConvert;
71 /**
72 * The root node in the tree
74 private CheckedTreeNode myRootNode;
76 /**
77 * The constructor
79 * @param project the project to which this dialog is related
81 GitConvertFilesDialog(Project project, Map<VirtualFile, Set<VirtualFile>> filesToShow) {
82 super(project, true);
83 ArrayList<VirtualFile> roots = new ArrayList<VirtualFile>(filesToShow.keySet());
84 Collections.sort(roots, GitUtil.VIRTUAL_FILE_COMPARATOR);
85 for (VirtualFile root : roots) {
86 CheckedTreeNode vcsRoot = new CheckedTreeNode(root);
87 myRootNode.add(vcsRoot);
88 ArrayList<VirtualFile> files = new ArrayList<VirtualFile>(filesToShow.get(root));
89 Collections.sort(files, GitUtil.VIRTUAL_FILE_COMPARATOR);
90 for (VirtualFile file : files) {
91 vcsRoot.add(new CheckedTreeNode(file));
94 TreeUtil.expandAll(myFilesToConvert);
95 setTitle(GitBundle.getString("crlf.convert.title"));
96 setOKButtonText(GitBundle.getString("crlf.convert.convert"));
97 init();
101 * {@inheritDoc}
103 @Override
104 protected Action[] createActions() {
105 return new Action[]{getOKAction(), new DoNotConvertAction(), getCancelAction()};
110 * Create custom UI components
112 private void createUIComponents() {
113 myRootNode = new CheckedTreeNode("ROOT");
114 myFilesToConvert = new CheckboxTree(new FileTreeCellRenderer(), myRootNode);
118 * {@inheritDoc}
120 @Override
121 protected JComponent createCenterPanel() {
122 return myRootPanel;
126 * {@inheritDoc}
128 @Override
129 protected String getDimensionServiceKey() {
130 return getClass().getName();
134 * Check if files need to be converted to other line separator
136 * @param project the project to use
137 * @param settings the vcs settings
138 * @param sortedChanges sorted changes
139 * @param exceptions the collection with exceptions
140 * @return true if conversion completed successfully, false if process was cancelled or there were errors
142 static boolean showDialogIfNeeded(final Project project,
143 final GitVcsSettings settings,
144 Map<VirtualFile, List<Change>> sortedChanges,
145 final List<VcsException> exceptions) {
146 try {
147 if (settings.LINE_SEPARATORS_CONVERSION_ASK ||
148 settings.LINE_SEPARATORS_CONVERSION == GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS) {
149 LocalFileSystem lfs = LocalFileSystem.getInstance();
150 final String nl = CodeStyleFacade.getInstance(project).getLineSeparator();
151 final Map<VirtualFile, Set<VirtualFile>> files = new HashMap<VirtualFile, Set<VirtualFile>>();
152 // preliminary screening of files
153 for (Map.Entry<VirtualFile, List<Change>> entry : sortedChanges.entrySet()) {
154 final VirtualFile root = entry.getKey();
155 final Set<VirtualFile> added = new HashSet<VirtualFile>();
156 for (Change change : entry.getValue()) {
157 switch (change.getType()) {
158 case NEW:
159 case MODIFICATION:
160 case MOVED:
161 VirtualFile f = lfs.findFileByPath(change.getAfterRevision().getFile().getPath());
162 if (f != null && !f.getFileType().isBinary() && !nl.equals(LoadTextUtil.detectLineSeparator(f, false))) {
163 added.add(f);
165 break;
166 case DELETED:
169 if (!added.isEmpty()) {
170 files.put(root, added);
173 // ignore files with CRLF unset
174 ignoreFilesWithCrlfUnset(project, files);
175 // check crlf for real
176 for (Iterator<Map.Entry<VirtualFile, Set<VirtualFile>>> i = files.entrySet().iterator(); i.hasNext();) {
177 Map.Entry<VirtualFile, Set<VirtualFile>> e = i.next();
178 Set<VirtualFile> fs = e.getValue();
179 for (Iterator<VirtualFile> j = fs.iterator(); j.hasNext();) {
180 VirtualFile f = j.next();
181 String detectedLineSeparator = LoadTextUtil.detectLineSeparator(f, true);
182 if (detectedLineSeparator == null || nl.equals(detectedLineSeparator)) {
183 j.remove();
186 if (fs.isEmpty()) {
187 i.remove();
190 if (files.isEmpty()) {
191 return true;
193 UIUtil.invokeAndWaitIfNeeded(new Runnable() {
194 public void run() {
195 VirtualFile[] selectedFiles = null;
196 if (settings.LINE_SEPARATORS_CONVERSION_ASK) {
197 GitConvertFilesDialog d = new GitConvertFilesDialog(project, files);
198 d.show();
199 if (d.isOK()) {
200 settings.LINE_SEPARATORS_CONVERSION_ASK = d.myDoNotShowCheckBox.isSelected();
201 settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.PROJECT_LINE_SEPARATORS;
202 selectedFiles = d.myFilesToConvert.getCheckedNodes(VirtualFile.class, null);
204 else if (d.getExitCode() == DO_NOT_CONVERT) {
205 settings.LINE_SEPARATORS_CONVERSION_ASK = d.myDoNotShowCheckBox.isSelected();
206 settings.LINE_SEPARATORS_CONVERSION = GitVcsSettings.ConversionPolicy.NONE;
208 else {
209 //noinspection ThrowableInstanceNeverThrown
210 exceptions.add(new VcsException("Commit was cancelled in file conversion dialog"));
213 else {
214 ArrayList<VirtualFile> fileList = new ArrayList<VirtualFile>();
215 for (Set<VirtualFile> fileSet : files.values()) {
216 fileList.addAll(fileSet);
218 selectedFiles = fileList.toArray(new VirtualFile[fileList.size()]);
220 if (selectedFiles != null) {
221 for (VirtualFile f : selectedFiles) {
222 try {
223 LoadTextUtil.changeLineSeparator(project, GitConvertFilesDialog.class.getName(), f, nl);
225 catch (IOException e) {
226 //noinspection ThrowableInstanceNeverThrown
227 exceptions.add(new VcsException("Failed to change line separators for the file: " + f.getPresentableUrl(), e));
235 catch (VcsException e) {
236 exceptions.add(e);
238 return exceptions.isEmpty();
242 * Remove files that have -crlf attribute specified
244 * @param project the context project
245 * @param files the files to check (map from vcs roots to the set of files under root)
246 * @throws VcsException if there is problem with running git
248 private static void ignoreFilesWithCrlfUnset(Project project, Map<VirtualFile, Set<VirtualFile>> files) throws VcsException {
249 boolean stdin = CHECK_ATTR_STDIN_SUPPORTED.isLessOrEqual(GitVcs.getInstance(project).version());
250 for (final Map.Entry<VirtualFile, Set<VirtualFile>> e : files.entrySet()) {
251 final VirtualFile r = e.getKey();
252 GitSimpleHandler h = new GitSimpleHandler(project, r, GitHandler.CHECK_ATTR);
253 if (stdin) {
254 h.addParameters("--stdin", "-z");
256 h.addParameters("crlf");
257 h.setSilent(true);
258 h.setNoSSH(true);
259 final HashMap<String, VirtualFile> filesToCheck = new HashMap<String, VirtualFile>();
260 Set<VirtualFile> fileSet = e.getValue();
261 for (VirtualFile file : fileSet) {
262 filesToCheck.put(GitUtil.relativePath(r, file), file);
264 if (stdin) {
265 h.setInputProcessor(new Processor<OutputStream>() {
266 public boolean process(OutputStream outputStream) {
267 try {
268 OutputStreamWriter out = new OutputStreamWriter(outputStream, GitUtil.UTF8_CHARSET);
269 try {
270 for (String file : filesToCheck.keySet()) {
271 out.write(file);
272 out.write("\u0000");
275 finally {
276 out.close();
279 catch (IOException ex) {
280 try {
281 outputStream.close();
283 catch (IOException ioe) {
284 // ignore exception
287 return true;
291 else {
292 h.endOptions();
293 h.addRelativeFiles(filesToCheck.values());
295 StringScanner output = new StringScanner(h.run());
296 String unsetIndicator = ": crlf: unset";
297 while (output.hasMoreData()) {
298 String l = output.line();
299 if (l.endsWith(unsetIndicator)) {
300 fileSet.remove(filesToCheck.get(GitUtil.unescapePath(l.substring(0, l.length() - unsetIndicator.length()))));
307 * Action used to indicate that no conversion should be performed
309 class DoNotConvertAction extends AbstractAction {
310 private static final long serialVersionUID = 1931383640152023206L;
313 * The constructor
315 DoNotConvertAction() {
316 putValue(NAME, GitBundle.getString("crlf.convert.leave"));
317 putValue(DEFAULT_ACTION, Boolean.FALSE);
321 * {@inheritDoc}
323 public void actionPerformed(ActionEvent e) {
324 if (myPerformAction) return;
325 try {
326 myPerformAction = true;
327 close(DO_NOT_CONVERT);
329 finally {
330 myPerformAction = false;
337 * The cell renderer for the tree
339 static class FileTreeCellRenderer extends CheckboxTree.CheckboxTreeCellRenderer {
341 * {@inheritDoc}
343 @Override
344 public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
345 ColoredTreeCellRenderer r = getTextRenderer();
346 if (!(value instanceof CheckedTreeNode)) {
347 // unknown node type
348 renderUnknown(r, value);
349 return;
351 CheckedTreeNode node = (CheckedTreeNode)value;
352 if (!(node.getUserObject() instanceof VirtualFile)) {
353 // unknown node type
354 renderUnknown(r, node.getUserObject());
355 return;
357 VirtualFile file = (VirtualFile)node.getUserObject();
358 if (leaf) {
359 VirtualFile parent = (VirtualFile)((CheckedTreeNode)node.getParent()).getUserObject();
360 // the real file
361 Icon i = file.getIcon();
362 if (i != null) {
363 r.setIcon(i);
365 r.append(GitUtil.getRelativeFilePath(file, parent), SimpleTextAttributes.REGULAR_ATTRIBUTES, true);
367 else {
368 // the vcs root node
369 r.append(file.getPresentableUrl(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES, true);
374 * Render unknown node
376 * @param r a renderer to use
377 * @param value the unknown value
379 private static void renderUnknown(ColoredTreeCellRenderer r, Object value) {
380 r.append("UNSUPPORTED NODE TYPE: " + (value == null ? "null" : value.getClass().getName()), SimpleTextAttributes.ERROR_ATTRIBUTES);