/* * Sonar SCM Activity Plugin * Copyright (C) 2010 SonarSource * dev@sonar.codehaus.org * * This program 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. * * This program 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 02 */ package org.sonar.plugins.scmactivity; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.*; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.Metric; import org.sonar.api.measures.PersistenceMode; import org.sonar.api.resources.Java; import org.sonar.api.resources.JavaFile; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.utils.TimeProfiler; import org.sonar.plugins.scmactivity.ProjectStatus.FileStatus; import java.util.Arrays; import java.util.List; /** * @author Evgeny Mandrikov */ public final class ScmActivitySensor implements Sensor { private static final Logger LOG = LoggerFactory.getLogger(ScmActivitySensor.class); private TimeMachine timeMachine; private ScmConfiguration conf; private LocalModificationChecker checkLocalModifications; private Changelog changelog; private Blame blameSensor; private UrlChecker urlChecker; public ScmActivitySensor(ScmConfiguration conf, LocalModificationChecker checkLocalModifications, Changelog changelog, Blame blameSensor, TimeMachine timeMachine, UrlChecker urlChecker) { this.conf = conf; this.checkLocalModifications = checkLocalModifications; this.changelog = changelog; this.blameSensor = blameSensor; this.timeMachine = timeMachine; this.urlChecker = urlChecker; } @DependedUpon public List<Metric> generatesMetrics() { return Arrays.asList( CoreMetrics.SCM_REVISION, CoreMetrics.SCM_LAST_COMMIT_DATE, CoreMetrics.SCM_COMMITS, CoreMetrics.SCM_AUTHORS_BY_LINE, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, CoreMetrics.SCM_REVISIONS_BY_LINE); } public boolean shouldExecuteOnProject(Project project) { return conf.isEnabled(); } public void analyse(Project project, SensorContext context) { urlChecker.check(); checkLocalModifications.check(); ProjectStatus projectStatus = new ProjectStatus(project); changelog.load(projectStatus, getPreviousRevision(project)); TimeProfiler profiler = new TimeProfiler().start("Retrieve files SCM info"); LOG.debug(String.format("%d files touched in changelog", projectStatus.getFileStatuses().size())); for (FileStatus fileStatus : projectStatus.getFileStatuses()) { inspectFile(context, fileStatus); } profiler.stop(); inspectProject(context, project, projectStatus); } private void inspectFile(SensorContext context, FileStatus fileStatus) { Resource resource = toResource(context, fileStatus); if (resource == null) { LOG.debug("File not found in Sonar index: " + fileStatus.getFile()); return; } if (fileStatus.isModified()) { blameSensor.analyse(fileStatus, resource, context); } else { LOG.debug("File not changed since previous analysis: " + fileStatus.getFile()); copyPreviousFileMeasures(resource, context); } } private void copyPreviousFileMeasures(Resource resource, SensorContext context) { List<Measure> previousMeasures = getPreviousMeasures(resource, CoreMetrics.SCM_REVISION, CoreMetrics.SCM_LAST_COMMIT_DATE, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE, CoreMetrics.SCM_REVISIONS_BY_LINE, CoreMetrics.SCM_AUTHORS_BY_LINE); for (Measure previousMeasure : previousMeasures) { if (StringUtils.isNotBlank( previousMeasure.getData())) { PersistenceMode persistence = previousMeasure.getMetric().isDataType() ? PersistenceMode.DATABASE : PersistenceMode.FULL; context.saveMeasure(resource, new Measure(previousMeasure.getMetric(), previousMeasure.getData())).setPersistenceMode(persistence); } } } private Resource toResource(SensorContext context, FileStatus fileStatus) { Resource resource = null; if (conf.isJavaProject()) { if (Java.isJavaFile(fileStatus.getFile())) { resource = JavaFile.fromRelativePath(fileStatus.getRelativePath(), false); } } else { resource = new org.sonar.api.resources.File(fileStatus.getRelativePath()); } if (resource != null) { return context.getResource(resource); } return null; } private void inspectProject(SensorContext context, Project project, ProjectStatus projectStatus) { final String revision; final String date; if (projectStatus.isModified()) { revision = projectStatus.getRevision(); date = ScmUtils.formatLastCommitDate(projectStatus.getDate()); } else { revision = getPreviousMeasure(project, CoreMetrics.SCM_REVISION); date = getPreviousMeasure(project, CoreMetrics.SCM_LAST_COMMIT_DATE); } Double commits = getPreviousMeasureValue(project, CoreMetrics.SCM_COMMITS); if (commits == null) { commits = 0d; } commits += projectStatus.getChanges(); context.saveMeasure(new Measure(CoreMetrics.SCM_REVISION, revision)); context.saveMeasure(new Measure(CoreMetrics.SCM_LAST_COMMIT_DATE, date)); context.saveMeasure(new Measure(CoreMetrics.SCM_COMMITS, commits)); } private Double getPreviousMeasureValue(Resource resource, Metric metric) { TimeMachineQuery query = new TimeMachineQuery(resource) .setOnlyLastAnalysis(true) .setMetrics(metric); List<Object[]> fields = timeMachine.getMeasuresFields(query); if (fields.isEmpty()) { return null; } return (Double) fields.get(0)[2]; } private String getPreviousMeasure(Resource resource, Metric metric) { List<Measure> measures = getPreviousMeasures(resource, metric); if (measures.isEmpty()) { return null; } return measures.get(0).getData(); } private List<Measure> getPreviousMeasures(Resource resource, Metric... metrics) { TimeMachineQuery query = new TimeMachineQuery(resource) .setOnlyLastAnalysis(true) .setMetrics(metrics); return timeMachine.getMeasures(query); } private String getPreviousRevision(Project project) { // warning: upgrade from SCM plugin 1.1 to 1.2 must be detected. // Data stored with SCM 1.1 is not enough for this analysis so it must be ignored. // The existence of new metrics of 1.2 (last_commit_date for example) is checked List<Measure> measures = getPreviousMeasures(project, CoreMetrics.SCM_REVISION, CoreMetrics.SCM_LAST_COMMIT_DATE); if (measures.size()==2) { for (Measure measure : measures) { if (measure.getMetric().equals(CoreMetrics.SCM_REVISION)) { return measure.getData(); } } } return null; } @Override public String toString() { return getClass().getSimpleName(); } }