IDEA-89754 Add a pre-commit check on core.autocrlf option and warn if CRLF is about to be committed to the repository. Step #2

* Write the detector according to the following algorithm:
  - check core.autocrlf
  - check if files to commit have CRLFs
  - check if there are CRLF-related attributes on these files.
* Respect multi-roots.

Tests:
* Write a test on the detector and the parser of the output of `git check-attr`.
* Try one more approach on unit testing Git: declare GitTestImpl as more real Git implementation than MockGit is: this one calls git. The tests are fast enough.
* Several fixes and enhancements in the mocks.
* Introduce GitExecutor which allows a nice syntax of calling to Git. This can be used as a mixin in tests.
This commit is contained in:
Kirill Likhodedov
2012-08-08 14:10:21 +04:00
parent 638b0e7629
commit 264de815c7
21 changed files with 960 additions and 30 deletions
@@ -17,6 +17,7 @@ package git4idea;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
@@ -86,4 +87,6 @@ public interface PlatformFacade {
@Nullable
IdeaPluginDescriptor getPluginByClassName(@NotNull String name);
@NotNull
FileDocumentManager getFileDocumentManager();
}
@@ -20,6 +20,7 @@ import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
@@ -109,6 +110,12 @@ public class PlatformFacadeImpl implements PlatformFacade {
return PluginManager.getPlugin(PluginManager.getPluginByClassName(name));
}
@NotNull
@Override
public FileDocumentManager getFileDocumentManager() {
return FileDocumentManager.getInstance();
}
@NotNull
@Override
public AbstractVcs getVcs(@NotNull Project project) {
@@ -0,0 +1,48 @@
/*
* 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.attributes;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Kirill Likhodedov
*/
public enum GitAttribute {
TEXT("text"),
CRLF("crlf");
@NotNull private final String myName;
GitAttribute(@NotNull String name) {
myName = name;
}
@NotNull
public String getName() {
return myName;
}
@Nullable
public static GitAttribute forName(String attribute) {
for (GitAttribute attr : values()) {
if (attr.myName.equalsIgnoreCase(attribute)) {
return attr;
}
}
return null;
}
}
@@ -0,0 +1,75 @@
/*
* 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.attributes;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Parses the output of {@code git check-attr}.
* All supported attributes are instances of {@link GitAttribute}. Others just ignored until needed.
* Values are also ignored: we just need to know if there is a specified attribute on a file, or not.
*
* @author Kirill Likhodedov
*/
public class GitCheckAttrParser {
private static final Logger LOG = Logger.getInstance(GitCheckAttrParser.class);
private static final String UNSPECIFIED_VALUE = "unspecified";
@NotNull private final Map<String, Collection<GitAttribute>> myAttributes;
private GitCheckAttrParser(@NotNull List<String> output) {
myAttributes = new HashMap<String, Collection<GitAttribute>>();
for (String line : output) {
if (line.isEmpty()) {
continue;
}
List<String> split = StringUtil.split(line, ":");
LOG.assertTrue(split.size() == 3, String.format("Output doesn't match the expected format. Line: %s%nAll output:%n%s",
line, StringUtil.join(output, "\n")));
String file = split.get(0).trim();
String attribute = split.get(1).trim();
String info = split.get(2).trim();
GitAttribute attr = GitAttribute.forName(attribute);
if (attr == null || info.equalsIgnoreCase(UNSPECIFIED_VALUE)) {
// ignoring attributes that we are not interested in
continue;
}
if (myAttributes.get(file) == null) {
myAttributes.put(file, new ArrayList<GitAttribute>());
}
myAttributes.get(file).add(attr);
}
}
@NotNull
public static GitCheckAttrParser parse(@NotNull List<String> output) {
return new GitCheckAttrParser(output);
}
@NotNull
public Map<String, Collection<GitAttribute>> getAttributes() {
return myAttributes;
}
}
@@ -15,6 +15,7 @@
*/
package git4idea.checkin;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
@@ -33,6 +34,8 @@ import com.intellij.util.PairConsumer;
import com.intellij.util.ui.UIUtil;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.PlatformFacade;
import git4idea.commands.Git;
import git4idea.config.GitConfigUtil;
import git4idea.config.GitVcsSettings;
import git4idea.config.GitVersion;
@@ -102,7 +105,11 @@ public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory {
return ReturnResult.COMMIT;
}
GitCrlfProblemsDetector crlfHelper = GitCrlfProblemsDetector.detect(myPanel.getVirtualFiles());
PlatformFacade platformFacade = ServiceManager.getService(myProject, PlatformFacade.class);
Git git = ServiceManager.getService(Git.class);
Collection<VirtualFile> files = myPanel.getVirtualFiles(); // deleted files aren't included, but for them we don't care about CRLFs.
GitCrlfProblemsDetector crlfHelper = GitCrlfProblemsDetector.detect(myProject, platformFacade, git, files);
if (crlfHelper.shouldWarn()) {
final GitCrlfDialog dialog = new GitCrlfDialog(myProject);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@@ -48,6 +48,9 @@ public interface Git {
@NotNull
GitCommandResult clone(@NotNull Project project, @NotNull File parentDirectory, @NotNull String url, @NotNull String clonedDirectoryName);
@NotNull
GitCommandResult config(@NotNull GitRepository repository, String... params);
@NotNull
GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, @Nullable List<String> additionalParams,
@NotNull GitLineHandlerListener... listeners);
@@ -101,4 +104,7 @@ public interface Git {
@NotNull
GitCommandResult getUnmergedFiles(@NotNull GitRepository repository);
@NotNull
GitCommandResult checkAttr(@NotNull GitRepository repository, @NotNull Collection<String> attributes,
@NotNull Collection<VirtualFile> files);
}
@@ -136,6 +136,25 @@ public class GitImpl implements Git {
return run(handler, true);
}
@NotNull
@Override
public GitCommandResult config(@NotNull GitRepository repository, String... params) {
final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CONFIG);
h.addParameters(params);
return run(h);
}
@NotNull
@Override
public GitCommandResult checkAttr(@NotNull GitRepository repository, @NotNull Collection<String> attributes,
@NotNull Collection<VirtualFile> files) {
final GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.CONFIG);
h.addParameters(new ArrayList<String>(attributes));
h.endOptions();
h.addRelativeFiles(files);
return run(h);
}
@Override
@NotNull
public GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge,
@@ -15,10 +15,22 @@
*/
package git4idea.crlf;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VirtualFile;
import git4idea.GitUtil;
import git4idea.PlatformFacade;
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.Collection;
import java.util.*;
/**
* Given a number of files, detects if CRLF line separators in them are about to be committed to Git. That is:
@@ -32,17 +44,152 @@ import java.util.Collection;
* </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 PlatformFacade myPlatformFacade;
@NotNull private final Git myGit;
private final boolean myShouldWarn;
@NotNull
public static GitCrlfProblemsDetector detect(Collection<VirtualFile> files) {
return new GitCrlfProblemsDetector();
public static GitCrlfProblemsDetector detect(@NotNull Project project, @NotNull PlatformFacade platformFacade,
@NotNull Git git, @NotNull Collection<VirtualFile> files) {
return new GitCrlfProblemsDetector(project, platformFacade, git, files);
}
private GitCrlfProblemsDetector(@NotNull Project project, @NotNull PlatformFacade 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);
GitCheckAttrParser parser = GitCheckAttrParser.parse(result.getOutput());
Map<String, Collection<GitAttribute>> attributes = parser.getAttributes();
Collection<VirtualFile> filesWithoutAttrs = new ArrayList<VirtualFile>();
for (VirtualFile file : files) {
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) {
if (CRLF.equals(myPlatformFacade.getFileDocumentManager().getLineSeparator(file, myProject))) {
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);
if (!result.success()) {
LOG.warn("Couldn't get git config core.autocrlf for " + root);
return true;
}
String value = result.getOutputAsJoinedString();
return value.equalsIgnoreCase("true") || value.equalsIgnoreCase("input");
}
@NotNull
private static Map<VirtualFile, List<VirtualFile>> sortFilesByRoots(@NotNull Collection<VirtualFile> files) {
try {
return GitUtil.sortFilesByGitRoot(files, true);
}
catch (VcsException e) {
LOG.error("Should never happen, since we passed 'ignore non-git' parameter", e);
return Collections.emptyMap();
}
}
public boolean shouldWarn() {
return false;
return myShouldWarn;
}
}
@@ -0,0 +1,45 @@
/*
* 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.attributes
import org.junit.Test
import static org.junit.Assert.assertEquals
import static org.junit.Assert.assertTrue
/**
*
* @author Kirill Likhodedov
*/
class GitCheckAttrParserTest {
@Test
void "test"() {
String output =
"""
org/example/MyClass.java: crlf: unset
org/example/MyClass.java: diff: java
org/example/MyClass.java: myAttr: set
"""
GitCheckAttrParser parse = GitCheckAttrParser.parse(Arrays.asList(output.split("\n")))
String path = "org/example/MyClass.java"
assertTrue "Missing path", parse.attributes.containsKey(path)
Collection<GitAttribute> attrs = parse.attributes.get(path)
assertTrue "CRLF attribute not found", attrs.contains(GitAttribute.CRLF)
assertEquals "Too many attributes", 1, attrs.size()
}
}
@@ -0,0 +1,200 @@
/*
* 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.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import git4idea.PlatformFacade
import git4idea.commands.Git
import git4idea.repo.GitRepository
import git4idea.repo.GitRepositoryImpl
import git4idea.test.*
import org.junit.After
import org.junit.Before
import org.junit.Test
import static org.junit.Assert.assertFalse
import static org.junit.Assert.assertTrue
/**
*
* @author Kirill Likhodedov
*/
@Mixin(GitExecutor)
class GitCrlfProblemsDetectorTest {
private GitMockProject myProject
private String myRootDir
private PlatformFacade myPlatformFacade
private Git myGit
private String myOldCoreAutoCrlfValue
@Before
public void setUp() throws Exception {
myRootDir = FileUtil.createTempDirectory("", "").getPath()
myProject = new GitMockProject(myRootDir)
myPlatformFacade = new GitTestPlatformFacade()
myGit = new GitTestImpl()
cd myRootDir
myOldCoreAutoCrlfValue = git "config --global core.autocrlf"
git "init"
git "config --global --unset core.autocrlf"
GitRepository repository = GitRepositoryImpl.getLightInstance(new GitMockVirtualFile(myRootDir), myProject, myProject)
((GitTestRepositoryManager)myPlatformFacade.getRepositoryManager(myProject)).add(repository)
}
@After
public void tearDown() throws Exception {
if (!StringUtil.isEmptyOrSpaces(myOldCoreAutoCrlfValue)) {
git "config --global core.autocrlf " + myOldCoreAutoCrlfValue;
}
FileUtil.delete(new File(myRootDir))
}
@Test
void "core.autocrlf is true = no warning"() {
git "config core.autocrlf true"
assertFalse "No warning should be done if core.autocrlf is true", detect("temp").shouldWarn()
}
@Test
void "core.autocrlf is input = no warning"() {
git "config core.autocrlf input"
assertFalse "No warning should be done if core.autocrlf is input", detect("temp").shouldWarn()
}
@Test
void "no files with CRLF = no warning"() {
createFile("temp", "Unix file\nNice separators\nOnly LF\n")
assertFalse "No warning should be done if all files are LFs", detect("temp").shouldWarn()
}
@Test
void "file with CRLF, no attrs, core.autocrlf is false = warning"() {
createCrlfFile("win")
assertFalse "Warning should be done for a CRLF file with core.autocrlf = false", detect("temp").shouldWarn()
}
@Test
void "file with CRLF, but text is set = no warning"() {
gitattributes("* text=auto")
createCrlfFile("win")
assertFalse "No warning should be done if the file has a text attribute", detect("win").shouldWarn()
}
@Test
void "file with CRLF, but crlf is set = no warning"() {
gitattributes("win crlf")
createCrlfFile("win")
assertFalse "No warning should be done if the file has a crlf attribute", detect("win").shouldWarn()
}
@Test
void "file with CRLF, but crlf is explicitly unset = no warning"() {
gitattributes("win -crlf")
createCrlfFile("win")
assertFalse "No warning should be done if the file has an explicitly unset crlf attribute", detect("win").shouldWarn()
}
@Test
void "file with CRLF, but crlf is set to input = no warning"() {
gitattributes("wi* crlf=input")
createCrlfFile("win")
assertFalse "No warning should be done if the file has a crlf attribute", detect("win").shouldWarn()
}
@Test
void "file with CRLF, nothing is set = warning"() {
createCrlfFile("win")
assertTrue "Warning should be done if the file has CRLFs inside, and no explicit attributes", detect("win").shouldWarn()
}
@Test
void "various files with various attributes, one doesn't match = warning"() {
gitattributes """
win1 text crlf diff
win2 -text
win3 text=auto
win4 crlf
win5 -crlf
win6 crlf=input
"""
createFile("unix", "Unix file\nNice separators\nOnly LF\n")
createCrlfFile("win1")
createCrlfFile("win2")
createCrlfFile("win3")
createCrlfFile("src/win4")
createCrlfFile("src/win5")
createCrlfFile("src/win6")
createCrlfFile("src/win7")
assertTrue "Warning should be done, since one of the files has CRLFs and no related attributes",
GitCrlfProblemsDetector.detect(myProject, myPlatformFacade, myGit,
["unix", "win1", "win2", "win3", "src/win4", "src/win5", "src/win6", "src/win7"]
.collect { it -> GitMockVirtualFile.fromPath(it, myRootDir) as VirtualFile})
.shouldWarn()
}
private void gitattributes(String content) {
createFile(".gitattributes", content)
}
private GitCrlfProblemsDetector detect(String relPath) {
return detect(createVirtualFile(relPath))
}
private GitCrlfProblemsDetector detect(VirtualFile file) {
GitCrlfProblemsDetector.detect(myProject, myPlatformFacade, myGit, Collections.<VirtualFile> singleton(file))
}
private void createCrlfFile(String relPath) {
createFile(relPath, "Windows file\r\nBad separators\r\nCRLF in action\r\n")
}
private void createFile(String relPath, String content) {
String path = createFile(relPath)
File file = new File(path)
FileUtil.appendToFile(file, content)
}
private VirtualFile createVirtualFile(String relPath) {
return new GitMockVirtualFile(createFile(relPath))
}
private String createFile(String relPath) {
List<String> split = StringUtil.split(relPath, "/")
File parent = new File(myRootDir)
for (Iterator<String> it = split.iterator(); it.hasNext(); ) {
String item = it.next()
File file = new File(parent, item)
if (it.hasNext()) {
parent = file
parent.mkdir()
}
else {
file.createNewFile()
return file.getPath()
}
}
}
}
@@ -153,7 +153,7 @@ public class GitExec {
}
@NotNull
private static String run(@NotNull GitRepository repository, @NotNull String command, String... params) throws IOException {
public static String run(@NotNull GitRepository repository, @NotNull String command, String... params) throws IOException {
return new GitTestRunEnv(new File(repository.getRoot().getPath())).run(command, params);
}
@@ -0,0 +1,39 @@
/*
* 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.test
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.ArrayUtil
/**
*
* @author Kirill Likhodedov
*/
class GitExecutor {
private String myCurrentDir
void cd(String path) {
myCurrentDir = path
}
String git(String command) {
List<String> split = StringUtil.split(command, " ")
String[] params = split.size() > 1 ? ArrayUtil.toObjectArray(split.subList(1, split.size()), String) : ArrayUtil.EMPTY_STRING_ARRAY
return new GitTestRunEnv(new File(myCurrentDir)).run(split.get(0), params);
}
}
@@ -0,0 +1,104 @@
/*
* 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.test;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
/**
* @author Kirill Likhodedov
*/
class GitMockFileDocumentManager extends FileDocumentManager {
@Override
public Document getDocument(@NotNull VirtualFile file) {
throw new UnsupportedOperationException();
}
@Override
public Document getCachedDocument(@NotNull VirtualFile file) {
throw new UnsupportedOperationException();
}
@Override
public VirtualFile getFile(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@Override
public void saveAllDocuments() {
throw new UnsupportedOperationException();
}
@Override
public void saveDocument(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@Override
public void saveDocumentAsIs(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Document[] getUnsavedDocuments() {
throw new UnsupportedOperationException();
}
@Override
public boolean isDocumentUnsaved(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@Override
public boolean isFileModified(@NotNull VirtualFile file) {
throw new UnsupportedOperationException();
}
@Override
public void reloadFromDisk(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public String getLineSeparator(@Nullable VirtualFile file, @Nullable Project project) {
try {
return FileUtil.loadFile(new File(file.getPath())).contains("\r\n") ? "\r\n" : "\n";
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean requestWriting(@NotNull Document document, @Nullable Project project) {
throw new UnsupportedOperationException();
}
@Override
public void reloadFiles(VirtualFile... files) {
throw new UnsupportedOperationException();
}
}
@@ -139,7 +139,7 @@ class GitMockProject implements Project {
@Override
MessageBus getMessageBus() {
throw new UnsupportedOperationException()
null
}
@Override
@@ -155,7 +155,7 @@ class GitMockProject implements Project {
@NotNull
@Override
Condition getDisposed() {
throw new UnsupportedOperationException()
Condition.FALSE
}
@Override
@@ -42,6 +42,11 @@ class GitMockVirtualFile extends VirtualFile {
return fromPath(new File(project.getBaseDir().getPath() + "/" + relativePath).getCanonicalPath())
}
@NotNull
static VirtualFile fromPath(@NotNull String relativePath, @NotNull String basePath) {
return fromPath(new File(basePath + "/" + relativePath).getCanonicalPath())
}
GitMockVirtualFile(@NotNull String path) {
myPath = FileUtil.toSystemIndependentName(path);
}
@@ -0,0 +1,198 @@
/*
* 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.test
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vcs.VcsException
import com.intellij.openapi.vfs.VirtualFile
import git4idea.commands.Git
import git4idea.commands.GitCommandResult
import git4idea.commands.GitLineHandlerListener
import git4idea.push.GitPushSpec
import git4idea.repo.GitRepository
import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.Nullable
/**
* @author Kirill Likhodedov
*/
@Mixin(GitExecutor)
public class GitTestImpl implements Git {
@NotNull
@Override
public GitCommandResult init(@NotNull Project project, @NotNull VirtualFile root, @NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Set<VirtualFile> untrackedFiles(@NotNull Project project, @NotNull VirtualFile root, @Nullable Collection<VirtualFile> files)
throws VcsException {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public Collection<VirtualFile> untrackedFilesNoChunk(@NotNull Project project,
@NotNull VirtualFile root,
@Nullable List<String> relativePaths) throws VcsException {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult clone(@NotNull Project project,
@NotNull File parentDirectory,
@NotNull String url,
@NotNull String clonedDirectoryName) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult config(@NotNull GitRepository repository, String... params) {
cd repository.getRoot().getPath()
String output = git "config " + params.join(" ")
return new GitCommandResult(!output.contains("fatal"), 0, Collections.<String>emptyList(),
Arrays.asList(StringUtil.splitByLines(output)))
}
@NotNull
@Override
GitCommandResult checkAttr(@NotNull GitRepository repository, @NotNull Collection<String> attributes, @NotNull Collection<VirtualFile> files) {
String root = repository.getRoot().getPath()
cd root
String output = git "check-attr " + attributes.join(" ") + " -- " +
files.collect({it -> FileUtil.getRelativePath(root, it.path, (char)'/')}).join(" ")
return new GitCommandResult(!output.contains("fatal"), 0, Collections.<String>emptyList(),
Arrays.asList(StringUtil.splitByLines(output)))
}
@NotNull
@Override
public GitCommandResult merge(@NotNull GitRepository repository,
@NotNull String branchToMerge,
@Nullable List<String> additionalParams,
@NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult checkout(@NotNull GitRepository repository,
@NotNull String reference,
@Nullable String newBranch,
boolean force,
@NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult checkoutNewBranch(@NotNull GitRepository repository,
@NotNull String branchName,
@Nullable GitLineHandlerListener listener) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult createNewTag(@NotNull GitRepository repository,
@NotNull String tagName,
@Nullable GitLineHandlerListener listener,
@NotNull String reference) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult branchDelete(@NotNull GitRepository repository,
@NotNull String branchName,
boolean force,
@NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult branchContains(@NotNull GitRepository repository, @NotNull String commit) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult branchCreate(@NotNull GitRepository repository, @NotNull String branchName) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult resetHard(@NotNull GitRepository repository, @NotNull String revision) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult resetMerge(@NotNull GitRepository repository, @Nullable String revision) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult tip(@NotNull GitRepository repository, @NotNull String branchName) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult push(@NotNull GitRepository repository,
@NotNull String remote,
@NotNull String spec,
@NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult push(@NotNull GitRepository repository,
@NotNull GitPushSpec pushSpec,
@NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult show(@NotNull GitRepository repository, @NotNull String... params) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult cherryPick(@NotNull GitRepository repository,
@NotNull String hash,
boolean autoCommit,
@NotNull GitLineHandlerListener... listeners) {
throw new UnsupportedOperationException();
}
@NotNull
@Override
public GitCommandResult getUnmergedFiles(@NotNull GitRepository repository) {
throw new UnsupportedOperationException();
}
}
@@ -18,6 +18,7 @@ package git4idea.test;
import com.intellij.ide.plugins.IdeaPluginDescriptor;
import com.intellij.mock.MockLocalFileSystem;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
@@ -145,6 +146,12 @@ public class GitTestPlatformFacade implements PlatformFacade {
return null;
}
@NotNull
@Override
public FileDocumentManager getFileDocumentManager() {
return new GitMockFileDocumentManager();
}
@NotNull
@Override
public AbstractVcs getVcs(@NotNull Project project) {
@@ -30,7 +30,7 @@ import java.util.List;
/**
* @author Kirill Likhodedov
*/
class GitTestRepositoryManager implements GitRepositoryManager {
public class GitTestRepositoryManager implements GitRepositoryManager {
private final Collection<GitRepository> myRepositories = new ArrayList<GitRepository>();
@@ -40,7 +40,12 @@ class GitTestRepositoryManager implements GitRepositoryManager {
@Override
public GitRepository getRepositoryForRoot(@Nullable VirtualFile root) {
return myRepositories.iterator().next();
for (GitRepository repository : myRepositories) {
if (repository.getRoot().equals(root)) {
return repository;
}
}
return null;
}
@Override
@@ -20,6 +20,7 @@ import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
@@ -35,11 +36,14 @@ import org.picocontainer.MutablePicoContainer;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.fail;
@@ -225,6 +229,21 @@ public class GitTestUtil {
return new File(pluginRoot, "testData");
}
// for testing purposes we test the behavior of windows and unix gits on both platforms
// this method sets SystemInfo.isWindows to whatever we want
public static void setWindows(boolean windows) throws NoSuchFieldException, IllegalAccessException {
Field win = SystemInfo.class.getDeclaredField("isWindows");
win.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(win, win.getModifiers() & ~Modifier.FINAL);
win.set(null, windows);
assertEquals(SystemInfo.isWindows, windows);
}
public interface EqualityChecker<T, E> {
boolean areEqual(T actual, E expected);
}
@@ -86,6 +86,12 @@ class MockGit implements Git {
throw new UnsupportedOperationException()
}
@NotNull
@Override
GitCommandResult config(@NotNull GitRepository repository, String... params) {
throw new UnsupportedOperationException()
}
@NotNull
@Override
GitCommandResult merge(@NotNull GitRepository repository, @NotNull String branchToMerge, @Nullable List<String> additionalParams,
@@ -187,6 +193,12 @@ class MockGit implements Git {
return FAKE_SUCCESS_RESULT
}
@NotNull
@Override
GitCommandResult checkAttr(@NotNull GitRepository repository, @NotNull Collection<String> attributes, @NotNull Collection<VirtualFile> files) {
}
private void produceOutput(String output, GitLineHandlerListener... listeners) {
for (String line : output.split("\n")) { // for simplicity all output goes to OUTPUT, no ERROR
listeners.each { it.onLineAvailable(line, ProcessOutputTypes.STDOUT) }
@@ -15,12 +15,11 @@
*/
package git4idea.tests;
import com.intellij.openapi.util.SystemInfo;
import git4idea.config.GitVersion;
import git4idea.test.GitTestUtil;
import org.testng.annotations.Test;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import static git4idea.config.GitVersion.Type;
import static org.testng.Assert.assertEquals;
@@ -53,13 +52,13 @@ public class GitVersionTest {
*/
@Test
public void testParse() throws Exception {
setWindows(false);
GitTestUtil.setWindows(false);
for (TestGitVersion test : commonTests) {
GitVersion version = GitVersion.parse(test.output);
assertEqualVersions(version, test, Type.UNIX);
}
setWindows(true);
GitTestUtil.setWindows(true);
for (TestGitVersion test : commonTests) {
GitVersion version = GitVersion.parse(test.output);
assertEqualVersions(version, test, Type.CYGWIN);
@@ -73,7 +72,7 @@ public class GitVersionTest {
@Test
public void testEqualsAndCompare() throws Exception {
setWindows(true);
GitTestUtil.setWindows(true);
GitVersion v1 = GitVersion.parse("git version 1.6.3");
GitVersion v2 = GitVersion.parse("git version 1.7.3");
GitVersion v3 = GitVersion.parse("git version 1.7.3");
@@ -119,21 +118,6 @@ public class GitVersionTest {
assertEquals(type, expectedType);
}
// for testing purposes we test the behavior of windows and unix gits on both platforms
// this method sets SystemInfo.isWindows to whatever we want
private static void setWindows(boolean windows) throws NoSuchFieldException, IllegalAccessException {
Field win = SystemInfo.class.getDeclaredField("isWindows");
win.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(win, win.getModifiers() & ~Modifier.FINAL);
win.set(null, windows);
assertEquals(SystemInfo.isWindows, windows);
}
private static class TestGitVersion {
private final String output;
private final int major;