org.impressivecode.depress.scm.git.GitOnlineLogParser.java Source code

Java tutorial

Introduction

Here is the source code for org.impressivecode.depress.scm.git.GitOnlineLogParser.java

Source

/*
ImpressiveCode Depress Framework
Copyright (C) 2013  ImpressiveCode contributors
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.impressivecode.depress.scm.git;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;

import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.LogCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRemoteException;
import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.io.DisabledOutputStream;
import org.impressivecode.depress.scm.SCMExtensionsParser;

/**
 * 
 * 
 * @author Tomasz Kuzemko
 * @author Sawomir Kaposki
 */
public class GitOnlineLogParser {

    public List<GitCommit> parseEntries(final String path, final GitParserOptions gitParserOptions)
            throws IOException, ParseException, NoHeadException, GitAPIException {
        checkArgument(!isNullOrEmpty(path), "Path has to be set.");

        List<GitCommit> commitsList = processRepo(path, gitParserOptions);

        return commitsList;
    }

    public static String getCurrentBranch(final String path) throws IOException, NoHeadException {
        Git git = initializeGit(path);
        return git.getRepository().getBranch();
    }

    public static List<String> getBranches(final String path) throws IOException, GitAPIException {
        Git git = initializeGit(path);

        List<Ref> refs = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
        List<String> branches = new ArrayList<String>();
        for (Ref r : refs) {
            branches.add(r.getName().replace("refs/heads/", "").replace("refs/remotes/", ""));
        }

        return branches;
    }

    public static void cloneRepository(final String remoteAddress, final String localPath,
            final ProgressMonitor monitor) throws InvalidRemoteException, TransportException, GitAPIException {
        CloneCommand clone = Git.cloneRepository();
        clone.setBare(false);
        clone.setCloneAllBranches(true);
        clone.setDirectory(new File(localPath));
        clone.setURI(remoteAddress);
        clone.setProgressMonitor(monitor);
        clone.call();
    }

    private static Git initializeGit(final String path) throws IOException, NoHeadException {
        RepositoryBuilder gitRepoBuilder = new RepositoryBuilder();
        Repository gitRepo = gitRepoBuilder.setGitDir(new File(path)).readEnvironment().findGitDir().build();
        Git git = new Git(gitRepo);

        // Make sure path contains a git repository.
        if (!gitRepo.getObjectDatabase().exists()) {
            throw new NoHeadException("Directory " + path + " does not look like a git repository.");
        }

        return git;
    }

    private List<GitCommit> processRepo(final String path, final GitParserOptions gitParserOptions)
            throws IOException, NoHeadException, GitAPIException {
        Git git = initializeGit(path);
        List<GitCommit> analyzedCommits = new ArrayList<GitCommit>();

        LogCommand log = git.log();

        if (gitParserOptions.hasBranch()) {
            List<Ref> branches = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
            Ref branch = null;
            for (Ref b : branches) {
                if (b.getName().equals("refs/heads/" + gitParserOptions.getBranch())
                        || b.getName().equals("refs/remotes/" + gitParserOptions.getBranch())) {
                    branch = b;
                    break;
                }
            }
            if (branch == null) {
                throw new IOException("Specified branch was not found in git repository");
            }
            log.add(branch.getObjectId());
        }

        Iterable<RevCommit> loglines = log.call();
        Iterator<RevCommit> logIterator = loglines.iterator();
        while (logIterator.hasNext()) {
            RevCommit commit = logIterator.next();
            GitcommitProcessor proc = new GitcommitProcessor(gitParserOptions, git.getRepository());
            proc.setRevCommit(commit);
            proc.processCommitData();
            analyzedCommits.add(proc.getResult());
        }
        return analyzedCommits;
    }

    class GitcommitProcessor {

        private final Pattern PATTERN = Pattern.compile("^(.*) [a-f0-9]{40} (A|C|D|M|R|T)");

        private final GitParserOptions options;
        private final GitCommit analyzedCommit = new GitCommit();
        private final Repository repository;
        private RevCommit revCommit;

        public GitcommitProcessor(final GitParserOptions options, final Repository repository) {
            this.options = options;
            this.repository = repository;
        }

        public void setRevCommit(final RevCommit revcommit) {
            this.revCommit = revcommit;
        }

        public GitCommit getResult() {
            return this.analyzedCommit;
        }

        private void processCommitData() throws IOException {
            hash();
            author();
            date();
            message();
            files();
        }

        private void files() throws IOException {
            List<String> filesList = getFilesInCommit(this.repository, this.revCommit);
            for (String fileLine : filesList) {
                Matcher matcher = PATTERN.matcher(fileLine);
                if (matcher.matches()) {
                    parsePath(matcher);
                }
            }
        }

