blob: 45fcfd0f175ba269de61eb10159ed2170da0eef9 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch>
*
* 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.ui.internal.submodules;
import static org.eclipse.egit.ui.JobFamilies.GENERATE_HISTORY;
import static org.eclipse.swtbot.eclipse.finder.waits.Conditions.waitForEditor;
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 java.io.File;
import java.util.Collections;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
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.QualifiedName;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.JobFamilies;
import org.eclipse.egit.core.internal.indexdiff.IndexDiffCacheEntry;
import org.eclipse.egit.core.project.GitProjectData;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.test.TestRepository;
import org.eclipse.egit.ui.common.LocalRepositoryTestCase;
import org.eclipse.egit.ui.internal.clone.ProjectRecord;
import org.eclipse.egit.ui.internal.clone.ProjectUtils;
import org.eclipse.egit.ui.internal.resources.IResourceState;
import org.eclipse.egit.ui.internal.resources.ResourceStateFactory;
import org.eclipse.egit.ui.test.ContextMenuHelper;
import org.eclipse.egit.ui.test.TestUtil;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.submodule.SubmoduleWalk;
import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView;
import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree;
import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IViewPart;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(SWTBotJunit4ClassRunner.class)
public class SubmoduleFolderTest extends LocalRepositoryTestCase {
private static final String SUBFOLDER = "sub";
private static final String CHILD = "child";
private static final String CHILDPROJECT = "ChildProject";
private Repository parentRepository;
private Repository childRepository;
private Repository subRepository;
private IProject parentProject;
private IProject childProject;
private IFolder childFolder;
private File parentRepositoryGitDir;
private File childRepositoryGitDir;
private File subRepositoryGitDir;
@Before
public void setUp() throws Exception {
parentRepositoryGitDir = createProjectAndCommitToRepository();
childRepositoryGitDir = createProjectAndCommitToRepository(CHILDREPO,
CHILDPROJECT);
Activator.getDefault().getRepositoryUtil()
.addConfiguredRepository(parentRepositoryGitDir);
parentRepository = lookupRepository(parentRepositoryGitDir);
childRepository = lookupRepository(childRepositoryGitDir);
parentProject = ResourcesPlugin.getWorkspace().getRoot()
.getProject(PROJ1);
IFolder folder = parentProject.getFolder(FOLDER);
IFolder subfolder = folder.getFolder(SUBFOLDER);
subfolder.create(false, true, null);
assertTrue(subfolder.exists());
IFile someFile = subfolder.getFile("dummy.txt");
touch(PROJ1, someFile.getProjectRelativePath().toOSString(),
"Dummy content");
addAndCommit(someFile, "Commit sub/dummy.txt");
childFolder = subfolder.getFolder(CHILD);
Git.wrap(parentRepository).submoduleAdd()
.setPath(childFolder.getFullPath().toPortableString())
.setURI(childRepository.getDirectory().toURI()
.toString())
.call();
TestRepository parentRepo = new TestRepository(parentRepository);
parentRepo.trackAllFiles(parentProject);
parentRepo.commit("Commit submodule");
assertTrue(SubmoduleWalk.containsGitModulesFile(parentRepository));
parentProject.refreshLocal(IResource.DEPTH_INFINITE, null);
assertTrue(childFolder.exists());
// Let's get rid of the child project imported directly from the child
// repository.
childProject = ResourcesPlugin.getWorkspace().getRoot()
.getProject(CHILDPROJECT);
childProject.delete(false, true, null);
// Re-import it from the parent repo's submodule!
IFile projectFile = childFolder.getFolder(CHILDPROJECT)
.getFile(IProjectDescription.DESCRIPTION_FILE_NAME);
assertTrue(projectFile.exists());
ProjectRecord pr = new ProjectRecord(
projectFile.getLocation().toFile());
ProjectUtils.createProjects(Collections.singleton(pr), null, null);
assertTrue(childProject.isOpen());
// Now we have a parent repo in a state as if we had recursively
// cloned some remote repo with a submodule and then imported all
// projects. Look up the submodule repository instance through the
// repository cache, so that we get the same instance that EGit
// uses.
subRepository = SubmoduleWalk.getSubmoduleRepository(
childFolder.getParent().getLocation().toFile(), CHILD);
assertNotNull(subRepository);
subRepositoryGitDir = subRepository.getDirectory();
subRepository.close();
subRepository = lookupRepository(subRepositoryGitDir);
assertNotNull(subRepository);
}
@After
public void removeConfiguredRepositories() {
if (parentRepositoryGitDir != null) {
Activator.getDefault().getRepositoryUtil()
.removeDir(parentRepositoryGitDir);
}
if (childRepositoryGitDir != null) {
Activator.getDefault().getRepositoryUtil()
.removeDir(childRepositoryGitDir);
}
childRepository = null;
parentRepository = null;
subRepository = null;
}
@Test
public void testChildProjectMapsToSubRepo() {
RepositoryMapping mapping = RepositoryMapping.getMapping(childProject);
assertNotNull("Child project should have a mapping", mapping);
assertEquals(subRepository, mapping.getRepository());
}
@Test
public void testChildFolderMapsToSubRepo() {
RepositoryMapping mapping = RepositoryMapping.getMapping(childFolder);
assertNotNull("Child folder should have a mapping", mapping);
assertEquals(subRepository, mapping.getRepository());
}
@Test
public void testParentFolderMapsToParentRepo() {
RepositoryMapping mapping = RepositoryMapping
.getMapping(childFolder.getParent());
assertNotNull("Child folder's parent should have a mapping", mapping);
assertEquals(parentRepository, mapping.getRepository());
}
/**
* Tests AddToIndex and RemoveFromIndex commands on a file from a submodule
* folder. Verifies the execution of the command by testing the state of the
* file in the index diff after it has been executed. Additionally verifies
* that decorations do get updated.
*
* @throws Exception
*/
@Test
public void testStageUnstageInSubRepo() throws Exception {
IFolder childProjectFolder = childFolder.getFolder(CHILDPROJECT);
IFolder folder = childProjectFolder.getFolder(FOLDER);
IFile file = folder.getFile(FILE1);
touch(PROJ1, file.getProjectRelativePath().toOSString(), "Modified");
TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);
SWTBotTree projectExplorerTree = TestUtil.getExplorerTree();
SWTBotTreeItem node = TestUtil.navigateTo(projectExplorerTree,
file.getFullPath().segments());
TestUtil.waitForDecorations();
assertTrue(node.getText().startsWith("> " + file.getName()));
node.select();
ContextMenuHelper.clickContextMenuSync(projectExplorerTree, "Team",
util.getPluginLocalizedValue("AddToIndexAction_label"));
TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);
IndexDiffCacheEntry cache = Activator.getDefault().getIndexDiffCache()
.getIndexDiffCacheEntry(subRepository);
TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);
IResourceState state = ResourceStateFactory.getInstance()
.get(cache.getIndexDiff(), file);
assertTrue("File should be staged", state.isStaged());
TestUtil.waitForDecorations();
assertFalse(node.getText().startsWith("> "));
ContextMenuHelper.clickContextMenuSync(projectExplorerTree, "Team",
util.getPluginLocalizedValue("RemoveFromIndexAction_label"));
TestUtil.joinJobs(JobFamilies.INDEX_DIFF_CACHE_UPDATE);
state = ResourceStateFactory.getInstance().get(cache.getIndexDiff(),
file);
assertFalse("File should not be staged", state.isStaged());
assertTrue("File should be dirty", state.isDirty());
TestUtil.waitForDecorations();
assertTrue(node.getText().startsWith("> " + file.getName()));
}
/**
* Tests that a CompareWithHeadAction on a file from a submodule folder does
* open the right compare editor, comparing against the version from the
* submodule (as opposed to the version from the parent repo).
*
* @throws Exception
*/
@Test
public void compareWithHeadInSubmoduleFolder() throws Exception {
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=446344#c11
// If the compare editor's title does not contain the HEAD id of
// the subrepo, then either no compare editor got opened, or
// it was opened using the parent repo.
IFolder childProjectFolder = childFolder.getFolder(CHILDPROJECT);
IFolder folder = childProjectFolder.getFolder(FOLDER);
IFile file = folder.getFile(FILE1);
touch(PROJ1, file.getProjectRelativePath().toOSString(), "Modified");
SWTBotTree projectExplorerTree = TestUtil.getExplorerTree();
SWTBotTreeItem node = TestUtil.navigateTo(projectExplorerTree,
file.getFullPath().segments());
node.select();
Ref headRef = subRepository.findRef(Constants.HEAD);
final String headId = headRef.getObjectId().abbreviate(6).name();
ContextMenuHelper.clickContextMenuSync(projectExplorerTree,
"Compare With",
util.getPluginLocalizedValue("CompareWithHeadAction_label"));
bot.waitUntil(waitForEditor(new BaseMatcher<IEditorReference>() {
@Override
public boolean matches(Object item) {
return (item instanceof IEditorReference)
&& ((IEditorReference) item).getTitle()
.contains(headId);
}
@Override
public void describeTo(Description description) {
description.appendText("Wait for editor containing " + headId);
}
}), 5000);
}
@Test
public void testDisconnect() throws Exception {
SWTBotTree projectExplorerTree = TestUtil.getExplorerTree();
getProjectItem(projectExplorerTree, PROJ1).select();
String menuString = util
.getPluginLocalizedValue("DisconnectAction_label");
ContextMenuHelper.clickContextMenuSync(projectExplorerTree, "Team",
menuString);
ResourcesPlugin.getWorkspace().getRoot()
.refreshLocal(IResource.DEPTH_INFINITE, null);
// Access the session property directly: RepositoryMapping.getMapping()
// checks whether the project is shared with git.
Object mapping = childFolder.getSessionProperty(new QualifiedName(
GitProjectData.class.getName(), "RepositoryMapping"));
assertNull("Should have no RepositoryMapping", mapping);
}
@Test
public void testDecoration() throws Exception {
SWTBotTree projectExplorerTree = TestUtil.getExplorerTree();
SWTBotTreeItem node = TestUtil.navigateTo(projectExplorerTree,
childFolder.getFullPath().segments());
TestUtil.waitForDecorations();
assertTrue("Folder should have repo/branch decoration",
node.getText().contains("[master"));
TestUtil.expandAndWait(node);
node = TestUtil.getChildNode(node, CHILDPROJECT);
TestUtil.waitForDecorations();
assertFalse("Folder should not have repo/branch decoration",
node.getText().contains("["));
node = TestUtil.navigateTo(projectExplorerTree, CHILDPROJECT);
TestUtil.waitForDecorations();
assertTrue("Project should have subrepo/branch decoration",
node.getText().contains("[child"));
}
/**
* Tests that unrelated changes to the configured repositories do not
* prematurely remove submodules from the cache.
*/
@Test
public void testRepoRemoval() {
Activator.getDefault().getRepositoryUtil()
.addConfiguredRepository(childRepositoryGitDir);
assertTrue("Should still have the subrepo in the cache",
containsRepo(Activator.getDefault().getRepositoryCache()
.getAllRepositories(), subRepository));
assertTrue("Should have changed the preference", Activator.getDefault()
.getRepositoryUtil().removeDir(childRepositoryGitDir));
assertTrue("Should still have the subrepo in the cache",
containsRepo(Activator.getDefault().getRepositoryCache()
.getAllRepositories(), subRepository));
}
@SuppressWarnings("restriction")
@Test
public void testHistoryFromProjectExplorerIsFromSubRepository()
throws Exception {
// Open history view
SWTBotView historyBot = TestUtil.showHistoryView();
IViewPart viewPart = historyBot.getViewReference().getView(false);
assertTrue(
viewPart instanceof org.eclipse.team.internal.ui.history.GenericHistoryView);
// Set link with selection
((org.eclipse.team.internal.ui.history.GenericHistoryView) viewPart)
.setLinkingEnabled(true);
// Select PROJ1 (has 3 commits)
TestUtil.navigateTo(TestUtil.getExplorerTree(), PROJ1).select();
assertRowCountInHistory(PROJ1, 3);
// Select the child folder (from the submodule; has 2 commits)
TestUtil.navigateTo(TestUtil.getExplorerTree(),
childFolder.getFullPath().segments()).select();
assertRowCountInHistory(childFolder.getFullPath() + " from submodule",
2);
}
private boolean containsRepo(Repository[] repositories, Repository needle) {
for (Repository repo : repositories) {
if (needle.equals(repo)) {
return true;
}
}
return false;
}
private void assertRowCountInHistory(String msg, int expected)
throws Exception {
SWTBotView historyBot = TestUtil.showHistoryView();
Job.getJobManager().join(GENERATE_HISTORY, null);
historyBot.getWidget().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
// Joins UI update triggered by GenerateHistoryJob
}
});
assertEquals(msg + " should show " + expected + " commits", expected,
historyBot.bot().table().rowCount());
}
}