All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.marschal.svndiffstat.DiffStatGenerator Maven / Gradle / Ivy

The newest version!
package com.github.marschal.svndiffstat;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;

import org.tmatesoft.svn.core.ISVNLogEntryHandler;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.wc.ISVNDiffGenerator;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNRevision;

import com.github.marschal.svndiffstat.TimeAxisKey.TimeAxisKeyFactory;
import com.github.marschal.svndiffstat.YearMonth.YearMonthFactory;
import com.github.marschal.svndiffstat.YearMonthDay.YearMonthDayFactory;
import com.github.marschal.svndiffstat.YearWeek.YearWeekFactory;


class DiffStatGenerator {


  static NavigableMap getData(DiffStatConfiguration configuration, ProgressReporter reporter) throws SVNException {
    SVNClientManager clientManager = SVNClientManager.newInstance();

    reporter.startRevisionLogging();
    List coordinates = getCommitCoordinates(clientManager, configuration, reporter);
    reporter.revisionLoggingDone(coordinates);

    reporter.startRevisionParsing();
    Map diffStats = getDiffStats(clientManager, coordinates, configuration, reporter);
    reporter.revisionParsinDone(diffStats);

    reporter.startAggregation();
    removeTooLargeValues(diffStats, configuration);
    NavigableMap aggregatedDiffstats = buildAggregatedDiffstats(coordinates, diffStats);
    insertZeroDataPoints(aggregatedDiffstats);
    reporter.aggregationDone(aggregatedDiffstats);

    return aggregatedDiffstats;
  }
  

  /**
   * If two consecutive data points are more than one tick away add one or tow
   * zero data points between them.
   * 
   * This makes the chart "spikier" but avoids boxes during large periods of inactivity.
   */
  static void insertZeroDataPoints(NavigableMap diffStats) {
    if (diffStats.size() < 2) {
      return;
    }
    TimeAxisKey previousKeyInMap = diffStats.firstKey();
    TimeAxisKey nextKeyInMap = diffStats.higherKey(previousKeyInMap);
    while (nextKeyInMap != null) {
      TimeAxisKey nextKeyInRange = previousKeyInMap.next();
      if (!nextKeyInMap.equals(nextKeyInRange)) {
        diffStats.put(nextKeyInRange, new DiffStat(0, 0));
        
        TimeAxisKey previousKeyInRange = nextKeyInMap.previous();
        if (!previousKeyInRange.equals(nextKeyInMap)) {
          diffStats.put(previousKeyInRange, new DiffStat(0, 0));
        }
      }
      
      previousKeyInMap = nextKeyInMap;
      nextKeyInMap = diffStats.higherKey(previousKeyInMap);
    }
  }
  

  private static void removeTooLargeValues(Map diffStats, DiffStatConfiguration configuration) {
    Iterator> entrySetIterator = diffStats.entrySet().iterator();
    while (entrySetIterator.hasNext()) {
      Entry entry = entrySetIterator.next();
      DiffStat diffStat = entry.getValue();
      if (diffStat.added() > configuration.getMaxChanges() || diffStat.removed() > configuration.getMaxChanges()) {
        entrySetIterator.remove();
      }
    }
  }

  private static NavigableMap buildAggregatedDiffstats(List coordinates, Map diffStats) {
    if (coordinates.isEmpty()) {
      return new TreeMap<>();
    }

    TimeAxisKeyFactory factory = getFactory(coordinates);

    Map revisionToDateMap = buildRevisionToDateMap(coordinates, factory);

    TimeAxisKey fakeStart = factory.fromDate(coordinates.get(0).getDate()).previous();
    NavigableMap aggregatedDiffstats = new TreeMap<>();
    aggregatedDiffstats.put(fakeStart, new DiffStat(0, 0));
    for (Entry entry : diffStats.entrySet()) {
      Long revision = entry.getKey();
      TimeAxisKey timeKey = revisionToDateMap.get(revision);
      DiffStat oldDiffStat = aggregatedDiffstats.get(timeKey);
      DiffStat diffStat = entry.getValue();
      if (oldDiffStat != null) {
        oldDiffStat.add(diffStat);
      } else {
        aggregatedDiffstats.put(timeKey, diffStat);
      }
    }
    return aggregatedDiffstats;
  }

  private static TimeAxisKeyFactory getFactory(List coordinates) {
    Date first = coordinates.get(0).getDate();
    Date last = coordinates.get(coordinates.size() - 1).getDate();

    if (YearMonthDay.daysBetween(first, last) >= 100) {
      if (YearWeek.weeksBetween(first, last) >= 100) {
        return new YearMonthFactory();
      } else {
        return new YearWeekFactory();
      }
    } else {
      return new YearMonthDayFactory();
    }

  }

