/*******************************************************************************
 * Copyright (C) 2011, Robin Rosenberg
 *
 * 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
 */
package org.eclipse.egit.core;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.egit.core.op.AddToIndexOperation;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.test.TestProject;
import org.eclipse.egit.core.test.TestRepository;
import org.eclipse.egit.core.test.TestUtils;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.junit.MockSystemReader;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * All sorts of interesting cases
 */
public class GitMoveDeleteHookTest {

	TestUtils testUtils = new TestUtils();

	TestRepository testRepository;

	Repository repository;

	Set<File> testDirs = new HashSet<File>();

	File workspaceSupplement;

	File workspace;

	@Before
	public void setUp() throws Exception {
		Activator.getDefault().getRepositoryCache().clear();
		MockSystemReader mockSystemReader = new MockSystemReader();
		SystemReader.setInstance(mockSystemReader);
		mockSystemReader.setProperty(Constants.GIT_CEILING_DIRECTORIES_KEY,
				ResourcesPlugin.getWorkspace().getRoot().getLocation().toFile()
						.getParentFile().getAbsoluteFile().toString());
		workspaceSupplement = testUtils.createTempDir("wssupplement");
		testDirs.add(testUtils.getBaseTempDir());
		workspace = ResourcesPlugin.getWorkspace().getRoot().getLocation().toFile().getAbsoluteFile();
	}

	@After
	public void tearDown() throws IOException, CoreException {
		ResourcesPlugin.getWorkspace().getRoot().delete(IResource.FORCE, null);
		if (testRepository != null)
			testRepository.dispose();
		repository = null;
		for (File d : testDirs)
			if (d.exists())
				FileUtils.delete(d, FileUtils.RECURSIVE | FileUtils.RETRY);
		SystemReader.setInstance(null);
	}

	private TestProject initRepoInsideProjectInsideWorkspace() throws IOException,
			CoreException {
		TestProject project = new TestProject(true, "Project-1", true, workspaceSupplement);
		File gitDir = new File(project.getProject().getLocationURI().getPath(),
				Constants.DOT_GIT);
		testDirs.add(gitDir);
		testRepository = new TestRepository(gitDir);
		repository = testRepository.getRepository();
		testRepository.connect(project.getProject());
		registerWorkspaceRelativeTestDir("Project-1");
		return project;
	}

	private TestProject initRepoInsideProjectOutsideWorkspace()
			throws IOException, CoreException {
		TestProject project = new TestProject(true, "Project-1", false,
				workspaceSupplement);
		File gitDir = new File(project.getProject().getLocationURI().getPath(),
				Constants.DOT_GIT);
		testDirs.add(gitDir);
		testRepository = new TestRepository(gitDir);
		repository = testRepository.getRepository();
		testRepository.connect(project.getProject());
		return project;
	}

	private TestProject initRepoAboveProjectInsideWs(String srcParent, String d)
	throws IOException, CoreException {
		return initRepoAboveProject(srcParent, d, true);
	}

	private TestProject initRepoAboveProject(String srcParent, String d, boolean insidews)
			throws IOException, CoreException {
		registerWorkspaceRelativeTestDir(srcParent);
		TestProject project = new TestProject(true, srcParent + "Project-1", insidews, workspaceSupplement);
		File gd = new File(insidews?workspace:workspaceSupplement, d);

		File gitDir = new File(gd, Constants.DOT_GIT);
		testDirs.add(gitDir);
		testRepository = new TestRepository(gitDir);
		repository = testRepository.getRepository();
		testRepository.connect(project.getProject());
		return project;
	}

