koraktor/mavanagaiata

View on GitHub
src/main/java/com/github/koraktor/mavanagaiata/mojo/ChangelogMojo.java

Summary

Maintainability
A
1 hr
Test Coverage
A
93%
/*
 * This code is free software; you can redistribute it and/or modify it under
 * the terms of the new BSD License.
 *
 * Copyright (c) 2011-2019, Sebastian Staudt
 *               2016, Jeff Kreska
 */

package com.github.koraktor.mavanagaiata.mojo;

import java.io.File;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

import com.github.koraktor.mavanagaiata.git.CommitWalkAction;
import com.github.koraktor.mavanagaiata.git.GitRepository;
import com.github.koraktor.mavanagaiata.git.GitRepositoryException;
import com.github.koraktor.mavanagaiata.git.GitTag;

import static org.apache.commons.lang3.StringUtils.*;

/**
 * This goal allows to generate a changelog of the currently checked out branch
 * of the Git repository. It will use information from tags and commit messages
 * to build a reverse chronological summary of the development. It can be
 * configured to display the changelog or save it to a file.
 *
 * @author Sebastian Staudt
 * @since 0.2.0
 */
@Mojo(name = "changelog",
      defaultPhase = LifecyclePhase.PROCESS_RESOURCES,
      threadSafe = true)
public class ChangelogMojo extends AbstractGitOutputMojo {

    /**
     * Pre-defined base URLs used for links
     */
    enum LinkToBaseUrl {
        GITHUB("https://github.com/%s/%s"),
        GITLAB("https://gitlab.com/%s/%s");

        private final String baseUrl;

        LinkToBaseUrl(String baseUrl) {
            this.baseUrl = baseUrl;
        }
    }

    /**
     * The format to use while generating the changelog
     *
     * @see #formatTemplate
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.format")
    protected ChangelogFormat format;

    /**
     * The formatting template to use while generating the changelog
     * <p>
     * This may be one of {@code DEFAULT} or {@code MARKDOWN}.
     * <p>
     * Individual attributes may be overridden using {@link #format}.
     *
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.formatTemplate",
               defaultValue = "DEFAULT")
    ChangelogFormat.Formats formatTemplate;

    /**
     * The project name for GitHub links
     *
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.linkToProject")
    String linkToProject;

    /**
     * The user name for GitHub links
     *
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.linkToUser")
    String linkToUser;

    /**
     * Used to select the service to create links to
     * <p>
     * {@code GITHUB} and {@code GITLAB} are available.
     *
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.linkTo",
               defaultValue = "GITHUB")
    LinkToBaseUrl linkTo;

    /**
     * Can be used to override the pre-defined URLs from {@link #linkTo} with
     * a customized URL
     *
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.linkToBaseUrl")
    String linkToBaseUrl;

    /**
     * The file to write the changelog to
     *
     * @since 0.4.1
     */
    @Parameter(property = "mavanagaiata.changelog.outputFile")
    protected File outputFile;

    /**
     * Whether to skip merge commits’ messages
     *
     * @since 0.9.0
     */
    @Parameter(property = "mavanagaiata.changelog.skipMergeCommits",
               defaultValue = "true")
    boolean skipMergeCommits;

    /**
     * Whether to skip tagged commits' messages
     * <br>
     * This is useful when usually tagging commits like "Version bump to X.Y.Z"
     */
    @Parameter(property = "mavanagaiata.changelog.skipTagged",
               defaultValue = "false")
    boolean skipTagged;

    /**
     * Whether to skip commits that match the given regular expression
     *
     * @since 0.8.0
     */
    @Parameter(property = "mavanagaiata.changelog.skipCommitsMatching")
    String skipCommitsMatching;

    private Pattern skipCommitsPattern;

    /**
     * Walks through the history of the currently checked out branch of the
     * Git repository and builds a changelog from the commits contained in that
     * branch.
     *
     * @throws MavanagaiataMojoException if retrieving information from the Git
     *         repository fails
     */
    @Override
    protected void writeOutput(GitRepository repository)
            throws MavanagaiataMojoException {
        try {
            format.printStream = printStream;
            format.printHeader();

            ChangelogWalkAction action = new ChangelogWalkAction(repository);
            action.currentRef = repository.getBranch();
            repository.walkCommits(action);

            format.printSeparator();
            format.printCompareLink(action.currentRef, null, action.currentRef.equals(repository.getBranch()));
        } catch (GitRepositoryException e) {
            throw MavanagaiataMojoException.create("Unable to generate changelog from Git", e);
        }
    }

    /**
     * Returns the output file for the generated changelog
     *
     * @return The output file for the generated changelog
     */
    @Override
    public File getOutputFile() {
        return outputFile;
    }

    @Override
    protected void initConfiguration() {
        super.initConfiguration();

        format = formatTemplate.getFormat().apply(format);
        if (format.dateFormat == null) {
            format.dateFormat = dateFormat;
        }
        format.prepare();

        if (isNotBlank(linkToUser) && isNotBlank(linkToProject)) {
            String baseUrl = isNotBlank(linkToBaseUrl) ? linkToBaseUrl : linkTo.baseUrl;
            format.enableCreateLinks(String.format(baseUrl, linkToUser, linkToProject));
        }

        if (skipCommitsMatching != null && !skipCommitsMatching.isEmpty()) {
            skipCommitsPattern = Pattern.compile(skipCommitsMatching, Pattern.MULTILINE);
        }
    }

    /**
     * Sets the output file for the generated changelog
     *
     * @param outputFile The output file for the generated changelog
     */
    public void setOutputFile(File outputFile) {
        this.outputFile = outputFile;
    }

    class ChangelogWalkAction extends CommitWalkAction {

        private String currentRef;
        private boolean firstCommit = true;
        private GitRepository repository;
        private Map<String, GitTag> tags;

        ChangelogWalkAction(GitRepository repository) throws GitRepositoryException {
            this.repository = repository;
            tags = repository.getTags();
        }

        protected void run() throws GitRepositoryException {
            if (skipCommitsPattern != null && skipCommitsPattern.matcher(currentCommit.getMessage()).find()) {
                return;
            }

            if (skipMergeCommits && currentCommit.isMergeCommit()) {
                return;
            }

            boolean firstLine = firstCommit;
            firstCommit = false;

            if (tags.containsKey(currentCommit.getId())) {
                String lastRef = currentRef;
                GitTag currentTag = tags.get(currentCommit.getId());
                currentRef = currentTag.getName();

                format.printSeparator();

                if (!firstLine) {
                    format.printCompareLink(currentRef, lastRef, lastRef.equals(repository.getBranch()));
                }

                repository.loadTag(currentTag);
                format.printTag(currentTag);

                if (skipTagged) {
                    return;
                }
            } else if (firstLine) {
                format.printBranch(repository.getBranch());
            }

            format.printCommit(currentCommit);
        }

    }

}