| /* |
| * 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.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProgressIndicatorProvider; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.io.FileUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import git4idea.GitPlatformFacade; |
| import git4idea.GitUtil; |
| import git4idea.attributes.GitAttribute; |
| import git4idea.attributes.GitCheckAttrParser; |
| import git4idea.commands.Git; |
| import git4idea.commands.GitCommandResult; |
| import git4idea.config.GitConfigUtil; |
| import git4idea.repo.GitRepository; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.*; |
| |
| /** |
| * Given a number of files, detects if CRLF line separators in them are about to be committed to Git. That is: |
| * <ul> |
| * <li>Checks if {@code core.autocrlf} is set to {@code true} or {@code input}.</li> |
| * <li>If not, checks if files contain CRLFs.</li> |
| * <li> |
| * 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. |
| * </li> |
| * </ul> |
| * All checks are made only for Windows system. |
| * |
| * Everywhere in the detection process we fail gracefully in case of exceptions: we log the fact, but don't fail totally, preferring to |
| * tell that the calling code should not warn the user. |
| * |
| * @author Kirill Likhodedov |
| */ |
| public class GitCrlfProblemsDetector { |
| |
| private static final Logger LOG = Logger.getInstance(GitCrlfProblemsDetector.class); |
| private static final String CRLF = "\r\n"; |
| |
| @NotNull private final Project myProject; |
| @NotNull private final GitPlatformFacade myPlatformFacade; |
| @NotNull private final Git myGit; |
| |
| private final boolean myShouldWarn; |
| |
| @NotNull |
| public static GitCrlfProblemsDetector detect(@NotNull Project project, @NotNull GitPlatformFacade platformFacade, |
| @NotNull Git git, @NotNull Collection<VirtualFile> files) { |
| return new GitCrlfProblemsDetector(project, platformFacade, git, files); |
| } |
| |
| private GitCrlfProblemsDetector(@NotNull Project project, @NotNull GitPlatformFacade platformFacade, @NotNull Git git, |
| @NotNull Collection<VirtualFile> files) { |
| myProject = project; |
| myPlatformFacade = platformFacade; |
| myGit = git; |
| |
| Map<VirtualFile, List<VirtualFile>> filesByRoots = sortFilesByRoots(files); |
| |
| boolean shouldWarn = false; |
| Collection<VirtualFile> rootsWithIncorrectAutoCrlf = getRootsWithIncorrectAutoCrlf(filesByRoots); |
| if (!rootsWithIncorrectAutoCrlf.isEmpty()) { |
| Map<VirtualFile, Collection<VirtualFile>> crlfFilesByRoots = findFilesWithCrlf(filesByRoots, rootsWithIncorrectAutoCrlf); |
| if (!crlfFilesByRoots.isEmpty()) { |
| Map<VirtualFile, Collection<VirtualFile>> crlfFilesWithoutAttrsByRoots = findFilesWithoutAttrs(crlfFilesByRoots); |
| shouldWarn = !crlfFilesWithoutAttrsByRoots.isEmpty(); |
| } |
| } |
| myShouldWarn = shouldWarn; |
| } |
| |
| private Map<VirtualFile, Collection<VirtualFile>> findFilesWithoutAttrs(Map<VirtualFile, Collection<VirtualFile>> filesByRoots) { |
| Map<VirtualFile, Collection<VirtualFile>> filesWithoutAttrsByRoot = new HashMap<VirtualFile, Collection<VirtualFile>>(); |
| for (Map.Entry<VirtualFile, Collection<VirtualFile>> entry : filesByRoots.entrySet()) { |
| VirtualFile root = entry.getKey(); |
| Collection<VirtualFile> files = entry.getValue(); |
| Collection<VirtualFile> filesWithoutAttrs = findFilesWithoutAttrs(root, files); |
| if (!filesWithoutAttrs.isEmpty()) { |
| filesWithoutAttrsByRoot.put(root, filesWithoutAttrs); |
| } |
| } |
| return filesWithoutAttrsByRoot; |
| } |
| |
| @NotNull |
| private Collection<VirtualFile> findFilesWithoutAttrs(@NotNull VirtualFile root, @NotNull Collection<VirtualFile> files) { |
| GitRepository repository = myPlatformFacade.getRepositoryManager(myProject).getRepositoryForRoot(root); |
| if (repository == null) { |
| LOG.warn("Repository is null for " + root); |
| return Collections.emptyList(); |
| } |
| Collection<String> interestingAttributes = Arrays.asList(GitAttribute.TEXT.getName(), GitAttribute.CRLF.getName()); |
| GitCommandResult result = myGit.checkAttr(repository, interestingAttributes, files); |
| if (!result.success()) { |
| LOG.warn(String.format("Couldn't git check-attr. Attributes: %s, files: %s", interestingAttributes, files)); |
| return Collections.emptyList(); |
| } |
| GitCheckAttrParser parser = GitCheckAttrParser.parse(result.getOutput()); |
| Map<String, Collection<GitAttribute>> attributes = parser.getAttributes(); |
| Collection<VirtualFile> filesWithoutAttrs = new ArrayList<VirtualFile>(); |
| for (VirtualFile file : files) { |
| ProgressIndicatorProvider.checkCanceled(); |
| String relativePath = FileUtil.getRelativePath(root.getPath(), file.getPath(), '/'); |
| Collection<GitAttribute> attrs = attributes.get(relativePath); |
| if (attrs == null || !attrs.contains(GitAttribute.TEXT) && !attrs.contains(GitAttribute.CRLF)) { |
| filesWithoutAttrs.add(file); |
| } |
| } |
| return filesWithoutAttrs; |
| } |
| |
| @NotNull |
| private Map<VirtualFile, Collection<VirtualFile>> findFilesWithCrlf(@NotNull Map<VirtualFile, List<VirtualFile>> allFilesByRoots, |
| @NotNull Collection<VirtualFile> rootsWithIncorrectAutoCrlf) { |
| Map<VirtualFile, Collection<VirtualFile>> filesWithCrlfByRoots = new HashMap<VirtualFile, Collection<VirtualFile>>(); |
| for (Map.Entry<VirtualFile, List<VirtualFile>> entry : allFilesByRoots.entrySet()) { |
| VirtualFile root = entry.getKey(); |
| List<VirtualFile> files = entry.getValue(); |
| if (rootsWithIncorrectAutoCrlf.contains(root)) { |
| Collection<VirtualFile> filesWithCrlf = findFilesWithCrlf(files); |
| if (!filesWithCrlf.isEmpty()) { |
| filesWithCrlfByRoots.put(root, filesWithCrlf); |
| } |
| } |
| } |
| return filesWithCrlfByRoots; |
| } |
| |
| @NotNull |
| private Collection<VirtualFile> findFilesWithCrlf(@NotNull Collection<VirtualFile> files) { |
| Collection<VirtualFile> filesWithCrlf = new ArrayList<VirtualFile>(); |
| for (VirtualFile file : files) { |
| ProgressIndicatorProvider.checkCanceled(); |
| String separator = myPlatformFacade.getLineSeparator(file, true); |
| if (CRLF.equals(separator)) { |
| filesWithCrlf.add(file); |
| } |
| } |
| return filesWithCrlf; |
| } |
| |
| @NotNull |
| private Collection<VirtualFile> getRootsWithIncorrectAutoCrlf(@NotNull Map<VirtualFile, List<VirtualFile>> filesByRoots) { |
| Collection<VirtualFile> rootsWithIncorrectAutoCrlf = new ArrayList<VirtualFile>(); |
| for (Map.Entry<VirtualFile, List<VirtualFile>> entry : filesByRoots.entrySet()) { |
| VirtualFile root = entry.getKey(); |
| boolean autocrlf = isAutoCrlfSetRight(root); |
| if (!autocrlf) { |
| rootsWithIncorrectAutoCrlf.add(root); |
| } |
| } |
| return rootsWithIncorrectAutoCrlf; |
| } |
| |
| private boolean isAutoCrlfSetRight(@NotNull VirtualFile root) { |
| GitRepository repository = myPlatformFacade.getRepositoryManager(myProject).getRepositoryForRoot(root); |
| if (repository == null) { |
| LOG.warn("Repository is null for " + root); |
| return true; |
| } |
| GitCommandResult result = myGit.config(repository, GitConfigUtil.CORE_AUTOCRLF); |
| String value = result.getOutputAsJoinedString(); |
| return value.equalsIgnoreCase("true") || value.equalsIgnoreCase("input"); |
| } |
| |
| @NotNull |
| private static Map<VirtualFile, List<VirtualFile>> sortFilesByRoots(@NotNull Collection<VirtualFile> files) { |
| return GitUtil.sortFilesByGitRootsIgnoringOthers(files); |
| } |
| |
| public boolean shouldWarn() { |
| return myShouldWarn; |
| } |
| |
| } |