  private static Map buildRevisionToDateMap(List coordinates, TimeAxisKeyFactory factory) {
    Map revisionToDateMap = new HashMap<>(coordinates.size());
    for (CommitCoordinate commitCoordinate : coordinates) {
      Date date = commitCoordinate.getDate();
      long revision = commitCoordinate.getRevision();
      TimeAxisKey timeAxisKey = factory.fromDate(date);
      revisionToDateMap.put(revision, timeAxisKey);
    }
    return revisionToDateMap;
  }

  private static List getCommitCoordinates(SVNClientManager clientManager, DiffStatConfiguration configuration, ProgressReporter reporter) throws SVNException {
    boolean stopOnCopy = true;
    boolean discoverChangedPaths = false;
    SVNRevision startRevision = SVNRevision.create(1L);
    SVNRevision endRevision = SVNRevision.HEAD;

    File[] paths = new File[]{configuration.getWorkingCopy()};
    RevisionCollector logHandler = new RevisionCollector(configuration, reporter);
    long limit = Long.MAX_VALUE;
    clientManager.getLogClient().doLog(paths, startRevision, endRevision, stopOnCopy, discoverChangedPaths,
        limit, logHandler);
    return logHandler.getCoordinates();
  }

  private static Map getDiffStats(SVNClientManager clientManager, List coordinates, DiffStatConfiguration configuration, ProgressReporter reporter) throws SVNException {
    SVNDiffClient diffClient = clientManager.getDiffClient();

    ResetOutputStream result = new ResetOutputStream();
    DiffStatDiffGenerator diffGenerator = new DiffStatDiffGenerator(diffClient.getDiffGenerator(),
        configuration, reporter, result);
    diffClient.setDiffGenerator(diffGenerator);
    File workingCopy = configuration.getWorkingCopy();
    for (CommitCoordinate coordinate : coordinates) {
      long revision = coordinate.getRevision();
      SVNRevision newRevision = SVNRevision.create(revision);
      SVNRevision oldRevision = SVNRevision.create(revision - 1L);
      doDiff(diffClient, result, workingCopy, newRevision, oldRevision);
    }

    return diffGenerator.getDiffStats();
  }


  private static void doDiff(SVNDiffClient diffClient, ResetOutputStream result, File workingCopy, SVNRevision newRevision, SVNRevision oldRevision) throws SVNException {
    SVNDepth depth = SVNDepth.INFINITY;
    boolean useAncestry = true;
    //diffClient.setGitDiffFormat(true);
    Collection changeLists = null;
    diffClient.doDiff(workingCopy, oldRevision, workingCopy, newRevision, depth, useAncestry, result, changeLists);
  }
  

  static String getExtension(String path) {
    int lastIndex = lastIndexOf('.', path);
    if (lastIndex == -1 || lastIndex == path.length() - 1) {
      return null;
    }
    return path.substring(lastIndex + 1, path.length());
  }
  

  private static int lastIndexOf(char c, String s) {
    int lastIndex = s.indexOf(c);
    if (lastIndex == -1) {
      return lastIndex;
    }
    while (true) {
      int nextIndex = s.indexOf(c, lastIndex + 1);
      if (nextIndex == -1) {
        return lastIndex;
      }
      lastIndex = nextIndex;
    }

  }


  static final class RevisionCollector implements ISVNLogEntryHandler {

    private final Set authors;
    private final List coordinates;
    private final ProgressReporter reporter;

    RevisionCollector(DiffStatConfiguration configuration, ProgressReporter reporter) {
      this.authors = configuration.getAuthors();
      this.reporter = reporter;
      this.coordinates = new ArrayList<>();
    }


    @Override
    public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
      long revision = logEntry.getRevision();
      String logEntryAuthor = logEntry.getAuthor();
      if (this.authors.contains(logEntryAuthor)) {
        Date date = logEntry.getDate();
        CommitCoordinate coordinate = new CommitCoordinate(revision, date);
        this.coordinates.add(coordinate);
      }
      this.reporter.revisionLogged(revision);
    }

