diff options
Diffstat (limited to 'sonar-batch')
13 files changed, 494 insertions, 70 deletions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java index afeec5e589b..10aa20e7872 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -91,14 +91,17 @@ public class DefaultIndex extends SonarIndex { private final DeprecatedViolations deprecatedViolations; private ModuleIssues moduleIssues; + private ResourceKeyMigration migration; + public DefaultIndex(PersistenceManager persistence, DefaultResourceCreationLock lock, ProjectTree projectTree, MetricFinder metricFinder, - ScanGraph graph, DeprecatedViolations deprecatedViolations) { + ScanGraph graph, DeprecatedViolations deprecatedViolations, ResourceKeyMigration migration) { this.persistence = persistence; this.lock = lock; this.projectTree = projectTree; this.metricFinder = metricFinder; this.graph = graph; this.deprecatedViolations = deprecatedViolations; + this.migration = migration; } public void start() { @@ -111,6 +114,7 @@ public class DefaultIndex extends SonarIndex { void doStart(Project rootProject) { Bucket bucket = new Bucket(rootProject); buckets.put(rootProject, bucket); + migration.checkIfMigrationNeeded(rootProject); persistence.saveProject(rootProject, null); currentProject = rootProject; @@ -555,7 +559,6 @@ public class DefaultIndex extends SonarIndex { } resource.setEffectiveKey(ComponentKeys.createEffectiveKey(currentProject, resource)); - resource.setDeprecatedEffectiveKey(ComponentKeys.createDeprecatedEffectiveKey(currentProject, resource)); bucket = new Bucket(resource).setParent(parentBucket); buckets.put(resource, bucket); diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java index 21b36dfc29e..4931060321c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultResourcePersister.java @@ -89,6 +89,8 @@ public final class DefaultResourcePersister implements ResourcePersister { project.setEffectiveKey(project.getKey()); ResourceModel model = findOrCreateModel(project); + // Used by ResourceKeyMigration in order to know that a project has already being migrated + model.setDeprecatedKey(project.getKey()); // ugly, only for projects model.setLanguageKey(project.getLanguageKey()); @@ -271,10 +273,6 @@ public final class DefaultResourcePersister implements ResourcePersister { ResourceModel model; try { model = session.getSingleResult(ResourceModel.class, "key", resource.getEffectiveKey()); - if (model == null && !StringUtils.equals(resource.getEffectiveKey(), resource.getDeprecatedEffectiveKey())) { - // Fallback on deprecated key when resource has not already been migrated - model = session.getSingleResult(ResourceModel.class, "key", resource.getDeprecatedEffectiveKey(), "deprecatedKey", null); - } if (model == null) { if (StringUtils.isBlank(resource.getEffectiveKey())) { throw new SonarException("Unable to persist resource " + resource.toString() + ". Resource effective key is blank. This may be caused by an outdated plugin."); @@ -296,8 +294,6 @@ public final class DefaultResourcePersister implements ResourcePersister { model.setEnabled(Boolean.TRUE); model.setDescription(resource.getDescription()); model.setKey(resource.getEffectiveKey()); - String deprecatedEffectiveKey = resource.getDeprecatedEffectiveKey(); - model.setDeprecatedKey(StringUtils.isNotBlank(deprecatedEffectiveKey) ? deprecatedEffectiveKey : resource.getEffectiveKey()); model.setPath(resource.getPath()); if (resource.getLanguage() != null) { model.setLanguageKey(resource.getLanguage().getKey()); @@ -316,12 +312,6 @@ public final class DefaultResourcePersister implements ResourcePersister { static void mergeModel(ResourceModel model, Resource resource) { model.setEnabled(true); model.setKey(resource.getEffectiveKey()); - if (StringUtils.isNotBlank(resource.getDeprecatedEffectiveKey())) { - model.setDeprecatedKey(resource.getDeprecatedEffectiveKey()); - } else if (StringUtils.isBlank(model.getDeprecatedKey())) { - // By default deprecated key is the same as previous key - model.setDeprecatedKey(model.getKey()); - } if (StringUtils.isNotBlank(resource.getName())) { model.setName(resource.getName()); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/ResourceKeyMigration.java b/sonar-batch/src/main/java/org/sonar/batch/index/ResourceKeyMigration.java new file mode 100644 index 00000000000..ae97d13aa4e --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/index/ResourceKeyMigration.java @@ -0,0 +1,174 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.database.DatabaseSession; +import org.sonar.api.database.model.ResourceModel; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Java; +import org.sonar.api.resources.JavaFile; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Resource; +import org.sonar.api.resources.Scopes; +import org.sonar.api.scan.filesystem.internal.DefaultInputFile; +import org.sonar.api.scan.filesystem.internal.InputFile; +import org.sonar.api.utils.PathUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ResourceKeyMigration { + + private final Logger logger; + private final DatabaseSession session; + + private boolean migrationNeeded = false; + + public ResourceKeyMigration(DatabaseSession session) { + this(session, LoggerFactory.getLogger(ResourceKeyMigration.class)); + } + + @VisibleForTesting + ResourceKeyMigration(DatabaseSession session, Logger logger) { + this.session = session; + this.logger = logger; + } + + public void checkIfMigrationNeeded(Project rootProject) { + ResourceModel model = session.getSingleResult(ResourceModel.class, "key", rootProject.getEffectiveKey()); + if (model != null && StringUtils.isBlank(model.getDeprecatedKey())) { + logger.info("Resources keys of project '" + rootProject.getName() + "' should be migrated"); + this.migrationNeeded = true; + } + } + + public void migrateIfNeeded(Project module, Iterable<InputFile> inputFiles) { + if (migrationNeeded) { + logger.info("Starting migration of resource keys"); + Map<String, InputFile> deprecatedFileKeyMapper = new HashMap<String, InputFile>(); + Map<String, InputFile> deprecatedTestKeyMapper = new HashMap<String, InputFile>(); + Map<String, String> deprecatedDirectoryKeyMapper = new HashMap<String, String>(); + for (InputFile inputFile : inputFiles) { + String deprecatedKey = inputFile.attribute(DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY); + if (deprecatedKey != null) { + if (InputFile.TYPE_TEST.equals(inputFile.attribute(InputFile.ATTRIBUTE_TYPE))) { + deprecatedTestKeyMapper.put(deprecatedKey, inputFile); + } else { + deprecatedFileKeyMapper.put(deprecatedKey, inputFile); + } + } + } + + ResourceModel moduleModel = session.getSingleResult(ResourceModel.class, "key", module.getEffectiveKey()); + int moduleId = moduleModel.getId(); + migrateFiles(module, deprecatedFileKeyMapper, deprecatedTestKeyMapper, deprecatedDirectoryKeyMapper, moduleId); + migrateDirectories(deprecatedDirectoryKeyMapper, moduleId); + session.commit(); + } + } + + private void migrateFiles(Project module, Map<String, InputFile> deprecatedFileKeyMapper, Map<String, InputFile> deprecatedTestKeyMapper, + Map<String, String> deprecatedDirectoryKeyMapper, + int moduleId) { + // Find all FIL or CLA resources for this module + StringBuilder hql = new StringBuilder().append("from ") + .append(ResourceModel.class.getSimpleName()) + .append(" where enabled = true ") + .append(" and rootId = :rootId ") + .append(" and scope = '").append(Scopes.FILE).append("'"); + List<ResourceModel> resources = session.createQuery(hql.toString()).setParameter("rootId", moduleId).getResultList(); + for (ResourceModel resourceModel : resources) { + String oldEffectiveKey = resourceModel.getKey(); + boolean isTest = Qualifiers.UNIT_TEST_FILE.equals(resourceModel.getQualifier()); + InputFile matchedFile = findInputFile(deprecatedFileKeyMapper, deprecatedTestKeyMapper, oldEffectiveKey, isTest); + if (matchedFile != null) { + String newEffectiveKey = matchedFile.attribute(DefaultInputFile.ATTRIBUTE_COMPONENT_KEY); + // Now compute migration of the parent dir + String oldKey = StringUtils.substringAfterLast(oldEffectiveKey, ":"); + Resource sonarFile; + if (Java.KEY.equals(resourceModel.getLanguageKey())) { + sonarFile = new JavaFile(oldKey); + } else { + sonarFile = new File(oldKey); + } + String parentOldKey = module.getEffectiveKey() + ":" + sonarFile.getParent().getDeprecatedKey(); + String parentNewKey = module.getEffectiveKey() + ":" + getParentKey(matchedFile); + if (!deprecatedDirectoryKeyMapper.containsKey(parentOldKey)) { + deprecatedDirectoryKeyMapper.put(parentOldKey, parentNewKey); + } else if (!parentNewKey.equals(deprecatedDirectoryKeyMapper.get(parentOldKey))) { + logger.warn("Directory with key " + parentOldKey + " matches both " + deprecatedDirectoryKeyMapper.get(parentOldKey) + " and " + + parentNewKey + ". First match is arbitrary chosen."); + } + resourceModel.setKey(newEffectiveKey); + resourceModel.setDeprecatedKey(oldEffectiveKey); + logger.info("Migrated resource " + oldEffectiveKey + " to " + newEffectiveKey); + } else { + logger.warn("Unable to migrate resource " + oldEffectiveKey + ". No match was found."); + } + } + } + + private InputFile findInputFile(Map<String, InputFile> deprecatedFileKeyMapper, Map<String, InputFile> deprecatedTestKeyMapper, String oldEffectiveKey, boolean isTest) { + if (isTest) { + return deprecatedTestKeyMapper.get(oldEffectiveKey); + } else { + return deprecatedFileKeyMapper.get(oldEffectiveKey); + } + } + + private void migrateDirectories(Map<String, String> deprecatedDirectoryKeyMapper, int moduleId) { + // Find all DIR resources for this module + StringBuilder hql = new StringBuilder().append("from ") + .append(ResourceModel.class.getSimpleName()) + .append(" where enabled = true ") + .append(" and rootId = :rootId ") + .append(" and qualifier = '").append(Qualifiers.DIRECTORY).append("')"); + List<ResourceModel> resources = session.createQuery(hql.toString()).setParameter("rootId", moduleId).getResultList(); + for (ResourceModel resourceModel : resources) { + String oldEffectiveKey = resourceModel.getKey(); + if (deprecatedDirectoryKeyMapper.containsKey(oldEffectiveKey)) { + String newEffectiveKey = deprecatedDirectoryKeyMapper.get(oldEffectiveKey); + resourceModel.setKey(newEffectiveKey); + resourceModel.setDeprecatedKey(oldEffectiveKey); + logger.info("Migrated resource " + oldEffectiveKey + " to " + newEffectiveKey); + } else { + logger.warn("Unable to migrate resource " + oldEffectiveKey); + } + } + } + + private String getParentKey(InputFile matchedFile) { + String filePath = PathUtils.sanitize(matchedFile.path()); + String parentFolderPath; + if (filePath.contains(Directory.SEPARATOR)) { + parentFolderPath = StringUtils.substringBeforeLast(filePath, Directory.SEPARATOR); + } else { + parentFolderPath = Directory.SEPARATOR; + } + return parentFolderPath; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java b/sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java index 408557f9c2b..880f74d44e0 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/phases/FileIndexer.java @@ -35,40 +35,39 @@ import org.sonar.api.resources.Resource; import org.sonar.api.scan.filesystem.FileQuery; import org.sonar.api.scan.filesystem.internal.InputFile; import org.sonar.api.utils.SonarException; +import org.sonar.batch.index.ResourceKeyMigration; import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; /** - * Index all files/directories of the module in SQ database. + * Index all files/directories of the module in SQ database and importing source code. * @since 4.2 */ @InstantiationStrategy(InstantiationStrategy.PER_PROJECT) public class FileIndexer implements BatchComponent { - private final Project module; private final DefaultModuleFileSystem fs; private final Languages languages; private final Settings settings; private final SonarIndex sonarIndex; + private ResourceKeyMigration migration; + private Project module; - private boolean importSource; - - public FileIndexer(Project module, DefaultModuleFileSystem fs, Languages languages, SonarIndex sonarIndex, Settings settings) { + public FileIndexer(Project module, DefaultModuleFileSystem fs, Languages languages, SonarIndex sonarIndex, Settings settings, ResourceKeyMigration migration) { this.module = module; this.fs = fs; this.languages = languages; this.sonarIndex = sonarIndex; this.settings = settings; + this.migration = migration; } public void execute() { - this.importSource = settings.getBoolean(CoreProperties.CORE_IMPORT_SOURCES_PROPERTY); - String languageKey = module.getLanguageKey(); - indexFiles(fs.inputFiles(FileQuery.onSource().onLanguage(languageKey)), false, languageKey); - indexFiles(fs.inputFiles(FileQuery.onTest().onLanguage(languageKey)), true, languageKey); - } - - private void indexFiles(Iterable<InputFile> files, boolean unitTest, String languageKey) { - for (InputFile inputFile : files) { + boolean importSource = settings.getBoolean(CoreProperties.CORE_IMPORT_SOURCES_PROPERTY); + Iterable<InputFile> inputFiles = fs.inputFiles(FileQuery.all()); + migration.migrateIfNeeded(module, inputFiles); + for (InputFile inputFile : inputFiles) { + String languageKey = inputFile.attribute(InputFile.ATTRIBUTE_LANGUAGE); + boolean unitTest = InputFile.TYPE_TEST.equals(inputFile.attribute(InputFile.ATTRIBUTE_TYPE)); Resource sonarFile; if (Java.KEY.equals(languageKey)) { sonarFile = JavaFile.create(inputFile.path(), inputFile.attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH), unitTest); diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index 3edf7f278ec..cac9e05a534 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -52,6 +52,7 @@ import org.sonar.batch.index.LinkPersister; import org.sonar.batch.index.MeasurePersister; import org.sonar.batch.index.MemoryOptimizer; import org.sonar.batch.index.ResourceCache; +import org.sonar.batch.index.ResourceKeyMigration; import org.sonar.batch.index.SnapshotCache; import org.sonar.batch.index.SourcePersister; import org.sonar.batch.issue.DefaultProjectIssues; @@ -134,6 +135,7 @@ public class ProjectScanContainer extends ComponentContainer { MetricProvider.class, ProjectConfigurator.class, DefaultIndex.class, + ResourceKeyMigration.class, DefaultFileLinesContextFactory.class, ProjectLock.class, LastSnapshots.class, diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java index 87b15d8c4df..886ca6c0834 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultIndexTest.java @@ -86,7 +86,7 @@ public class DefaultIndexTest { ruleFinder = mock(RuleFinder.class); ProjectTree projectTree = mock(ProjectTree.class); - index = new DefaultIndex(mock(PersistenceManager.class), lock, projectTree, metricFinder, mock(ScanGraph.class), deprecatedViolations); + index = new DefaultIndex(mock(PersistenceManager.class), lock, projectTree, metricFinder, mock(ScanGraph.class), deprecatedViolations, mock(ResourceKeyMigration.class)); java.io.File baseDir = temp.newFolder(); project = new Project("project"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java index b08ab61d303..8799039f1ce 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DefaultResourcePersisterTest.java @@ -146,7 +146,7 @@ public class DefaultResourcePersisterTest extends AbstractDbUnitTestCase { ResourcePersister persister = new DefaultResourcePersister(getSession(), mock(ResourcePermissions.class), snapshotCache, resourceCache); persister.saveProject(singleProject, null); persister.saveResource(singleProject, - Directory.create("src/main/java/org/foo", "org.foo").setEffectiveKey("foo:src/main/java/org/foo").setDeprecatedEffectiveKey("foo:org.foo")); + Directory.create("src/main/java/org/foo", "org.foo").setEffectiveKey("foo:src/main/java/org/foo")); // check that the directory is attached to the project checkTables("shouldSaveNewDirectory", new String[] {"build_date", "created_at"}, "projects", "snapshots"); diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/ResourceKeyMigrationTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/ResourceKeyMigrationTest.java new file mode 100644 index 00000000000..aad9df2dce5 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/index/ResourceKeyMigrationTest.java @@ -0,0 +1,128 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.index; + +import com.google.common.base.Charsets; +import org.apache.commons.configuration.PropertiesConfiguration; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; +import org.sonar.api.resources.Project; +import org.sonar.api.scan.filesystem.internal.DefaultInputFile; +import org.sonar.api.scan.filesystem.internal.InputFile; +import org.sonar.api.scan.filesystem.internal.InputFileBuilder; +import org.sonar.jpa.test.AbstractDbUnitTestCase; + +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Arrays; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class ResourceKeyMigrationTest extends AbstractDbUnitTestCase { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + Project multiModuleProject, phpModule, javaModule; + SnapshotCache snapshotCache = mock(SnapshotCache.class); + ResourceCache resourceCache = mock(ResourceCache.class); + private Iterable<InputFile> javaInputFiles; + private Iterable<InputFile> phpInputFiles; + private File baseDir; + + @Before + public void before() throws ParseException, IOException { + SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy"); + + multiModuleProject = newProject("root", "java"); + multiModuleProject.setName("Root").setAnalysisDate(format.parse("25/12/2010")); + + phpModule = newProject("a", "php"); + phpModule.setName("A").setAnalysisDate(format.parse("25/12/2010")); + phpModule.setParent(multiModuleProject); + phpModule.setPath("/moduleA"); + + javaModule = newProject("b", "java"); + javaModule.setName("B").setAnalysisDate(format.parse("25/12/2010")); + javaModule.setParent(multiModuleProject); + javaModule.setPath("/moduleB"); + + baseDir = temp.newFolder(); + + javaInputFiles = (Iterable) Arrays.asList( + newInputFile(javaModule, "src/main/java/org/foo/Bar.java", "org.foo.Bar", false), + newInputFile(javaModule, "src/main/java/RootBar.java", "[default].RootBar", false), + newInputFile(javaModule, "src/test/java/org/foo/BarTest.java", "org.foo.BarTest", true)); + + phpInputFiles = (Iterable) Arrays.asList( + newInputFile(phpModule, "org/foo/Bar.php", "org/foo/Bar.php", false), + newInputFile(phpModule, "RootBar.php", "RootBar.php", false), + newInputFile(phpModule, "test/org/foo/BarTest.php", "org/foo/BarTest.php", true)); + + } + + private DefaultInputFile newInputFile(Project module, String path, String deprecatedKey, boolean isTest) { + File file = new File(baseDir, path); + String effectiveKey = module.getKey() + ":" + path; + String deprecatedEffectiveKey = module.getKey() + ":" + deprecatedKey; + return new InputFileBuilder(file, Charsets.UTF_8, path) + .attribute(DefaultInputFile.ATTRIBUTE_COMPONENT_KEY, effectiveKey) + .attribute(DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, deprecatedEffectiveKey) + .attribute(InputFile.ATTRIBUTE_TYPE, isTest ? InputFile.TYPE_TEST : InputFile.TYPE_SOURCE).build(); + } + + @Test + public void shouldMigrateResourceKeys() { + setupData("shouldMigrateResourceKeys"); + + Logger logger = mock(Logger.class); + ResourceKeyMigration migration = new ResourceKeyMigration(getSession(), logger); + migration.checkIfMigrationNeeded(multiModuleProject); + verify(logger).info("Resources keys of project 'Root' should be migrated"); + + migration.migrateIfNeeded(javaModule, javaInputFiles); + migration.migrateIfNeeded(phpModule, phpInputFiles); + + verify(logger).info("Migrated resource b:org.foo.Bar to b:src/main/java/org/foo/Bar.java"); + verify(logger).warn("Directory with key b:org/foo matches both b:src/main/java/org/foo and b:src/test/java/org/foo. First match is arbitrary chosen."); + verify(logger).info("Migrated resource b:org.foo.BarTest to b:src/test/java/org/foo/BarTest.java"); + verify(logger).info("Migrated resource b:[default].RootBar to b:src/main/java/RootBar.java"); + verify(logger).info("Migrated resource b:org/foo to b:src/main/java/org/foo"); + verify(logger).info("Migrated resource b:[root] to b:src/main/java"); + + checkTables("shouldMigrateResourceKeys", new String[] {"build_date", "created_at"}, "projects"); + } + + private static Project newProject(String key, String language) { + PropertiesConfiguration configuration = new PropertiesConfiguration(); + configuration.setProperty("sonar.language", language); + return new Project(key).setConfiguration(configuration).setAnalysisType(Project.AnalysisType.DYNAMIC); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java b/sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java index cf8d3aaf20c..e029d097752 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/phases/FileIndexerTest.java @@ -20,7 +20,6 @@ package org.sonar.batch.phases; import com.google.common.base.Charsets; -import edu.emory.mathcs.backport.java.util.Collections; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.CharEncoding; import org.junit.Before; @@ -40,8 +39,10 @@ import org.sonar.api.resources.Project; import org.sonar.api.resources.Qualifiers; import org.sonar.api.resources.Resource; import org.sonar.api.scan.filesystem.FileQuery; +import org.sonar.api.scan.filesystem.internal.DefaultInputFile; import org.sonar.api.scan.filesystem.internal.InputFile; import org.sonar.api.scan.filesystem.internal.InputFileBuilder; +import org.sonar.batch.index.ResourceKeyMigration; import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem; import java.io.File; @@ -88,19 +89,13 @@ public class FileIndexerTest { } @Test - public void should_index_java_files() { - File javaFile1 = new File(baseDir, "src/main/java/foo/bar/Foo.java"); - File javaFile2 = new File(baseDir, "src/main/java2/foo/bar/Foo.java"); - when(fs.inputFiles(FileQuery.onSource().onLanguage(Java.KEY))).thenReturn((Iterable) Arrays.asList( - new InputFileBuilder(javaFile1, Charsets.UTF_8, "src/main/java/foo/bar/Foo.java").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java").build(), - new InputFileBuilder(javaFile2, Charsets.UTF_8, "src/main/java2/foo/bar/Foo.java").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java").build())); - File javaTestFile1 = new File(baseDir, "src/test/java/foo/bar/FooTest.java"); - when(fs.inputFiles(FileQuery.onTest().onLanguage(Java.KEY))).thenReturn( - (Iterable) Arrays.asList( - new InputFileBuilder(javaTestFile1, Charsets.UTF_8, "src/test/java/foo/bar/FooTest.java").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/FooTest.java") - .build())); + public void should_index_java_files() throws IOException { + when(fs.inputFiles(FileQuery.all())).thenReturn((Iterable) Arrays.asList( + newInputFile("src/main/java/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false), + newInputFile("src/main/java2/foo/bar/Foo.java", "", "foo/bar/Foo.java", "java", false), + newInputFile("src/test/java/foo/bar/FooTest.java", "", "foo/bar/FooTest.java", "java", true))); when(project.getLanguageKey()).thenReturn(Java.KEY); - FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings); + FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class)); indexer.execute(); verify(sonarIndex).index(JavaFile.create("src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false)); @@ -118,17 +113,13 @@ public class FileIndexerTest { @Test public void should_index_cobol_files() throws IOException { - File cobolFile1 = new File(baseDir, "src/foo/bar/Foo.cbl"); - File cobolFile2 = new File(baseDir, "src2/foo/bar/Foo.cbl"); - when(fs.inputFiles(FileQuery.onSource().onLanguage("cobol"))).thenReturn((Iterable) Arrays.asList( - new InputFileBuilder(cobolFile1, Charsets.UTF_8, "src/foo/bar/Foo.cbl").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.cbl").build(), - new InputFileBuilder(cobolFile2, Charsets.UTF_8, "src2/foo/bar/Foo.cbl").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.cbl").build())); - File cobolTestFile1 = new File(baseDir, "src/test/foo/bar/FooTest.cbl"); - when(fs.inputFiles(FileQuery.onTest().onLanguage("cobol"))).thenReturn((Iterable) Arrays.asList( - new InputFileBuilder(cobolTestFile1, Charsets.UTF_8, "src/test/foo/bar/FooTest.cbl").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/FooTest.cbl").build())); + when(fs.inputFiles(FileQuery.all())).thenReturn((Iterable) Arrays.asList( + newInputFile("src/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false), + newInputFile("src2/foo/bar/Foo.cbl", "", "foo/bar/Foo.cbl", "cobol", false), + newInputFile("src/test/foo/bar/FooTest.cbl", "", "foo/bar/FooTest.cbl", "cobol", true))); when(project.getLanguageKey()).thenReturn("cobol"); - FileIndexer indexer = new FileIndexer(project, fs, new Languages(cobolLanguage), sonarIndex, settings); + FileIndexer indexer = new FileIndexer(project, fs, new Languages(cobolLanguage), sonarIndex, settings, mock(ResourceKeyMigration.class)); indexer.execute(); verify(sonarIndex).index(org.sonar.api.resources.File.create("/src/foo/bar/Foo.cbl", "foo/bar/Foo.cbl", cobolLanguage, false)); @@ -140,14 +131,10 @@ public class FileIndexerTest { public void shouldImportSource() throws IOException { settings.setProperty(CoreProperties.CORE_IMPORT_SOURCES_PROPERTY, "true"); - File javaFile1 = new File(baseDir, "src/main/java/foo/bar/Foo.java"); - FileUtils.write(javaFile1, "sample code"); - when(fs.inputFiles(FileQuery.onSource().onLanguage(Java.KEY))).thenReturn((Iterable) Arrays.asList( - new InputFileBuilder(javaFile1, Charsets.UTF_8, "src/main/java/foo/bar/Foo.java").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java").build())); - when(fs.inputFiles(FileQuery.onTest().onLanguage(Java.KEY))).thenReturn( - (Iterable) Collections.emptyList()); + when(fs.inputFiles(FileQuery.all())).thenReturn((Iterable) Arrays.asList( + newInputFile("src/main/java/foo/bar/Foo.java", "sample code", "foo/bar/Foo.java", "java", false))); when(project.getLanguageKey()).thenReturn(Java.KEY); - FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings); + FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class)); indexer.execute(); Resource sonarFile = JavaFile.create("src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false); @@ -155,6 +142,16 @@ public class FileIndexerTest { verify(sonarIndex).setSource(sonarFile, "sample code"); } + private DefaultInputFile newInputFile(String path, String content, String sourceRelativePath, String languageKey, boolean unitTest) throws IOException { + File file = new File(baseDir, path); + FileUtils.write(file, content); + return new InputFileBuilder(file, Charsets.UTF_8, path) + .attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, sourceRelativePath) + .attribute(InputFile.ATTRIBUTE_LANGUAGE, languageKey) + .attribute(InputFile.ATTRIBUTE_TYPE, unitTest ? InputFile.TYPE_TEST : InputFile.TYPE_SOURCE) + .build(); + } + @Test public void should_use_mac_roman_charset_for_reading_source_files() throws Exception { String encoding = "MacRoman"; @@ -182,12 +179,13 @@ public class FileIndexerTest { File javaFile1 = new File(baseDir, "src/main/java/foo/bar/Foo.java"); FileUtils.write(javaFile1, "\uFEFFpublic class Test", Charsets.UTF_8); - when(fs.inputFiles(FileQuery.onSource().onLanguage(Java.KEY))).thenReturn((Iterable) Arrays.asList( - new InputFileBuilder(javaFile1, Charsets.UTF_8, "src/main/java/foo/bar/Foo.java").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java").build())); - when(fs.inputFiles(FileQuery.onTest().onLanguage(Java.KEY))).thenReturn( - (Iterable) Collections.emptyList()); + when(fs.inputFiles(FileQuery.all())).thenReturn((Iterable) Arrays.asList( + new InputFileBuilder(javaFile1, Charset.forName("UTF-8"), "src/main/java/foo/bar/Foo.java") + .attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java") + .attribute(InputFile.ATTRIBUTE_LANGUAGE, "java") + .build())); when(project.getLanguageKey()).thenReturn(Java.KEY); - FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings); + FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class)); indexer.execute(); Resource sonarFile = JavaFile.create("src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false); @@ -206,15 +204,15 @@ public class FileIndexerTest { File javaFile1 = new File(baseDir, "src/main/java/foo/bar/Foo.java"); FileUtils.copyFile(getFile(testFile), javaFile1); - when(fs.inputFiles(FileQuery.onSource().onLanguage(Java.KEY))) + when(fs.inputFiles(FileQuery.all())) .thenReturn( (Iterable) Arrays.asList( - new InputFileBuilder(javaFile1, Charset.forName(encoding), "src/main/java/foo/bar/Foo.java").attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java") + new InputFileBuilder(javaFile1, Charset.forName(encoding), "src/main/java/foo/bar/Foo.java") + .attribute(InputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, "foo/bar/Foo.java") + .attribute(InputFile.ATTRIBUTE_LANGUAGE, "java") .build())); - when(fs.inputFiles(FileQuery.onTest().onLanguage(Java.KEY))).thenReturn( - (Iterable) Collections.emptyList()); when(project.getLanguageKey()).thenReturn(Java.KEY); - FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings); + FileIndexer indexer = new FileIndexer(project, fs, new Languages(Java.INSTANCE), sonarIndex, settings, mock(ResourceKeyMigration.class)); indexer.execute(); Resource sonarFile = JavaFile.create("/src/main/java/foo/bar/Foo.java", "foo/bar/Foo.java", false); diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewDirectory-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewDirectory-result.xml index fee6de33350..6ca27edcd8b 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewDirectory-result.xml +++ b/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewDirectory-result.xml @@ -17,7 +17,7 @@ <projects id="1002" scope="DIR" qualifier="DIR" kee="foo:src/main/java/org/foo" root_id="1001" name="src/main/java/org/foo" long_name="src/main/java/org/foo" description="[null]" - enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/main/java/org/foo" deprecated_kee="foo:org.foo" /> + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="src/main/java/org/foo" deprecated_kee="[null]" /> <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="3001" project_id="1001" parent_snapshot_id="[null]" root_project_id="1001" root_snapshot_id="[null]" scope="PRJ" qualifier="TRK" created_at="2010-12-25 00:00:00.00" build_date="2010-12-25 00:00:00.00" version="[null]" path="" diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewLibrary-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewLibrary-result.xml index 745203dabf1..a7107a2871a 100644 --- a/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewLibrary-result.xml +++ b/sonar-batch/src/test/resources/org/sonar/batch/index/DefaultResourcePersisterTest/shouldSaveNewLibrary-result.xml @@ -17,7 +17,7 @@ <projects id="1002" scope="PRJ" qualifier="LIB" kee="junit:junit" root_id="[null]" name="junit:junit" long_name="junit:junit" description="[null]" - enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" deprecated_kee="junit:junit" /> + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" deprecated_kee="[null]" /> <snapshots purge_status="[null]" period1_mode="[null]" period1_param="[null]" period1_date="[null]" period2_mode="[null]" period2_param="[null]" period2_date="[null]" period3_mode="[null]" period3_param="[null]" period3_date="[null]" period4_mode="[null]" period4_param="[null]" period4_date="[null]" period5_mode="[null]" period5_param="[null]" period5_date="[null]" id="3001" project_id="1001" parent_snapshot_id="[null]" root_project_id="1001" root_snapshot_id="[null]" scope="PRJ" qualifier="TRK" created_at="2010-12-25 00:00:00.00" build_date="2010-12-25 00:00:00.00" version="[null]" path="" diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ResourceKeyMigrationTest/shouldMigrateResourceKeys-result.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ResourceKeyMigrationTest/shouldMigrateResourceKeys-result.xml new file mode 100644 index 00000000000..3dacd66ea57 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/index/ResourceKeyMigrationTest/shouldMigrateResourceKeys-result.xml @@ -0,0 +1,65 @@ +<dataset> + + <!-- other project already migrated --> + <projects id="1000" scope="PRJ" qualifier="TRK" kee="my:key" root_id="[null]" + name="Other project" long_name="Other" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="my:key" /> + + <!-- Root project not migrated --> + <projects id="1001" scope="PRJ" qualifier="TRK" kee="root" root_id="[null]" + name="Root" long_name="Root" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <!-- PHP module --> + <projects id="1002" scope="PRJ" qualifier="BRC" kee="a" root_id="1001" + name="A" long_name="A" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <!-- Java module --> + <projects id="1003" scope="PRJ" qualifier="BRC" kee="b" root_id="1001" + name="B" long_name="B" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <!-- Java resources --> + <projects id="1004" scope="DIR" qualifier="DIR" kee="b:src/main/java/org/foo" root_id="1003" + name="org/foo" long_name="org/foo" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="b:org/foo" /> + + <projects id="1005" scope="FIL" qualifier="CLA" kee="b:src/main/java/org/foo/Bar.java" root_id="1003" + name="Bar" long_name="org.foo.Bar" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="b:org.foo.Bar" /> + + <projects id="1006" scope="FIL" qualifier="UTS" kee="b:src/test/java/org/foo/BarTest.java" root_id="1003" + name="BarTest" long_name="org.foo.BarTest" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="b:org.foo.BarTest" /> + + <projects id="1007" scope="DIR" qualifier="DIR" kee="b:src/main/java" root_id="1003" + name="[root]" long_name="[root]" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="b:[root]" /> + + <projects id="1008" scope="FIL" qualifier="CLA" kee="b:src/main/java/RootBar.java" root_id="1003" + name="RootBar" long_name="RootBar" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="b:[default].RootBar" /> + + + <!-- PHP resources --> + <projects id="1009" scope="DIR" qualifier="DIR" kee="a:org/foo" root_id="1002" + name="org/foo" long_name="org/foo" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="a:org/foo" /> + + <projects id="1010" scope="FIL" qualifier="FIL" kee="a:org/foo/Bar.php" root_id="1002" + name="Bar.php" long_name="Bar.php" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="a:org/foo/Bar.php" /> + + <projects id="1011" scope="FIL" qualifier="UTS" kee="a:test/org/foo/BarTest.php" root_id="1002" + name="BarTest.php" long_name="BarTest.php" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="a:org/foo/BarTest.php" /> + + <projects id="1012" scope="DIR" qualifier="DIR" kee="a:/" root_id="1002" + name="[root]" long_name="[root]" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="a:[root]" /> + + <projects id="1013" scope="FIL" qualifier="FIL" kee="a:RootBar.php" root_id="1002" + name="RootBar.php" long_name="RootBar.php" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="a:RootBar.php" /> +</dataset> diff --git a/sonar-batch/src/test/resources/org/sonar/batch/index/ResourceKeyMigrationTest/shouldMigrateResourceKeys.xml b/sonar-batch/src/test/resources/org/sonar/batch/index/ResourceKeyMigrationTest/shouldMigrateResourceKeys.xml new file mode 100644 index 00000000000..4cfae50cb46 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/index/ResourceKeyMigrationTest/shouldMigrateResourceKeys.xml @@ -0,0 +1,65 @@ +<dataset> + + <!-- other project already migrated --> + <projects id="1000" scope="PRJ" qualifier="TRK" kee="my:key" root_id="[null]" + name="Other project" long_name="Other" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="my:key" /> + + <!-- Root project not migrated --> + <projects id="1001" scope="PRJ" qualifier="TRK" kee="root" root_id="[null]" + name="Root" long_name="Root" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <!-- PHP module --> + <projects id="1002" scope="PRJ" qualifier="BRC" kee="a" root_id="1001" + name="A" long_name="A" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <!-- Java module --> + <projects id="1003" scope="PRJ" qualifier="BRC" kee="b" root_id="1001" + name="B" long_name="B" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <!-- Java resources --> + <projects id="1004" scope="DIR" qualifier="DIR" kee="b:org/foo" root_id="1003" + name="org/foo" long_name="org/foo" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1005" scope="FIL" qualifier="CLA" kee="b:org.foo.Bar" root_id="1003" + name="Bar" long_name="org.foo.Bar" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1006" scope="FIL" qualifier="UTS" kee="b:org.foo.BarTest" root_id="1003" + name="BarTest" long_name="org.foo.BarTest" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1007" scope="DIR" qualifier="DIR" kee="b:[root]" root_id="1003" + name="[root]" long_name="[root]" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1008" scope="FIL" qualifier="CLA" kee="b:[default].RootBar" root_id="1003" + name="RootBar" long_name="RootBar" description="[null]" + enabled="true" language="java" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + + <!-- PHP resources --> + <projects id="1009" scope="DIR" qualifier="DIR" kee="a:org/foo" root_id="1002" + name="org/foo" long_name="org/foo" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1010" scope="FIL" qualifier="FIL" kee="a:org/foo/Bar.php" root_id="1002" + name="Bar.php" long_name="Bar.php" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1011" scope="FIL" qualifier="UTS" kee="a:org/foo/BarTest.php" root_id="1002" + name="BarTest.php" long_name="BarTest.php" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1012" scope="DIR" qualifier="DIR" kee="a:[root]" root_id="1002" + name="[root]" long_name="[root]" description="[null]" + enabled="true" language="[null]" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> + + <projects id="1013" scope="FIL" qualifier="FIL" kee="a:RootBar.php" root_id="1002" + name="RootBar.php" long_name="RootBar.php" description="[null]" + enabled="true" language="php" copy_resource_id="[null]" person_id="[null]" path="[null]" deprecated_kee="[null]" /> +</dataset> |