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
;
41 import java
.awt
.event
.ActionEvent
;
42 import java
.io
.IOException
;
43 import java
.io
.OutputStream
;
44 import java
.io
.OutputStreamWriter
;
48 * This dialog allows converting the specified files before committing them.
50 public class GitConvertFilesDialog
extends DialogWrapper
{
52 * The version when option --stdin was added
54 private static final GitVersion CHECK_ATTR_STDIN_SUPPORTED
= new GitVersion(1, 6, 1, 0);
56 * Do not convert exit code
58 public static final int DO_NOT_CONVERT
= NEXT_USER_EXIT_CODE
;
60 * The checkbox used to indicate that dialog should not be shown
62 private JCheckBox myDoNotShowCheckBox
;
64 * The root panel of the dialog
66 private JPanel myRootPanel
;
68 * The tree of files to convert
70 private CheckboxTreeBase myFilesToConvert
;
72 * The root node in the tree
74 private CheckedTreeNode myRootNode
;
79 * @param project the project to which this dialog is related
81 GitConvertFilesDialog(Project project
, Map
<VirtualFile
, Set
<VirtualFile
>> filesToShow
) {
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"));
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
);
121 protected JComponent
createCenterPanel() {
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
) {
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()) {
161 VirtualFile f
= lfs
.findFileByPath(change
.getAfterRevision().getFile().getPath());
162 if (f
!= null && !f
.getFileType().isBinary() && !nl
.equals(LoadTextUtil
.detectLineSeparator(f
, false))) {
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
)) {
190 if (files
.isEmpty()) {
193 UIUtil
.invokeAndWaitIfNeeded(new Runnable() {
195 VirtualFile
[] selectedFiles
= null;
196 if (settings
.LINE_SEPARATORS_CONVERSION_ASK
) {
197 GitConvertFilesDialog d
= new GitConvertFilesDialog(project
, files
);
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
;
209 //noinspection ThrowableInstanceNeverThrown
210 exceptions
.add(new VcsException("Commit was cancelled in file conversion dialog"));
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
) {
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
) {
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
);
254 h
.addParameters("--stdin", "-z");
256 h
.addParameters("crlf");
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
);
265 h
.setInputProcessor(new Processor
<OutputStream
>() {
266 public boolean process(OutputStream outputStream
) {
268 OutputStreamWriter out
= new OutputStreamWriter(outputStream
, GitUtil
.UTF8_CHARSET
);
270 for (String file
: filesToCheck
.keySet()) {
279 catch (IOException ex
) {
281 outputStream
.close();
283 catch (IOException ioe
) {
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;
315 DoNotConvertAction() {
316 putValue(NAME
, GitBundle
.getString("crlf.convert.leave"));
317 putValue(DEFAULT_ACTION
, Boolean
.FALSE
);
323 public void actionPerformed(ActionEvent e
) {
324 if (myPerformAction
) return;
326 myPerformAction
= true;
327 close(DO_NOT_CONVERT
);
330 myPerformAction
= false;
337 * The cell renderer for the tree
339 static class FileTreeCellRenderer
extends CheckboxTree
.CheckboxTreeCellRenderer
{
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
)) {
348 renderUnknown(r
, value
);
351 CheckedTreeNode node
= (CheckedTreeNode
)value
;
352 if (!(node
.getUserObject() instanceof VirtualFile
)) {
354 renderUnknown(r
, node
.getUserObject());
357 VirtualFile file
= (VirtualFile
)node
.getUserObject();
359 VirtualFile parent
= (VirtualFile
)((CheckedTreeNode
)node
.getParent()).getUserObject();
361 Icon i
= file
.getIcon();
365 r
.append(GitUtil
.getRelativeFilePath(file
, parent
), SimpleTextAttributes
.REGULAR_ATTRIBUTES
, true);
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
);