    List getCoordinates() {
      return this.coordinates;
    }

  }

  static final class DiffStatDiffGenerator implements ISVNDiffGenerator {

    private final ISVNDiffGenerator delegate;
    private final Map diffStats;
    private final Set includedFileExtensions;
    private final ProgressReporter reporter;
    private final ResetOutputStream output;

    DiffStatDiffGenerator(ISVNDiffGenerator delegate, DiffStatConfiguration configuration, ProgressReporter reporter, ResetOutputStream output) {
      this.delegate = delegate;
      this.includedFileExtensions = configuration.getIncludedFiles();
      this.reporter = reporter;
      this.output = output;
      this.diffStats = new HashMap<>();
    }

    Map getDiffStats() {
      return this.diffStats;
    }

    void clearDiffStats() {
      this.diffStats.clear();
    }

    @Override
    public void init(String anchorPath1, String anchorPath2) {
      this.delegate.init(anchorPath1, anchorPath2);
    }

    @Override
    public void setBasePath(File basePath) {
      this.delegate.setBasePath(basePath);
    }

    @Override
    public void setForcedBinaryDiff(boolean forced) {
      this.delegate.setForcedBinaryDiff(forced);
    }

    @Override
    public boolean isForcedBinaryDiff() {
      return this.delegate.isForcedBinaryDiff();
    }

    @Override
    public void setEncoding(String encoding) {
      this.delegate.setEncoding(encoding);
      this.output.setEncoding(encoding);
    }

    @Override
    public String getEncoding() {
      return this.delegate.getEncoding();
    }

    @Override
    public void setEOL(byte[] eol) {
      this.delegate.setEOL(eol);
      this.output.setEOL(eol);
    }

    @Override
    public byte[] getEOL() {
      return this.delegate.getEOL();
    }

    @Override
    public void setDiffDeleted(boolean isDiffDeleted) {
      this.delegate.setDiffDeleted(isDiffDeleted);
    }

    @Override
    public boolean isDiffDeleted() {
      return this.delegate.isDiffDeleted();
    }

    @Override
    public void setDiffAdded(boolean isDiffAdded) {
      this.delegate.setDiffAdded(isDiffAdded);
    }

    @Override
    public boolean isDiffAdded() {
      return this.delegate.isDiffAdded();
    }

    @Override
    public void setDiffCopied(boolean isDiffCopied) {
      this.delegate.setDiffCopied(isDiffCopied);
    }

    @Override
    public boolean isDiffCopied() {
      return this.delegate.isDiffCopied();
    }

    @Override
    public void setDiffUnversioned(boolean diffUnversioned) {
      this.delegate.setDiffUnversioned(diffUnversioned);

    }

    @Override
    public boolean isDiffUnversioned() {
      return this.delegate.isDiffUnversioned();
    }

    @Override
    public File createTempDirectory() throws SVNException {
      return this.delegate.createTempDirectory();
    }

    @Override
    public void displayPropDiff(String path, SVNProperties baseProps, SVNProperties diff, OutputStream result) throws SVNException {
    }

    @Override
    public void displayFileDiff(String path, File file1, File file2, String rev1, String rev2, String mimeType1, String mimeType2, OutputStream result) throws SVNException {
      long newRevision = Long.parseLong(rev2.substring("(revision ".length(), rev2.length() - 1));
      if (this.considerFile(path)) {
        DiffStat diffStat;
        if (file1 == null) {
          int added = this.countLines(file2);
          diffStat = new DiffStat(added, 0);
        } else if (file2 == null ) {
          int removed = this.countLines(file1);
          diffStat = new DiffStat(0, removed);
        } else {
          diffStat = this.parseNormal(path, file1, file2, rev1, rev2, mimeType1, mimeType2, result, newRevision);
        }
        this.addDiffStat(newRevision, diffStat);
      }
      this.reporter.revisionParsed(newRevision);
    }
    
    private int countLines(File file) throws SVNException {
      try (InputStream fileInput = new FileInputStream(file);
          BufferedReader reader = new BufferedReader(new InputStreamReader(fileInput))) {
        int count = 0;
        while (reader.readLine() != null) {
          count += 1;
        }
        return count;
      } catch (IOException e) {
        SVNErrorCode errorCode = SVNErrorCode.IO_ERROR;
        SVNErrorMessage message = SVNErrorMessage.create(errorCode, "could not count lines");
        throw new SVNException(message, e);
      }
    }

    private DiffStat parseNormal(String path, File file1, File file2, String rev1, String rev2, String mimeType1, String mimeType2, OutputStream result, long newRevision) throws SVNException {
      ResetOutputStream resetOutStream = (ResetOutputStream) result;
      resetOutStream.initialize();
      this.delegate.displayFileDiff(path, file1, file2, rev1, rev2, mimeType1, mimeType2, result);
      return resetOutStream.finish();
    }

    private void addDiffStat(Long revision, DiffStat diffStat) {
      DiffStat oldStat = this.diffStats.get(revision);
      if (oldStat != null) {
        oldStat.add(diffStat);
      } else {
        this.diffStats.put(revision, diffStat);
      }
    }

    private boolean considerFile(String path) {
      if (path == null || path.isEmpty()) {
        return false;
      }
      String extension = getExtension(path);
      return extension != null && this.includedFileExtensions.contains(extension);
    }

    @Override
    public void displayDeletedDirectory(String path, String rev1, String rev2) throws SVNException {
    }

    @Override
    public void displayAddedDirectory(String path, String rev1, String rev2) throws SVNException {
    }


  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy