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.
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
.VfsUtil
;
26 import com
.intellij
.openapi
.vfs
.VirtualFile
;
27 import com
.intellij
.ui
.*;
28 import com
.intellij
.util
.Processor
;
29 import com
.intellij
.util
.containers
.HashMap
;
30 import com
.intellij
.util
.ui
.UIUtil
;
31 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
32 import git4idea
.GitUtil
;
33 import git4idea
.GitVcs
;
34 import git4idea
.commands
.GitCommand
;
35 import git4idea
.commands
.GitSimpleHandler
;
36 import git4idea
.commands
.StringScanner
;
37 import git4idea
.config
.GitVcsSettings
;
38 import git4idea
.config
.GitVersion
;
39 import git4idea
.i18n
.GitBundle
;
42 import java
.awt
.event
.ActionEvent
;
43 import java
.io
.IOException
;
44 import java
.io
.OutputStream
;
45 import java
.io
.OutputStreamWriter
;
49 * This dialog allows converting the specified files before committing them.
51 public class GitConvertFilesDialog
extends DialogWrapper
{
53 * The version when option --stdin was added
55 private static final GitVersion CHECK_ATTR_STDIN_SUPPORTED
= new GitVersion(1, 6, 1, 0);
57 * Do not convert exit code
59 public static final int DO_NOT_CONVERT
= NEXT_USER_EXIT_CODE
;
61 * The checkbox used to indicate that dialog should not be shown
63 private JCheckBox myDoNotShowCheckBox
;
65 * The root panel of the dialog
67 private JPanel myRootPanel
;
69 * The tree of files to convert
71 private CheckboxTreeBase myFilesToConvert
;
73 * The root node in the tree
75 private CheckedTreeNode myRootNode
;
80 * @param project the project to which this dialog is related
81 * @param filesToShow the files to show sorted by vcs root
83 GitConvertFilesDialog(Project project
, Map
<VirtualFile
, Set
<VirtualFile
>> filesToShow
) {
85 ArrayList
<VirtualFile
> roots
= new ArrayList
<VirtualFile
>(filesToShow
.keySet());
86 Collections
.sort(roots
, GitUtil
.VIRTUAL_FILE_COMPARATOR
);
87 for (VirtualFile root
: roots
) {
88 CheckedTreeNode vcsRoot
= new CheckedTreeNode(root
);
89 myRootNode
.add(vcsRoot
);
90 ArrayList
<VirtualFile
> files
= new ArrayList
<VirtualFile
>(filesToShow
.get(root
));
91 Collections
.sort(files
, GitUtil
.VIRTUAL_FILE_COMPARATOR
);
92 for (VirtualFile file
: files
) {
93 vcsRoot
.add(new CheckedTreeNode(file
));
96 TreeUtil
.expandAll(myFilesToConvert
);
97 setTitle(GitBundle
.getString("crlf.convert.title"));
98 setOKButtonText(GitBundle
.getString("crlf.convert.convert"));
106 protected Action
[] createActions() {
107 return new Action
[]{getOKAction(), new DoNotConvertAction(), getCancelAction()};
112 * Create custom UI components
114 private void createUIComponents() {
115 myRootNode
= new CheckedTreeNode("ROOT");
116 myFilesToConvert
= new CheckboxTree(new FileTreeCellRenderer(), myRootNode
) {
117 protected void onNodeStateChanged(CheckedTreeNode node
) {
118 VirtualFile
[] files
= myFilesToConvert
.getCheckedNodes(VirtualFile
.class, null);
119 setOKActionEnabled(files
!= null && files
.length
> 0);
120 super.onNodeStateChanged(node
);
129 protected JComponent
createCenterPanel() {
137 protected String
getDimensionServiceKey() {
138 return getClass().getName();
142 * Check if files need to be converted to other line separator. The method could be invoked from non-UI thread.
144 * @param project the project to use
145 * @param settings the vcs settings
146 * @param sortedChanges sorted changes
147 * @param exceptions the collection with exceptions
148 * @return true if conversion completed successfully, false if process was cancelled or there were errors
150 public static boolean showDialogIfNeeded(final Project project
,
151 final GitVcsSettings settings
,
152 Map
<VirtualFile
, List
<Change
>> sortedChanges
,
153 final List
<VcsException
> exceptions
) {
155 if (settings
.LINE_SEPARATORS_CONVERSION_ASK
||
156 settings
.LINE_SEPARATORS_CONVERSION
== GitVcsSettings
.ConversionPolicy
.PROJECT_LINE_SEPARATORS
) {
157 LocalFileSystem lfs
= LocalFileSystem
.getInstance();
158 final String nl
= CodeStyleFacade
.getInstance(project
).getLineSeparator();
159 final Map
<VirtualFile
, Set
<VirtualFile
>> files
= new HashMap
<VirtualFile
, Set
<VirtualFile
>>();
160 // preliminary screening of files
161 for (Map
.Entry
<VirtualFile
, List
<Change
>> entry
: sortedChanges
.entrySet()) {
162 final VirtualFile root
= entry
.getKey();
163 final Set
<VirtualFile
> added
= new HashSet
<VirtualFile
>();
164 for (Change change
: entry
.getValue()) {
165 switch (change
.getType()) {
169 VirtualFile f
= lfs
.findFileByPath(change
.getAfterRevision().getFile().getPath());
170 if (f
!= null && !f
.getFileType().isBinary() && !nl
.equals(LoadTextUtil
.detectLineSeparator(f
, false))) {
177 if (!added
.isEmpty()) {
178 files
.put(root
, added
);
181 // ignore files with CRLF unset
182 ignoreFilesWithCrlfUnset(project
, files
);
183 // check crlf for real
184 for (Iterator
<Map
.Entry
<VirtualFile
, Set
<VirtualFile
>>> i
= files
.entrySet().iterator(); i
.hasNext();) {
185 Map
.Entry
<VirtualFile
, Set
<VirtualFile
>> e
= i
.next();
186 Set
<VirtualFile
> fs
= e
.getValue();
187 for (Iterator
<VirtualFile
> j
= fs
.iterator(); j
.hasNext();) {
188 VirtualFile f
= j
.next();
189 String detectedLineSeparator
= LoadTextUtil
.detectLineSeparator(f
, true);
190 if (detectedLineSeparator
== null || nl
.equals(detectedLineSeparator
)) {
198 if (files
.isEmpty()) {
201 UIUtil
.invokeAndWaitIfNeeded(new Runnable() {
203 VirtualFile
[] selectedFiles
= null;
204 if (settings
.LINE_SEPARATORS_CONVERSION_ASK
) {
205 GitConvertFilesDialog d
= new GitConvertFilesDialog(project
, files
);
208 settings
.LINE_SEPARATORS_CONVERSION_ASK
= !d
.myDoNotShowCheckBox
.isSelected();
209 settings
.LINE_SEPARATORS_CONVERSION
= GitVcsSettings
.ConversionPolicy
.PROJECT_LINE_SEPARATORS
;
210 selectedFiles
= d
.myFilesToConvert
.getCheckedNodes(VirtualFile
.class, null);
212 else if (d
.getExitCode() == DO_NOT_CONVERT
) {
213 settings
.LINE_SEPARATORS_CONVERSION_ASK
= !d
.myDoNotShowCheckBox
.isSelected();
214 settings
.LINE_SEPARATORS_CONVERSION
= GitVcsSettings
.ConversionPolicy
.NONE
;
217 //noinspection ThrowableInstanceNeverThrown
218 exceptions
.add(new VcsException("Commit was cancelled in file conversion dialog"));
222 ArrayList
<VirtualFile
> fileList
= new ArrayList
<VirtualFile
>();
223 for (Set
<VirtualFile
> fileSet
: files
.values()) {
224 fileList
.addAll(fileSet
);
226 selectedFiles
= VfsUtil
.toVirtualFileArray(fileList
);
228 if (selectedFiles
!= null) {
229 for (VirtualFile f
: selectedFiles
) {
231 LoadTextUtil
.changeLineSeparator(project
, GitConvertFilesDialog
.class.getName(), f
, nl
);
233 catch (IOException e
) {
234 //noinspection ThrowableInstanceNeverThrown
235 exceptions
.add(new VcsException("Failed to change line separators for the file: " + f
.getPresentableUrl(), e
));
243 catch (VcsException e
) {
246 return exceptions
.isEmpty();
250 * Remove files that have -crlf attribute specified
252 * @param project the context project
253 * @param files the files to check (map from vcs roots to the set of files under root)
254 * @throws VcsException if there is problem with running git
256 private static void ignoreFilesWithCrlfUnset(Project project
, Map
<VirtualFile
, Set
<VirtualFile
>> files
) throws VcsException
{
257 boolean stdin
= CHECK_ATTR_STDIN_SUPPORTED
.isLessOrEqual(GitVcs
.getInstance(project
).version());
258 for (final Map
.Entry
<VirtualFile
, Set
<VirtualFile
>> e
: files
.entrySet()) {
259 final VirtualFile r
= e
.getKey();
260 GitSimpleHandler h
= new GitSimpleHandler(project
, r
, GitCommand
.CHECK_ATTR
);
262 h
.addParameters("--stdin", "-z");
264 h
.addParameters("crlf");
267 final HashMap
<String
, VirtualFile
> filesToCheck
= new HashMap
<String
, VirtualFile
>();
268 Set
<VirtualFile
> fileSet
= e
.getValue();
269 for (VirtualFile file
: fileSet
) {
270 filesToCheck
.put(GitUtil
.relativePath(r
, file
), file
);
273 h
.setInputProcessor(new Processor
<OutputStream
>() {
274 public boolean process(OutputStream outputStream
) {
276 OutputStreamWriter out
= new OutputStreamWriter(outputStream
, GitUtil
.UTF8_CHARSET
);
278 for (String file
: filesToCheck
.keySet()) {
287 catch (IOException ex
) {
289 outputStream
.close();
291 catch (IOException ioe
) {
301 h
.addRelativeFiles(filesToCheck
.values());
303 StringScanner output
= new StringScanner(h
.run());
304 String unsetIndicator
= ": crlf: unset";
305 while (output
.hasMoreData()) {
306 String l
= output
.line();
307 if (l
.endsWith(unsetIndicator
)) {
308 fileSet
.remove(filesToCheck
.get(GitUtil
.unescapePath(l
.substring(0, l
.length() - unsetIndicator
.length()))));
315 * Action used to indicate that no conversion should be performed
317 class DoNotConvertAction
extends AbstractAction
{
318 private static final long serialVersionUID
= 1931383640152023206L;
323 DoNotConvertAction() {
324 putValue(NAME
, GitBundle
.getString("crlf.convert.leave"));
325 putValue(DEFAULT_ACTION
, Boolean
.FALSE
);
331 public void actionPerformed(ActionEvent e
) {
332 if (myPerformAction
) return;
334 myPerformAction
= true;
335 close(DO_NOT_CONVERT
);
338 myPerformAction
= false;
345 * The cell renderer for the tree
347 static class FileTreeCellRenderer
extends CheckboxTree
.CheckboxTreeCellRenderer
{
352 public void customizeRenderer(JTree tree
, Object value
, boolean selected
, boolean expanded
, boolean leaf
, int row
, boolean hasFocus
) {
353 ColoredTreeCellRenderer r
= getTextRenderer();
354 if (!(value
instanceof CheckedTreeNode
)) {
356 renderUnknown(r
, value
);
359 CheckedTreeNode node
= (CheckedTreeNode
)value
;
360 if (!(node
.getUserObject() instanceof VirtualFile
)) {
362 renderUnknown(r
, node
.getUserObject());
365 VirtualFile file
= (VirtualFile
)node
.getUserObject();
367 VirtualFile parent
= (VirtualFile
)((CheckedTreeNode
)node
.getParent()).getUserObject();
369 Icon i
= file
.getIcon();
373 r
.append(GitUtil
.getRelativeFilePath(file
, parent
), SimpleTextAttributes
.REGULAR_ATTRIBUTES
, true);
377 r
.append(file
.getPresentableUrl(), SimpleTextAttributes
.REGULAR_BOLD_ATTRIBUTES
, true);
382 * Render unknown node
384 * @param r a renderer to use
385 * @param value the unknown value
387 private static void renderUnknown(ColoredTreeCellRenderer r
, Object value
) {
388 r
.append("UNSUPPORTED NODE TYPE: " + (value
== null ?
"null" : value
.getClass().getName()), SimpleTextAttributes
.ERROR_ATTRIBUTES
);