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.
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 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);
}
}
Original file line number Diff line number Diff line change
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
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,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
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</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;
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
}
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
@@ -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());
}
}
Loading

0 comments on commit a297237

Please sign in to comment.