Skip to content

Commit

Permalink
[SCM-914] Introduce properly typed last modified date (#193)
Browse files Browse the repository at this point in the history
Populate it with svnexe, gitexe and JGit providers

Co-authored-by: Michael Osipov <michaelo@apache.org>
  • Loading branch information
kwin and michael-o committed Apr 8, 2024
1 parent 93d0142 commit a297237
Show file tree
Hide file tree
Showing 15 changed files with 495 additions and 102 deletions.
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
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 a 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,36 @@ public void setLastChangedRevision(String lastChangedRevision) {
this.lastChangedRevision = lastChangedRevision;
}

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

/**
* @deprecated Use {@link #setLastChangedDateTime(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
* @since 2.1.0
*/
public OffsetDateTime getLastChangedDateTime() {
return lastChangedDateTime;
}

/**
* @param accessor temporal accessor from which to populate the last changed date
* @since 2.1.0
*/
public void setLastChangedDateTime(TemporalAccessor accessor) {
this.lastChangedDateTime = OffsetDateTime.from(accessor);
}
}
Expand Up @@ -29,7 +29,7 @@ public final class FilenameUtils {
private FilenameUtils() {}

public static String normalizeFilename(File file) {
return normalizeFilename(file.getName());
return normalizeFilename(file.getPath());
}

/**
Expand Down
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,39 @@ 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.createArg().setValue("--no-merges"); // skip merge commits
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<>();
if (fileSet.getFileList().isEmpty()) {
infoItems.add(executeInfoCommand(baseCli, parameters, fileSet.getBasedir()));
} else {
// iterate over files
for (File scmFile : fileSet.getFileList()) {
baseCli = GitCommandLineUtils.getBaseGitCommandLine(fileSet.getBasedir(), "log");
baseCli.createArg().setValue("-1"); // only most recent commit matters
baseCli.createArg().setValue("--no-merges"); // skip merge commits
baseCli.addArg(GitInfoConsumer.getFormatArgument());
// Insert a separator to make sure that files aren't interpreted as part of the version spec
baseCli.createArg().setValue("--");
GitCommandLineUtils.addTarget(baseCli, Collections.singletonList(scmFile));
infoItems.add(executeInfoCommand(baseCli, parameters, scmFile));
}
}
return new InfoScmResult(cli.toString(), consumer.getInfoItems());
return new InfoScmResult(baseCli.toString(), 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);
protected InfoItem executeInfoCommand(Commandline cli, CommandParameters parameters, File scmFile)
throws ScmException {
GitInfoConsumer consumer = new GitInfoConsumer(scmFile.toPath(), getRevisionLength(parameters));
CommandLineUtils.StringStreamConsumer stderr = new CommandLineUtils.StringStreamConsumer();
int exitCode = GitCommandLineUtils.execute(cli, consumer, stderr);
if (exitCode != 0) {
throw new ScmException("The git log command failed: " + cli.toString() + " returned " + stderr.getOutput());
}
cli.createArg().setValue("HEAD");

return cli;
return consumer.getInfoItem();
}

/**
Expand Down
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</a>
*/
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);
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.setLastChangedDateTime(
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;
}
}
@@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.scm.provider.git.gitexe.command.info;

import org.apache.maven.scm.provider.git.GitScmTestUtils;
import org.apache.maven.scm.provider.git.command.info.GitInfoCommandTckTest;

public class GitExeInfoCommandTckTest extends GitInfoCommandTckTest {

public String getScmUrl() throws Exception {
return GitScmTestUtils.getScmUrl(getRepositoryRoot(), "git");
}
}
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
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.scm.provider.git.command.info;

import org.apache.maven.scm.provider.git.GitScmTestUtils;
import org.apache.maven.scm.tck.command.info.InfoCommandTckTest;

/**
* @author <a href="mailto:struberg@yahoo.de">Mark Struberg</a>
*
*/
public abstract class GitInfoCommandTckTest extends InfoCommandTckTest {
/** {@inheritDoc} */
public void initRepo() throws Exception {
GitScmTestUtils.initRepo("src/test/resources/repository/", getRepositoryRoot(), getWorkingDirectory());
}
}

0 comments on commit a297237

Please sign in to comment.