blob: 3d817607b8a9a1dec91530e45be0898b6778c54f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Obeo and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.emf.compare.ide.ui.tests.git.framework.internal.statements;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.RepositoryCache;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.egit.core.op.DisconnectProviderOperation;
import org.eclipse.egit.core.project.RepositoryFinder;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.ui.dialogs.IOverwriteQuery;
import org.eclipse.ui.wizards.datatransfer.FileSystemStructureProvider;
import org.eclipse.ui.wizards.datatransfer.ImportOperation;
/**
* This class contains utility methods to perform git tests. Those methods are internal and are not intended
* to be used by clients.
*
* @author <a href="mailto:mathieu.cartaud@obeo.fr">Mathieu Cartaud</a>
*/
@SuppressWarnings({"restriction" })
public class InternalGitTestSupport {
/** The prefix of Git branches. */
private static final String GIT_BRANCH_PREFIX = "refs/heads/"; //$NON-NLS-1$
/** Name of the Eclipse metadata folder. */
private static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
/** Name of the git metadata folder. */
private static final String GIT_FOLDER = ".git"; //$NON-NLS-1$
/** Size of the buffer to read/write data. */
private static final int BUFFER_SIZE = 4096;
/** The JGit repository. */
protected Repository repository;
/** The list of projects in the repository. */
protected IProject[] projects;
/** The list of JGit disposers. */
protected ArrayList<Runnable> disposers;
/** Query used to specify if the projects import must override existing projects. */
private IOverwriteQuery overwriteQuery = new IOverwriteQuery() {
public String queryOverwrite(String file) {
return ALL;
}
};
/**
* Adapt the given <code>branch</code> name to be in a correct format for Git or return it directly if it
* is already the case.
*
* @param branch
* The branch name we want to normalize
* @return the branch in Git format
*/
protected static String normalizeBranch(String branch) {
if (branch.startsWith(GIT_BRANCH_PREFIX)) {
return branch;
} else {
return GIT_BRANCH_PREFIX + branch;
}
}
/**
* Perform all loading actions (unzip the archive, connect the git repository to JGit, import the
* contained projects in the workspace and connect them through JGit).
*
* @param clazz
* The test class
* @param path
* The path of the archive containing the Git repository
* @throws Exception
* If something goes wrong during unzip or import steps
*/
protected void createRepositoryFromPath(Class<?> clazz, String path) throws Exception {
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
// Delete all projects that can remain in the workspace : prevent errors dues to wrong cleanup of
// other tests
root.delete(true, new NullProgressMonitor());
IPath location = root.getLocation();
List<String> extractedPaths = extractArchive(clazz, path, root);
for (String extractedPath : extractedPaths) {
File extractedFile = new File(extractedPath);
if (!extractedFile.getPath().contains(File.separatorChar + METADATA_FOLDER + File.separatorChar)
&& !extractedFile.getPath()
.contains(File.separatorChar + GIT_FOLDER + File.separatorChar)) {
if (".project".equals(extractedFile.getName())) { //$NON-NLS-1$
importProject(extractedFile);
}
}
}
connectRepository(new File(location.toString()));
List<IProject> existingProjects = Arrays.asList(root.getProjects());
List<IProject> connectedProjects = new ArrayList<>();
for (IProject project : existingProjects) {
RepositoryFinder finder = new RepositoryFinder(project);
finder.setFindInChildren(false);
Collection<RepositoryMapping> repos = finder.find(new NullProgressMonitor());
if (!repos.isEmpty()) {
connect(project);
connectedProjects.add(project);
}
}
projects = connectedProjects.toArray(new IProject[connectedProjects.size()]);
}
/**
* Connect a project to the Git repository.
*
* @param project
* The project to connect
* @throws CoreException
* Thrown if the project cannot be connected to the workspace
* @throws InterruptedException
* Thrown if the operation is interrupted
*/
private void connect(IProject project) throws CoreException, InterruptedException {
ConnectProviderOperation op = new ConnectProviderOperation(project, repository.getDirectory());
op.execute(null);
}
/**
* Connect the Git repository to JGit.
*
* @param file
* The path to the root of the repository
* @throws IOException
* If their is a problem during the connection to JGit
*/
private void connectRepository(File file) throws IOException {
File gitDir = findGitDir(file);
this.repository = RepositoryCache.INSTANCE.lookupRepository(gitDir);
this.disposers = new ArrayList<Runnable>();
}
/**
* Find the ".git" folder in the repository.
*
* @param file
* The path of the root of the unziped files
* @return The path to the .git folder
*/
private File findGitDir(File file) {
File[] listFiles = file.listFiles();
if (listFiles != null) {
for (File child : listFiles) {
if (child.isDirectory() && child.getName().equals(".git")) { //$NON-NLS-1$
return child;
} else if (child.isDirectory()) {
File findGitDir = findGitDir(child);
if (findGitDir != null) {
return findGitDir;
}
}
}
}
return null;
}
/**
* Import the project located in the given path into the test workspace.
*
* @param file
* The path to the .project file
* @throws InvocationTargetException
* Thrown if an error happen during the import of the project
* @throws InterruptedException
* Thrown if the import operation is interrupted
* @throws CoreException
* Thrown if the project cannot be created in the workspace
*/
private void importProject(File file)
throws InvocationTargetException, InterruptedException, CoreException {
IProjectDescription description = ResourcesPlugin.getWorkspace()
.loadProjectDescription(new Path(file.getAbsolutePath()));
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(description.getName());
project.create(description, new NullProgressMonitor());
project.open(new NullProgressMonitor());
ImportOperation importOperation = new ImportOperation(project.getFullPath(), file.getParentFile(),
FileSystemStructureProvider.INSTANCE, overwriteQuery);
importOperation.setCreateContainerStructure(false);
importOperation.run(new NullProgressMonitor());
}
/**
* Extract the zip file into the given workspace.
*
* @param clazz
* The test class
* @param path
* The path to the archive (relative to the test class)
* @param root
* The root of the test workspace
* @return The list of files extracted from this archive. Does not include folders.
* @throws IOException
* Thrown if the zip extraction goes wrong
*/
private List<String> extractArchive(Class<?> clazz, String path, IWorkspaceRoot root) throws IOException {
List<String> extractedPaths = new ArrayList<>();
try (InputStream resourceAsStream = clazz.getResourceAsStream(path);
ZipInputStream zipIn = new ZipInputStream(resourceAsStream);) {
ZipEntry entry = null;
while ((entry = zipIn.getNextEntry()) != null) {
String filePath = root.getLocation() + File.separator + entry.getName();
if (!entry.isDirectory()) {
extractFile(zipIn, filePath);
extractedPaths.add(filePath);
} else {
File dir = new File(filePath);
dir.mkdir();
}
zipIn.closeEntry();
}
}
return extractedPaths;
}
/**
* Extract the given input stream to the given location.
*
* @param zipIn
* The zip input stream
* @param filePath
* The destination path for the extraction
* @throws IOException
* Thrown if something happen during the extraction
*/
private void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
File file = new File(filePath);
file.getParentFile().mkdirs();
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));) {
byte[] bytesIn = new byte[BUFFER_SIZE];
int read = 0;
while ((read = zipIn.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
}
}
/**
* Clean all possibly remaining elements to start the test in a clean state.
*
* @throws CoreException
* Thrown if a project cannot be deleted
* @throws IOException
* Thrown if a file cannot be deleted
*/
protected void setup() throws CoreException, IOException {
final IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
IProject[] unknownProjects = workspaceRoot.getProjects();
if (unknownProjects != null && unknownProjects.length > 0) {
for (IProject iProject : unknownProjects) {
iProject.delete(true, new NullProgressMonitor());
}
}
RepositoryCache.INSTANCE.clear();
File file = new File(workspaceRoot.getLocation().toOSString());
File[] listFiles = file.listFiles();
if (listFiles != null) {
for (File child : listFiles) {
if (!child.getName().equals(METADATA_FOLDER)) {
try (Stream<java.nio.file.Path> walk = Files.walk(child.toPath())) {
walk.sorted(Comparator.reverseOrder()).map(java.nio.file.Path::toFile)
.forEach(File::delete);
}
}
}
}
}
/**
* Clear workspace and repository for next tests.
*
* @throws CoreException
* Thrown if a project cannot be deleted
* @throws IOException
* Thrown if a file cannot be deleted
*/
protected void tearDown() throws CoreException, IOException {
if (disposers != null) {
for (Runnable disposer : disposers) {
disposer.run();
}
disposers.clear();
}
RepositoryCache.INSTANCE.clear();
if (projects != null) {
List<IProject> disconnectMe = new ArrayList<>();
for (IProject iProject : projects) {
disconnectMe.add(iProject);
}
new DisconnectProviderOperation(disconnectMe).execute(null);
// Delete sub-projects first
List<IProject> allProjects = new ArrayList<>(
Arrays.asList(ResourcesPlugin.getWorkspace().getRoot().getProjects()));
allProjects.sort((p1, p2) -> p2.getLocation().toString().compareTo(p1.getLocation().toString()));
for (IProject iProject : allProjects) {
if (iProject.isAccessible()) {
iProject.delete(true, new NullProgressMonitor());
}
}
ResourcesPlugin.getWorkspace().getRoot().refreshLocal(IResource.DEPTH_INFINITE,
new NullProgressMonitor());
}
File file = new File(ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString());
File[] listFiles = file.listFiles();
if (listFiles != null) {
for (File child : listFiles) {
if (!child.getName().equals(METADATA_FOLDER)) {
try (Stream<java.nio.file.Path> walk = Files.walk(child.toPath())) {
walk.sorted(Comparator.reverseOrder()).map(java.nio.file.Path::toFile)
.forEach(File::delete);
}
}
}
}
}
}