blob: 68bb3a11578f1d042fe90f7793864f1b126fed42 [file] [log] [blame]
/*
* 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;
}
}