	@Test
	public void testDeleteFile() throws Exception {
		TestProject project = initRepoInsideProjectInsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		testUtils.addFileToProject(project.getProject(), "file2.txt",
				"some  more text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt"),
						project.getProject().getFile("file2.txt") });
		addToIndexOperation.execute(null);

		// Validate pre-conditions
		DirCache dirCache = DirCache.read(repository.getIndexFile(),
				FS.DETECTED);
		assertEquals(2, dirCache.getEntryCount());
		assertNotNull(dirCache.getEntry("file.txt"));
		assertNotNull(dirCache.getEntry("file2.txt"));
		// Modify the content before the move
		testUtils.changeContentOfFile(project.getProject(), project
				.getProject().getFile("file.txt"), "other text");
		project.getProject().getFile("file.txt").delete(true, null);

		// Check index for the deleted file
		dirCache.read();
		assertEquals(1, dirCache.getEntryCount());
		assertNull(dirCache.getEntry("file.txt"));
		assertNotNull(dirCache.getEntry("file2.txt"));
		// Actual file is deleted
		assertFalse(project.getProject().getFile("file.txt").exists());
		// But a non-affected file remains
		assertTrue(project.getProject().getFile("file2.txt").exists());
	}

	@Test
	public void testDeleteFolder() throws Exception {
		TestProject project = initRepoInsideProjectInsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "folder/file.txt",
				"some text");
		testUtils.addFileToProject(project.getProject(), "folder2/file.txt",
				"some other text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] {
						project.getProject().getFile("folder/file.txt"),
						project.getProject().getFile("folder2/file.txt") });
		addToIndexOperation.execute(null);

		DirCache dirCache = DirCache.read(repository.getIndexFile(),
				FS.DETECTED);
		assertNotNull(dirCache.getEntry("folder/file.txt"));
		assertNotNull(dirCache.getEntry("folder2/file.txt"));
		// Modify the content before the move
		testUtils.changeContentOfFile(project.getProject(), project
				.getProject().getFile("folder/file.txt"), "other text");
		project.getProject().getFolder("folder").delete(true, null);

		dirCache.read();
		// Unlike delete file, dircache is untouched... pretty illogical
		// TODO: Change the behavior of the hook.
		assertNotNull(dirCache.getEntry("folder/file.txt"));
		// Not moved file still there
		assertNotNull(dirCache.getEntry("folder2/file.txt"));
	}

	@Test
	public void testDeleteProject() throws Exception {
		TestProject project = initRepoAboveProjectInsideWs("P/", "");
		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt") });
		addToIndexOperation.execute(null);

		RepositoryMapping mapping = RepositoryMapping.getMapping(project
				.getProject());
		IPath gitDirAbsolutePath = mapping.getGitDirAbsolutePath();
		Repository db = FileRepositoryBuilder.create(gitDirAbsolutePath
				.toFile());
		DirCache index = DirCache.read(db.getIndexFile(), db.getFS());
		assertNotNull(index.getEntry("P/Project-1/file.txt"));
		db.close();
		db = null;
		project.getProject().delete(true, null);
		assertNull(RepositoryMapping.getMapping(project.getProject()));
		// Check that the repo is still there. Being a bit paranoid we look for
		// a file
		assertTrue(gitDirAbsolutePath.toString(),
				gitDirAbsolutePath.append("HEAD").toFile().exists());

		db = FileRepositoryBuilder.create(gitDirAbsolutePath.toFile());
		index = DirCache.read(db.getIndexFile(), db.getFS());
		// FIXME: Shouldn't we unstage deleted projects?
		assertNotNull(index.getEntry("P/Project-1/file.txt"));
		db.close();
	}

	@Test
	public void testMoveFile() throws Exception {
		TestProject project = initRepoInsideProjectInsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		testUtils.addFileToProject(project.getProject(), "file2.txt",
				"some  more text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt"),
						project.getProject().getFile("file2.txt") });
		addToIndexOperation.execute(null);

		// Validate pre-conditions
		DirCache dirCache = DirCache.read(repository.getIndexFile(),
				FS.DETECTED);
		assertNotNull(dirCache.getEntry("file.txt"));
		assertNotNull(dirCache.getEntry("file2.txt"));
		assertNull(dirCache.getEntry("data.txt"));
		assertFalse(project.getProject().getFile("data.txt").exists());
		ObjectId oldContentId = dirCache.getEntry("file.txt").getObjectId();
		// Modify the content before the move
		testUtils.changeContentOfFile(project.getProject(), project
				.getProject().getFile("file.txt"), "other text");
		project.getProject()
				.getFile("file.txt")
				.move(project.getProject().getFile("data.txt").getFullPath(),
						false, null);

		dirCache.read();
		assertTrue(project.getProject().getFile("data.txt").exists());
		assertNotNull(dirCache.getEntry("data.txt"));
		// Same content in index as before the move
		assertEquals(oldContentId, dirCache.getEntry("data.txt").getObjectId());

		// Not moved file still in its old place
		assertNotNull(dirCache.getEntry("file2.txt"));
	}

	/**
	 * Rename "folder" to "dir".
	 *
	 * @throws Exception
	 */
	@Test
	public void testMoveFolder() throws Exception {
		TestProject project = initRepoInsideProjectInsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "folder/file.txt",
				"some text");
		testUtils.addFileToProject(project.getProject(), "folder2/file.txt",
				"some other text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] {
						project.getProject().getFile("folder/file.txt"),
						project.getProject().getFile("folder2/file.txt") });
		addToIndexOperation.execute(null);

		DirCache dirCache = DirCache.read(repository.getIndexFile(),
				FS.DETECTED);
		assertNotNull(dirCache.getEntry("folder/file.txt"));
		assertNotNull(dirCache.getEntry("folder2/file.txt"));
		assertNull(dirCache.getEntry("dir/file.txt"));
		assertFalse(project.getProject().getFile("dir/file.txt").exists());
		ObjectId oldContentId = dirCache.getEntry("folder/file.txt")
				.getObjectId();
		// Modify the content before the move
		testUtils.changeContentOfFile(project.getProject(), project
				.getProject().getFile("folder/file.txt"), "other text");
		project.getProject()
				.getFolder("folder")
				.move(project.getProject().getFolder("dir").getFullPath(),
						false, null);

		dirCache.read();
		assertTrue(project.getProject().getFile("dir/file.txt").exists());
		assertNull(dirCache.getEntry("folder/file.txt"));
		assertNotNull(dirCache.getEntry("dir/file.txt"));
		// Same content in index as before the move
		assertEquals(oldContentId, dirCache.getEntry("dir/file.txt")
				.getObjectId());
		// Not moved file still there
		assertNotNull(dirCache.getEntry("folder2/file.txt"));
	}

	/**
	 * Rename and move a project in the workspace containing a Git repository.
	 * <p>
	 * The repository will be moved with the project.
	 * Note that there is no way to rename a project in the workspace without
	 * moving it. See https://bugs.eclipse.org/358828 for a discussion.
	 *
	 * @throws Exception
	 */
	@Test
	public void testMoveAndRenameProjectContainingGitRepo() throws Exception {
		ResourcesPlugin.getWorkspace().getRoot().getProject("Project-1").delete(true, null);
		ResourcesPlugin.getWorkspace().getRoot().getProject("P2").delete(true, null);

		TestProject project = initRepoInsideProjectInsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt") });
		addToIndexOperation.execute(null);
		IProjectDescription description = project.getProject().getDescription();
		description.setName("P2");
		registerWorkspaceRelativeTestDir("P2");
		project.getProject().move(description,
				IResource.FORCE | IResource.SHALLOW, null);
		IProject project2 = ResourcesPlugin.getWorkspace().getRoot()
				.getProject("P2");
		assertNotNull(RepositoryMapping.getMapping(project2.getProject()));
		Repository movedRepo = RepositoryMapping.getMapping(project2)
				.getRepository();
		assertEquals("P2",
				movedRepo.getDirectory().getParentFile()
						.getName());
		DirCache dc = movedRepo.readDirCache();
		assertEquals(1, dc.getEntryCount());
		assertEquals("file.txt", dc.getEntry(0).getPathString());

		assertFalse(ResourcesPlugin.getWorkspace().getRoot().getProject("Project-1").exists());
	}

	/**
	 * Rename a project outside the workspace containing a Git repository.
	 * <p>
	 * Note the similarity of the code with {@link #testMoveAndRenameProjectContainingGitRepo()}
	 *
	 * @throws Exception
	 */
	@Test
	public void testRenameProjectOutsideWorkspaceContainingGitRepo() throws Exception {
		ResourcesPlugin.getWorkspace().getRoot().getProject("Project-1").delete(true, null);
		ResourcesPlugin.getWorkspace().getRoot().getProject("P2").delete(true, null);
		TestProject project = initRepoInsideProjectOutsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt") });
		addToIndexOperation.execute(null);
		IProjectDescription description = project.getProject().getDescription();
		description.setName("P2");
		project.getProject().move(description,
				IResource.FORCE | IResource.SHALLOW, null);
		IProject project2 = ResourcesPlugin.getWorkspace().getRoot()
				.getProject("P2");
		assertNotNull(RepositoryMapping.getMapping(project2.getProject()));
		Repository movedRepo = RepositoryMapping.getMapping(project2)
				.getRepository();
		assertEquals("Project-1",
				movedRepo.getDirectory().getParentFile()
						.getName());
		DirCache dc = movedRepo.readDirCache();
		assertEquals(1, dc.getEntryCount());
		assertEquals("file.txt", dc.getEntry(0).getPathString());

		assertFalse(ResourcesPlugin.getWorkspace().getRoot().getProject("Project-1").exists());
	}

	/**
	 * Move a project outside the workspace containing a Git repository, but do not rename it.
	 * <p>
	 * Note the similarity of the code with {@link #testMoveAndRenameProjectContainingGitRepo()}
	 *
	 * @throws Exception
	 */
	@Test
	public void testMoveButDoNotRenameProjectOutsideWorkspaceContainingGitRepo() throws Exception {
		ResourcesPlugin.getWorkspace().getRoot().getProject("Project-1").delete(true, null);
		ResourcesPlugin.getWorkspace().getRoot().getProject("P2").delete(true, null);
		TestProject project = initRepoInsideProjectOutsideWorkspace();
		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt") });
		addToIndexOperation.execute(null);
		IProjectDescription description = project.getProject().getDescription();
		description.setLocationURI(URIUtil.toURI(new Path(new File(project.getWorkspaceSupplement(), "P2").getAbsolutePath())));
		project.getProject().move(description,
				IResource.FORCE | IResource.SHALLOW, null);
		IProject project2 = ResourcesPlugin.getWorkspace().getRoot()
				.getProject("Project-1"); // same name
		assertNotNull(RepositoryMapping.getMapping(project2.getProject()));
		Repository movedRepo = RepositoryMapping.getMapping(project2)
				.getRepository();
		assertEquals("P2",
				movedRepo.getDirectory().getParentFile()
						.getName());
		DirCache dc = movedRepo.readDirCache();
		assertEquals(1, dc.getEntryCount());
		assertEquals("file.txt", dc.getEntry(0).getPathString());

		assertFalse(ResourcesPlugin.getWorkspace().getRoot().getProject("P2").exists());
	}


	@Test
	public void testMoveProjectWithinGitRepoMoveAtSameTopLevel()
			throws Exception {
		dotestMoveProjectWithinRepoWithinWorkspace("", "Project-1", "", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitRepoMoveFromTopOneLevelDown()
			throws Exception {
		dotestMoveProjectWithinRepoWithinWorkspace("", "Project-1", "X/", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitRepoMoveFromOneLevelDownToTop()
			throws Exception {
		dotestMoveProjectWithinRepoWithinWorkspace("P/", "Project-1", "", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitRepoMoveFromOneLevelDownToSameDepth()
			throws Exception {
		dotestMoveProjectWithinRepoWithinWorkspace("P/", "Project-1", "X/", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitRepoMoveFromOneLevelDownOutsideTheRepo()
			throws Exception {
		dotestMoveProjectWithinRepoWithinWorkspace("P/", "Project-1", "P/", "P2", "P/");
	}

	@Test
	public void testMoveProjectWithinGitOutsideWorkspaceRepoMoveAtSameTopLevel()
			throws Exception {
		dotestMoveProjectWithinRepoOutsideWorkspace("", "Project-1", "", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitOutsideWorkspaceRepoMoveFromTopOneLevelDown()
			throws Exception {
		dotestMoveProjectWithinRepoOutsideWorkspace("", "Project-1", "X/", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitOutsideWorkspaceRepoMoveFromOneLevelDownToTop()
			throws Exception {
		dotestMoveProjectWithinRepoOutsideWorkspace("P/", "Project-1", "", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitOutsideWorkspaceRepoMoveFromOneLevelDownToSameDepth()
			throws Exception {
		dotestMoveProjectWithinRepoOutsideWorkspace("P/", "Project-1", "X/", "P2", "");
	}

	@Test
	public void testMoveProjectWithinGitOutsideWorkspaceRepoMoveFromOneLevelDownOutsideTheRepo()
			throws Exception {
		dotestMoveProjectWithinRepoOutsideWorkspace("P/", "Project-1", "P/", "P2", "P/");
	}


	@Test
	public void testMoveProjectWithinGitRepoMoveFromLevelZeroDownOne()
			throws Exception {
		// In this case we'd expect the project to move, but not the repository
		// TODO: Eclipse cannot do this even without the Git plugin either,
		// TODO: See Bug 307140)
		try {
			dotestMoveProjectWithinRepoWithinWorkspace("P/", "Project-1",
					"P/Project-1/", "P2", "P/Project-1/");
			if (!"true".equals(System.getProperty("egit.assume_307140_fixed")))
				fail("ResourceException expected, core functionality dangerously broken and therefore forbidden");
		} catch (CoreException e) {
			if ("true".equals(System.getProperty("egit.assume_307140_fixed")))
				throw e;
		}
	}

	@Test
	public void testMoveFileWithConflictsShouldBeCanceled() throws Exception {
		TestProject project = initRepoInsideProjectInsideWorkspace();
		String filePath = "file.txt";
		IFile file = testUtils.addFileToProject(project.getProject(), filePath, "some text");

		Repository repo = testRepository.getRepository();
		DirCache index = repo.lockDirCache();
		DirCacheBuilder builder = index.builder();
		addUnmergedEntry(filePath, builder);
		builder.commit();

		try {
			file.move(new Path("destination.txt"), false, null);
			fail("Expected move of file with conflicts to fail.");
		} catch (CoreException e) {
			IStatus status = e.getStatus();
			assertNotNull(status);
			assertEquals(IStatus.WARNING, status.getSeverity());
		}

		assertTrue("File should still exist at old location", file.exists());
		DirCache indexAfter = repo.readDirCache();
		DirCacheEntry entry = indexAfter.getEntry(filePath);
		assertEquals("Expected entry to still be in non-zero (conflict) stage",
				DirCacheEntry.STAGE_1, entry.getStage());
	}

	@Test
	public void testMoveFolderWithFileWithConflictsShouldBeCanceled() throws Exception {
		TestProject project = initRepoInsideProjectInsideWorkspace();
		String filePath = "folder/file.txt";
		IFile file = testUtils.addFileToProject(project.getProject(), filePath, "some text");

		Repository repo = testRepository.getRepository();
		DirCache index = repo.lockDirCache();
		DirCacheBuilder builder = index.builder();
		addUnmergedEntry(filePath, builder);
		builder.commit();

		try {
			project.getProject()
					.getFolder("folder")
					.move(project.getProject().getFolder("newfolder")
							.getFullPath(), false, null);
			fail("Expected move of folder with file with conflicts to fail.");
		} catch (CoreException e) {
			IStatus status = e.getStatus();
			assertNotNull(status);
			assertEquals(IStatus.WARNING, status.getSeverity());
		}

		assertTrue("File should still exist at old location", file.exists());
		DirCache indexAfter = repo.readDirCache();
		DirCacheEntry entry = indexAfter.getEntry(filePath);
		assertEquals("Expected entry to still be in non-zero (conflict) stage",
				DirCacheEntry.STAGE_1, entry.getStage());
	}

	private static void addUnmergedEntry(String filePath, DirCacheBuilder builder) {
		DirCacheEntry stage1 = new DirCacheEntry(filePath, DirCacheEntry.STAGE_1);
		DirCacheEntry stage2 = new DirCacheEntry(filePath, DirCacheEntry.STAGE_2);
		DirCacheEntry stage3 = new DirCacheEntry(filePath, DirCacheEntry.STAGE_3);
		stage1.setFileMode(FileMode.REGULAR_FILE);
		stage2.setFileMode(FileMode.REGULAR_FILE);
		stage3.setFileMode(FileMode.REGULAR_FILE);
		builder.add(stage1);
		builder.add(stage2);
		builder.add(stage3);
	}

	private void dotestMoveProjectWithinRepoWithinWorkspace(String srcParent,
			String srcProjectName, String dstParent, String dstProjecName,
			String gitDir) throws CoreException, IOException, Exception,
			CorruptObjectException {
		dotestMoveProjectWithinRepo(srcParent, srcProjectName, dstParent, dstProjecName, gitDir, true);
	}

	private void dotestMoveProjectWithinRepoOutsideWorkspace(String srcParent,
			String srcProjectName, String dstParent, String dstProjecName,
			String gitDir) throws CoreException, IOException, Exception,
			CorruptObjectException {
		dotestMoveProjectWithinRepo(srcParent, srcProjectName, dstParent, dstProjecName, gitDir, false);
	}

	private void dotestMoveProjectWithinRepo(String srcParent,
			String srcProjectName, String dstParent, String dstProjecName,
			String gitDir, boolean sourceInsideWs) throws IOException, CoreException {
		String gdRelativeSrcParent = srcParent + srcProjectName + "/";
		if (gdRelativeSrcParent.startsWith(gitDir))
			gdRelativeSrcParent = gdRelativeSrcParent
					.substring(gitDir.length());
		testDirs.add(new File(dstParent));
		String gdRelativeDstParent = dstParent + dstProjecName + "/";
		if (gdRelativeDstParent.startsWith(gitDir))
			gdRelativeDstParent = gdRelativeDstParent
					.substring(gitDir.length());

		registerWorkspaceRelativeTestDirProject(srcParent, srcProjectName);
		registerWorkspaceRelativeTestDirProject(dstParent, dstProjecName);

		// Old cruft may be laying around
		TestProject project = initRepoAboveProject(srcParent, gitDir, sourceInsideWs);
		IProject project0 = project.getProject().getWorkspace().getRoot()
				.getProject(dstProjecName);
		project0.delete(true, null);

		testUtils.addFileToProject(project.getProject(), "file.txt",
				"some text");
		AddToIndexOperation addToIndexOperation = new AddToIndexOperation(
				new IResource[] { project.getProject().getFile("file.txt") });
		addToIndexOperation.execute(null);

		// Check condition before move
		DirCache dirCache = DirCache.read(repository.getIndexFile(),
				FS.DETECTED);
		assertNotNull(dirCache.getEntry(gdRelativeSrcParent + "file.txt"));
		ObjectId oldContentId = dirCache.getEntry(
				gdRelativeSrcParent + "file.txt").getObjectId();

		// Modify the content before the move, we want to see the staged content
		// as it was before the move in the index
		testUtils.changeContentOfFile(project.getProject(), project
				.getProject().getFile("file.txt"), "other text");
		IProjectDescription description = project.getProject().getDescription();
		description.setName(dstProjecName);
		if (sourceInsideWs)
			if (dstParent.length() > 0)
				description.setLocationURI(URIUtil.toURI(project.getProject()
						.getWorkspace().getRoot().getLocation()
						.append(dstParent + dstProjecName)));
			else
				description.setLocationURI(null);
		else
			description.setLocationURI(URIUtil.toURI(new Path(workspaceSupplement + "/" + dstParent + "/" + dstProjecName)));
		project.getProject().move(description,
				IResource.FORCE | IResource.SHALLOW, null);
		IProject project2 = project.getProject().getWorkspace().getRoot()
				.getProject(dstProjecName);
		assertTrue(project2.exists());
		assertNotNull(RepositoryMapping.getMapping(project2));

		// Check that our file exists on disk has a new location in the index
		dirCache.read();
		assertTrue(project2.getFile("file.txt").exists());
		assertNotNull(dirCache.getEntry(gdRelativeDstParent + "file.txt"));

		// Same content in index as before the move, i.e. not same as on disk
		assertEquals(oldContentId,
				dirCache.getEntry(gdRelativeDstParent + "file.txt")
						.getObjectId());
	}


	private void registerWorkspaceRelativeTestDirProject(String parent, String projName) {
		if ((parent != null) && !parent.equals(""))
			registerWorkspaceRelativeTestDir(parent);
		else
			registerWorkspaceRelativeTestDir(projName);
	}

	private void registerWorkspaceRelativeTestDir(String relativeDir) {
		if ((relativeDir != null) && !relativeDir.equals("")) {
			File d = new File(workspace, relativeDir);
			testDirs.add(d);
		}
	}
}
