/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library 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 2.1 of the License, or (at your option)
* any later version.
*
* This library 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.
*/
package com.liferay.gradle.plugins.change.log.builder;
import com.liferay.gradle.plugins.change.log.builder.util.GitUtil;
import com.liferay.gradle.plugins.change.log.builder.util.NaturalOrderStringComparator;
import com.liferay.gradle.util.GradleUtil;
import com.liferay.gradle.util.Validator;
import java.io.BufferedWriter;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.StopExecutionException;
import org.gradle.api.tasks.TaskAction;
import org.gradle.util.GUtil;
/**
* @author Andrea Di Giorgi
*/
public class BuildChangeLogTask extends DefaultTask {
public BuildChangeLogTask() {
Project project = getProject();
setGitDir(project.getRootDir());
setTicketIdPrefixes("CLDSVCS", "LPS", "SOS", "SYNC");
}
@TaskAction
public void buildChangeLog() throws Exception {
Project project = getProject();
File changeLogFile = getChangeLogFile();
Path changeLogPath = changeLogFile.toPath();
String changeLogContent = null;
if (changeLogFile.exists()) {
changeLogContent = new String(
Files.readAllBytes(changeLogPath), StandardCharsets.UTF_8);
}
String rangeEnd = getRangeEnd();
String rangeStart = getRangeStart();
Set<String> ticketIds;
try (Repository repository = GitUtil.openRepository(getGitDir())) {
if (Validator.isNull(rangeEnd)) {
rangeEnd = GitUtil.getHashHead(repository);
}
if (Validator.isNull(rangeStart)) {
rangeStart = getRangeStart(changeLogContent, repository);
}
ticketIds = getTicketIds(rangeStart, rangeEnd, repository);
}
String range = rangeStart + ".." + rangeEnd;
if (ticketIds.isEmpty()) {
throw new StopExecutionException(
project + " does not have changes for range " + range);
}
File changeLogDir = changeLogFile.getParentFile();
changeLogDir.mkdirs();
try (BufferedWriter bufferedWriter = Files.newBufferedWriter(
changeLogPath, StandardCharsets.UTF_8,
StandardOpenOption.APPEND, StandardOpenOption.CREATE)) {
if (Validator.isNotNull(changeLogContent)) {
bufferedWriter.newLine();
bufferedWriter.newLine();
}
bufferedWriter.append('#');
bufferedWriter.newLine();
bufferedWriter.append("# ");
bufferedWriter.append(getChangeLogHeader());
bufferedWriter.newLine();
bufferedWriter.append('#');
bufferedWriter.newLine();
bufferedWriter.append(range);
bufferedWriter.append('=');
boolean firstTicket = true;
for (String ticketId : ticketIds) {
if (firstTicket) {
firstTicket = false;
}
else {
bufferedWriter.append(' ');
}
bufferedWriter.append(ticketId);
}
}
}
public BuildChangeLogTask dirs(Iterable<?> dirs) {
GUtil.addToCollection(_dirs, dirs);
return this;
}
public BuildChangeLogTask dirs(Object... dirs) {
return dirs(Arrays.asList(dirs));
}
@Input
public File getChangeLogFile() {
return GradleUtil.toFile(getProject(), _changeLogFile);
}
@Input
public String getChangeLogHeader() {
return GradleUtil.toString(_changeLogHeader);
}
@Input
public Iterable<File> getDirs() {
Project project = getProject();
return project.files(_dirs);
}
@Input
public File getGitDir() {
return GradleUtil.toFile(getProject(), _gitDir);
}
@Input
@Optional
public String getRangeEnd() {
return GradleUtil.toString(_rangeEnd);
}
@Input
@Optional
public String getRangeStart() {
return GradleUtil.toString(_rangeStart);
}
@Input
public Set<String> getTicketIdPrefixes() {
return _ticketIdPrefixes;
}
public void setChangeLogFile(Object changeLogFile) {
_changeLogFile = changeLogFile;
}
public void setChangeLogHeader(Object changeLogHeader) {
_changeLogHeader = changeLogHeader;
}
public void setDirs(Iterable<?> dirs) {
_dirs.clear();
dirs(dirs);
}
public void setDirs(Object... dirs) {
setDirs(Arrays.asList(dirs));
}
public void setGitDir(Object gitDir) {
_gitDir = gitDir;
}
public void setRangeEnd(Object rangeEnd) {
_rangeEnd = rangeEnd;
}
public void setRangeStart(Object rangeStart) {
_rangeStart = rangeStart;
}
public void setTicketIdPrefixes(Iterable<String> ticketIdPrefixes) {
_ticketIdPrefixes.clear();
ticketIdPrefixes(ticketIdPrefixes);
}
public void setTicketIdPrefixes(String... ticketIdPrefixes) {
setTicketIdPrefixes(Arrays.asList(ticketIdPrefixes));
}
public BuildChangeLogTask ticketIdPrefixes(
Iterable<String> ticketIdPrefixes) {
GUtil.addToCollection(_ticketIdPrefixes, ticketIdPrefixes);
return this;
}
public BuildChangeLogTask ticketIdPrefixes(String... ticketIdPrefixes) {
return ticketIdPrefixes(Arrays.asList(ticketIdPrefixes));
}
protected String getRangeStart(
String changeLogContent, Repository repository)
throws Exception {
String rangeStart;
if (Validator.isNotNull(changeLogContent)) {
Matcher matcher = _lastRangeEndPattern.matcher(changeLogContent);
if (!matcher.find()) {
throw new GradleException("Unable to find the range start");
}
rangeStart = matcher.group(1);
}
else {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, -2);
rangeStart = GitUtil.getHashBefore(calendar.getTime(), repository);
}
return rangeStart + "^";
}
protected Set<String> getTicketIds(
String rangeStart, String rangeEnd, Repository repository)
throws Exception {
Set<String> ticketIds = new TreeSet<>(
new NaturalOrderStringComparator());
Set<String> ticketIdPrefixes = getTicketIdPrefixes();
Iterable<RevCommit> revCommits = GitUtil.getCommits(
getDirs(), rangeStart, rangeEnd, repository);
for (RevCommit revCommit : revCommits) {
String message = revCommit.getShortMessage();
int index = message.indexOf('-');
if (index == -1) {
continue;
}
String prefix = message.substring(0, index);
if (!ticketIdPrefixes.contains(prefix)) {
continue;
}
index = message.indexOf(' ');
if (index == -1) {
index = message.length();
}
ticketIds.add(message.substring(0, index));
}
return ticketIds;
}
private static final Pattern _lastRangeEndPattern = Pattern.compile(
"\\.\\.([0-9a-f]{40})=.*$");
private Object _changeLogFile;
private Object _changeLogHeader;
private final Set<Object> _dirs = new HashSet<>();
private Object _gitDir;
private Object _rangeEnd;
private Object _rangeStart;
private final Set<String> _ticketIdPrefixes = new HashSet<>();
}