diff options
author | Thomas Wolf | 2016-02-02 06:44:14 +0000 |
---|---|---|
committer | Thomas Wolf | 2016-02-25 20:22:16 +0000 |
commit | 5dc7ac99cb0b52ad87e2c563f5dfecf9a800e82c (patch) | |
tree | 4c33a45ba99b441e1e5d8191eb7fedfebad9a6d8 /org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal | |
parent | 005b828c74505df76dc8f237f7e2dab36eada45c (diff) | |
download | egit-5dc7ac99cb0b52ad87e2c563f5dfecf9a800e82c.tar.gz egit-5dc7ac99cb0b52ad87e2c563f5dfecf9a800e82c.tar.xz egit-5dc7ac99cb0b52ad87e2c563f5dfecf9a800e82c.zip |
Fix recognition of submodules in folders.
Most of the basic plumbing for this was already in place, but it looks
as if the implementation was never really finished. For instance,
GitProjectData is prepared to record RepositoryMapping on container
level, not just for projects. The feature was just not used, and many
places in EGit make assumptions that imply that a project is fully
within one repository. This is a first commit to get rid of this
assumption, and to properly deal with submodules that exist only as
folders in the Eclipse workspace.
Augment the already existing resource change listener in GitProjectData
to also handle additions of DOT_GIT resources. In that way it'll pick
up submodules as they appear in the Eclipse resource tree.
RepositoryMapping.getMapping(IResource) must consider mappings
entered for folders below the project level. One mustn't jump directly
to project level; that will skip any submodules that might have been
applicable.
StagingView: no need anymore to use a submodule walk. The
RepositoryMapping for any resource will point to the correct
repository, even if it's a submodule. Not using a submodule walk also
avoids problem with the walk returning non-normalized git directory
paths that may contain ".." segments, while our RepositoryCache uses
normalized paths. This may yield two versions of the same repo in the
cache, and listening for index diff changes on one wouldn't trigger on
the other.
GitResourceDeltaVisitor: must descend into folders even if the
repository doesn't match on project level. Otherwise submodules are not
updated.
For scheduling rule calculation, it is not sufficient to search for
projects in the repository's working directory. One also needs to
include projects containing the repository working directory.
Deprecated:
* RepositoryMapping.getSubmoduleRepository(IResource)
Also changed some uses of RepositoryMapping.getMapping(IProject) to
RepositoryMapping.getMapping(IResource). I'd like to have deprecated
the project variant, but this needs more careful analysis of the
remaining places its used.
Properly adding submodule mappings and considering them fixes at least
bug 446344, comment 11. Also related is bug 401556, though that was
reported for the behavior in the Repositories view, which isn't fixed by
this commit yet. However, a selection in the project explorer for a
folder belonging to a submodule showed the same erroneous behavior.
Added new tests.
Bug: 446344
Bug: 401556
Change-Id: I4caa06113b5280114a7816f2c3932711b2fedf08
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal')
-rw-r--r-- | org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java new file mode 100644 index 0000000000..c15b6b156f --- /dev/null +++ b/org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal/submodules/SubmoduleFolderTest.java @@ -0,0 +1,288 @@ +/******************************************************************************* + * 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.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.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.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.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); + 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); + } + + @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()); + 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); + IResourceState state = ResourceStateFactory.getInstance() + .get(cache.getIndexDiff(), file); + assertTrue("File should be staged", state.isStaged()); + 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()); + 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); + } + + @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 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()); + } + + @SuppressWarnings("restriction") + private void waitForDecorations() throws Exception { + TestUtil.joinJobs( + org.eclipse.ui.internal.decorators.DecoratorManager.FAMILY_DECORATE); + } +} |