Skip to content

Commit

Permalink
[SCM-914] Introduce properly typed last modified date
Browse files Browse the repository at this point in the history
Populate it with svnexe, gitexe and JGit providers
  • Loading branch information
kwin committed Feb 12, 2024
1 parent f5d8bb4 commit ca278a9
Show file tree
Hide file tree
Showing 8 changed files with 208 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public class CommandParameter implements Serializable {
public static final CommandParameter SCM_MKDIR_CREATE_IN_LOCAL = new CommandParameter("createInLocal");

/**
* Parameter used only for Git SCM and simulate the <code>git rev-parse --short=lenght</code> command.
* Parameter used only for Git SCM to truncate the emitted hash to the given character length, simulates <code>git rev-parse --short=length</code> command.
*
* @since 1.7
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
*/
package org.apache.maven.scm.command.info;

import java.time.OffsetDateTime;
import java.time.temporal.TemporalAccessor;

/**
* Encapsulates meta information about one file (or directory) being managed with an SCM.
*
* For historical reasons the field/method names are inspired from (and sometimes only applicable to) the <a href="https://svnbook.red-bean.com/">Subversion SCM</a>.
*
* @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
* @author Olivier Lamy
*
Expand All @@ -45,6 +52,8 @@ public class InfoItem {

private String lastChangedDate;

private OffsetDateTime lastChangedDateTime;

public String getPath() {
return path;
}
Expand Down Expand Up @@ -117,11 +126,31 @@ public void setLastChangedRevision(String lastChangedRevision) {
this.lastChangedRevision = lastChangedRevision;
}

/**
* @deprecated Use {@link #getLastModifiedDate()} instead
*/
@Deprecated
public String getLastChangedDate() {
return lastChangedDate;
}

/**
* @deprecated Use {@link #setLastModifiedDate(TemporalAccessor)} instead
*/
@Deprecated
public void setLastChangedDate(String lastChangedDate) {
this.lastChangedDate = lastChangedDate;
}

/**
*
* @return the date when the file indicated via {@link #getPath()} has been changed in the SCM for the last time
*/
OffsetDateTime getLastModifiedDate() {
return lastChangedDateTime;
}

public void setLastModifiedDate(TemporalAccessor accessor) {
this.lastChangedDateTime = OffsetDateTime.from(accessor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@
*/
package org.apache.maven.scm.provider.git.gitexe.command.info;

import java.io.File;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.apache.maven.scm.CommandParameter;
import org.apache.maven.scm.CommandParameters;
import org.apache.maven.scm.ScmException;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmResult;
import org.apache.maven.scm.command.AbstractCommand;
import org.apache.maven.scm.command.info.InfoItem;
import org.apache.maven.scm.command.info.InfoScmResult;
import org.apache.maven.scm.provider.ScmProviderRepository;
import org.apache.maven.scm.provider.git.command.GitCommand;
Expand All @@ -32,6 +38,7 @@
import org.codehaus.plexus.util.cli.Commandline;

/**
* Uses {@code git log} command to retrieve info about the most recent commits related to specific files.
* @author Olivier Lamy
* @since 1.5
*/
Expand All @@ -43,31 +50,37 @@ public class GitInfoCommand extends AbstractCommand implements GitCommand {
protected ScmResult executeCommand(
ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException {

GitInfoConsumer consumer = new GitInfoConsumer(fileSet);
CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();

Commandline cli = createCommandLine(repository, fileSet, parameters);
Commandline baseCli = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "log");
baseCli.createArg().setValue("-1"); // only most recent commit matters
baseCli.addArg(GitInfoConsumer.getFormatArgument());

int exitCode = GitCommandLineUtils.execute(cli, consumer, stderr);
if (exitCode != 0) {
return new InfoScmResult(cli.toString(), "The git rev-parse command failed.", stderr.getOutput(), false);
List<InfoItem> infoItems = new LinkedList<>();
// iterate over files
for (File scmFile : getScmFilesList(fileSet)) {
Commandline cliClone = (Commandline) baseCli.clone();
cliClone.createArg().setFile(scmFile);
GitInfoConsumer consumer = new GitInfoConsumer(scmFile.toPath(), getRevisionLength(parameters));
CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
int exitCode = GitCommandLineUtils.execute(cliClone, consumer, stderr);
if (exitCode != 0) {
return new InfoScmResult(cliClone.toString(), "The git log command failed.", stderr.getOutput(), false);
}
infoItems.add(consumer.getInfoItem());
}
return new InfoScmResult(cli.toString(), consumer.getInfoItems());
return new InfoScmResult("", infoItems);
}

public static Commandline createCommandLine(
ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException {
Commandline cli = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "rev-parse");
cli.createArg().setValue("--verify");
final int revLength = getRevisionLength(parameters);
if (revLength > NO_REVISION_LENGTH) // set the --short key only if revision length parameter is passed and
// different from -1
{
cli.createArg().setValue("--short=" + revLength);
/**
*
* @param scmFileSet
* @return the normalized SCM file list (if no explicit files given, returns just the base directory)
*/
public static List<File> getScmFilesList(ScmFileSet scmFileSet) {
if (scmFileSet.getFileList().isEmpty()) {
return Collections.singletonList(scmFileSet.getBasedir());
} else {
return scmFileSet.getFileList();
}
cli.createArg().setValue("HEAD");

return cli;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,50 +18,90 @@
*/
package org.apache.maven.scm.provider.git.gitexe.command.info;

import java.util.ArrayList;
import java.util.List;
import java.nio.file.Path;
import java.time.format.DateTimeFormatter;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.command.info.InfoItem;
import org.apache.maven.scm.util.AbstractConsumer;
import org.codehaus.plexus.util.cli.Arg;
import org.codehaus.plexus.util.cli.Commandline;

/**
* Parses output of {@code git log} with a particular format and populates a {@link InfoItem}.
*
* @author Olivier Lamy
* @since 1.5
* @see <a href="https://git-scm.com/docs/git-log#_pretty_formats">Pretty Formats
*/
public class GitInfoConsumer extends AbstractConsumer {

// $ git show
// commit cd3c0dfacb65955e6fbb35c56cc5b1bf8ce4f767
private final InfoItem infoItem;
private final int revisionLength;

public GitInfoConsumer(Path path, int revisionLength) {
infoItem = new InfoItem();
infoItem.setPath(path.toString());
infoItem.setURL(path.toUri().toASCIIString());
this.revisionLength = revisionLength;
}

enum LineParts {
HASH(0),
AUTHOR_NAME(3),
AUTHOR_EMAIL(2),
AUTHOR_LAST_MODIFIED(1);

private final List<InfoItem> infoItems = new ArrayList<>(1);
private final int index;

private final ScmFileSet scmFileSet;
LineParts(int index) {
this.index = index;
}

public GitInfoConsumer(ScmFileSet scmFileSet) {
this.scmFileSet = scmFileSet;
public int getIndex() {
return index;
}
}

/**
* @param line the line which is supposed to have the format as specified by {@link #getFormatArgument()}.
* @see org.codehaus.plexus.util.cli.StreamConsumer#consumeLine(java.lang.String)
*/
public void consumeLine(String line) {
if (logger.isDebugEnabled()) {
logger.debug("consume line " + line);
}

if (infoItems.isEmpty()) {
if (!(line == null || line.isEmpty())) {
InfoItem infoItem = new InfoItem();
infoItem.setRevision(StringUtils.trim(line));
infoItem.setURL(scmFileSet.getBasedir().toPath().toUri().toASCIIString());
infoItems.add(infoItem);
}
// name must be last token as it may contain separators
String[] parts = line.split("\\s", 4);
if (parts.length != 4) {
throw new IllegalArgumentException(
"Unexpected line: expecting 4 tokens separated by whitespace but got " + line);
}
infoItem.setLastChangedAuthor(
parts[LineParts.AUTHOR_NAME.getIndex()] + " <" + parts[LineParts.AUTHOR_EMAIL.getIndex()] + ">");
String revision = parts[LineParts.HASH.getIndex()];
if (revisionLength > -1) {
// do not truncate below 4 characters
revision = StringUtils.truncate(revision, Integer.max(4, revisionLength));
}
infoItem.setRevision(revision);
infoItem.setLastModifiedDate(
DateTimeFormatter.ISO_OFFSET_DATE_TIME.parse(parts[LineParts.AUTHOR_LAST_MODIFIED.getIndex()]));
}

public InfoItem getInfoItem() {
return infoItem;
}

public List<InfoItem> getInfoItems() {
return infoItems;
/**
* The format argument to use with {@code git log}
* @return the format argument to use {@code git log} command
* @see <a href="https://git-scm.com/docs/git-log#_pretty_formats">Pretty Formats</a>
*/
public static Arg getFormatArgument() {
Commandline.Argument arg = new Commandline.Argument();
arg.setValue("--format=format:%H %aI %aE %aN");
return arg;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public void testInfoCommandWithZeroShortRevision() throws Exception {
InfoScmResult result = provider.info(repository, new ScmFileSet(getRepositoryRoot()), commandParameters);
assertNotNull(result);
assertTrue(
"revision should be not empty, minimum 4 (see git help rev-parse --short)",
"revision should be not empty, minimum 4 (similar to git help rev-parse --short)",
result.getInfoItems().get(0).getRevision().length() >= 4);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
package org.apache.maven.scm.provider.git.jgit.command.info;

import java.io.File;
import java.util.Collections;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.scm.CommandParameters;
Expand All @@ -33,7 +35,12 @@
import org.apache.maven.scm.provider.git.command.GitCommand;
import org.apache.maven.scm.provider.git.jgit.command.JGitUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.TreeWalk;

/**
* @since 1.9.5
Expand All @@ -44,23 +51,45 @@ protected ScmResult executeCommand(
ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException {
Git git = null;
try {
// TODO: basedir is not necessarily the root of the working copy
File basedir = fileSet.getBasedir();

git = Git.open(basedir);

ObjectId objectId = git.getRepository().resolve("HEAD");
ObjectId objectId = git.getRepository().resolve(Constants.HEAD);
RevCommit headCommit = git.getRepository().parseCommit(objectId);

InfoItem infoItem = new InfoItem();
infoItem.setRevision(StringUtils.trim(objectId.name()));
infoItem.setURL(basedir.toPath().toUri().toASCIIString());
List<InfoItem> infoItems = new LinkedList<>();
// iterate over all files
for (File file : fileSet.getFileList()) {
RevCommit fileCommit = getMostRecentCommitForPath(git.getRepository(), headCommit, file.getPath());
InfoItem infoItem = new InfoItem();
infoItem.setPath(file.getPath());
infoItem.setRevision(StringUtils.trim(fileCommit.name()));
// TODO: what is the URL in this context?
infoItem.setURL(basedir.toPath().toUri().toASCIIString());
PersonIdent authorIdent = fileCommit.getAuthorIdent();
infoItem.setLastModifiedDate(authorIdent
.getWhen()
.toInstant()
.atZone(authorIdent.getTimeZone().toZoneId()));
infoItem.setLastChangedAuthor(authorIdent.getName() + "<" + authorIdent.getEmailAddress() + ">");
infoItems.add(infoItem);
}

return new InfoScmResult(
Collections.singletonList(infoItem),
new ScmResult("JGit.resolve(HEAD)", "", objectId.toString(), true));
return new InfoScmResult(infoItems, new ScmResult("JGit.resolve(HEAD)", "", objectId.toString(), true));
} catch (Exception e) {
throw new ScmException("JGit resolve failure!", e);
} finally {
JGitUtils.closeRepo(git);
}
}

private RevCommit getMostRecentCommitForPath(Repository repository, RevCommit commit, String path)
throws IOException {
try (TreeWalk treeWalk = TreeWalk.forPath(repository, path, commit.getTree())) {
ObjectId fileObjectId = treeWalk.getObjectId(0);
return repository.parseCommit(fileObjectId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.maven.scm.provider.svn.svnexe.command.info;

import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -33,6 +35,8 @@ public class SvnInfoConsumer extends AbstractConsumer {

private InfoItem currentItem = new InfoItem();

private static final DateTimeFormatter DT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");

/** {@inheritDoc} */
public void consumeLine(String s) {
if (s.equals("")) {
Expand Down Expand Up @@ -60,6 +64,7 @@ public void consumeLine(String s) {
} else if (s.startsWith("Last Changed Rev: ")) {
currentItem.setLastChangedRevision(getValue(s));
} else if (s.startsWith("Last Changed Date: ")) {
currentItem.setLastModifiedDate(parseDate(getValue(s)));
currentItem.setLastChangedDate(getValue(s));
}
}
Expand All @@ -78,4 +83,13 @@ private static String getValue(String s) {
public List<InfoItem> getInfoItems() {
return infoItems;
}

static TemporalAccessor parseDate(String dateText) {
// strip the tailing text in parenthesis
int startSuffix = dateText.indexOf('(');
if (startSuffix != -1) {
dateText = dateText.substring(0, startSuffix);
}
return DT_FORMATTER.parse(dateText.trim());
}
}

0 comments on commit ca278a9

Please sign in to comment.