        private void message() {
            String message = this.revCommit.getFullMessage();
            if (message.endsWith("\n")) {
                message = message.substring(0, message.length() - 1);
            }
            this.analyzedCommit.addToMessage(message);
        }

        private void author() {
            this.analyzedCommit.setAuthor(this.revCommit.getAuthorIdent().getName());
        }

        private void date() {
            this.analyzedCommit.setDate(this.revCommit.getAuthorIdent().getWhen());
        }

        private void hash() {
            String[] commitId = this.revCommit.getId().toString().split(" ");
            this.analyzedCommit.setId(commitId[1]);
        }

        private void parsePath(final Matcher matcher) {
            String operationCode = matcher.group(2);
            String origin = matcher.group(1);
            String transformed = origin.replaceAll("/", ".");

            String parseJavaClass = "";
            if (SCMExtensionsParser.extensionFits(transformed, Arrays.asList("*"))) {
                if (transformed.endsWith(".java")) {
                    if (packagePrefixValidate(transformed)) {
                        parseJavaClass = parseJavaClass(transformed);
                    }
                }
                GitCommitFile gitFile = new GitCommitFile();
                gitFile.setRawOperation(operationCode.charAt(0));
                gitFile.setPath(origin);
                gitFile.setExtension(FilenameUtils.getExtension(transformed));
                gitFile.setJavaClass(parseJavaClass);
                this.analyzedCommit.getFiles().add(gitFile);
            }
        }

        private boolean packagePrefixValidate(final String path) {
            if (options.hasPackagePrefix()) {
                return path.indexOf(options.getPackagePrefix()) != -1;
            }
            return false;
        }

        private String parseJavaClass(final String path) {
            String javaClass = path.replace(".java", "");
            if (options.hasPackagePrefix()) {
                javaClass = javaClass.substring(javaClass.indexOf(options.getPackagePrefix()));
            }
            return javaClass;
        }
    }

    //modified method from https://github.com/gitblit/gitblit/blob/master/src/main/java/com/gitblit/utils/JGitUtils.java#L718
    private List<String> getFilesInCommit(final Repository repository, final RevCommit commit) throws IOException {
        RevWalk rw = new RevWalk(repository);
        List<String> filesList = new ArrayList<String>();
        String fileLine = "";

        if (commit.getParentCount() > 1) {
            // Merge commit
        } else if (commit.getParentCount() == 0) {
            TreeWalk tw = new TreeWalk(repository);
            tw.reset();
            tw.setRecursive(true);
            tw.addTree(commit.getTree());
            while (tw.next()) {
                fileLine = tw.getPathString() + " " + tw.getObjectId(0).getName() + " "
                        + this.setOperationSymbol("ADD");
            }
            tw.release();
        } else {
            RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
            DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE);
            df.setRepository(repository);
            df.setDiffComparator(RawTextComparator.DEFAULT);
            df.setDetectRenames(true);
            List<DiffEntry> diffs = df.scan(parent.getTree(), commit.getTree());
            for (DiffEntry diff : diffs) {
                String objectId = diff.getNewId().name();
                if (diff.getChangeType().equals(ChangeType.DELETE)) {
                    fileLine = diff.getOldPath() + " " + objectId + " "
                            + this.setOperationSymbol(diff.getChangeType().name());
                    filesList.add(fileLine);
                } else if (diff.getChangeType().equals(ChangeType.RENAME)) {
                    //in git log there is two operations for RENAME: DELETE old file and ADD new so we need to add also two files to log:
                    fileLine = diff.getOldPath() + " " + objectId + " "
                            + this.setOperationSymbol(ChangeType.DELETE.name());
                    filesList.add(fileLine);
                    fileLine = diff.getNewPath() + " " + objectId + " "
                            + this.setOperationSymbol(ChangeType.ADD.name());
                    filesList.add(fileLine);
                } else {
                    fileLine = diff.getNewPath() + " " + objectId + " "
                            + this.setOperationSymbol(diff.getChangeType().name());
                    filesList.add(fileLine);
                }
            }
        }
        return filesList;
    }

    private char setOperationSymbol(final String operationCode) {
        char op;
        switch (operationCode) {
        case "MODIFY":
            op = 'M';
            break;
        case "ADD":
            op = 'A';
            break;
        case "COPY":
            op = 'C';
            break;
        case "DELETE":
            op = 'D';
            break;
        case "RENAME":
            op = 'R';
            break;
        default:
            op = 'O';
            break;
        }
        return op;
    }

}