Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Wolf2016-02-02 06:44:14 +0000
committerThomas Wolf2016-02-25 20:22:16 +0000
commit5dc7ac99cb0b52ad87e2c563f5dfecf9a800e82c (patch)
tree4c33a45ba99b441e1e5d8191eb7fedfebad9a6d8 /org.eclipse.egit.ui.test/src/org/eclipse/egit/ui/internal
parent005b828c74505df76dc8f237f7e2dab36eada45c (diff)
downloadegit-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.java288
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);
+ }
+}

Back to the top