| /******************************************************************************* |
| * 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()); |
| } |
| |
| } |