diff --git a/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java b/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java
index ceff4c6fd..7fcc4621c 100644
--- a/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java
+++ b/maven-scm-api/src/main/java/org/apache/maven/scm/CommandParameter.java
@@ -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 git rev-parse --short=lenght
command.
+ * Parameter used only for Git SCM to truncate the emitted hash to the given character length, simulates git rev-parse --short=length
command.
*
* @since 1.7
*/
diff --git a/maven-scm-api/src/main/java/org/apache/maven/scm/command/info/InfoItem.java b/maven-scm-api/src/main/java/org/apache/maven/scm/command/info/InfoItem.java
index 83837dc91..4c18c714f 100644
--- a/maven-scm-api/src/main/java/org/apache/maven/scm/command/info/InfoItem.java
+++ b/maven-scm-api/src/main/java/org/apache/maven/scm/command/info/InfoItem.java
@@ -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 Subversion SCM.
+ *
* @author Kenney Westerhof
* @author Olivier Lamy
*
@@ -45,6 +52,8 @@ public class InfoItem {
private String lastChangedDate;
+ private OffsetDateTime lastChangedDateTime;
+
public String getPath() {
return path;
}
@@ -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);
+ }
}
diff --git a/maven-scm-api/src/main/java/org/apache/maven/scm/util/FilenameUtils.java b/maven-scm-api/src/main/java/org/apache/maven/scm/util/FilenameUtils.java
index 6994deff4..89bffb5fc 100644
--- a/maven-scm-api/src/main/java/org/apache/maven/scm/util/FilenameUtils.java
+++ b/maven-scm-api/src/main/java/org/apache/maven/scm/util/FilenameUtils.java
@@ -29,7 +29,7 @@ public final class FilenameUtils {
private FilenameUtils() {}
public static String normalizeFilename(File file) {
- return normalizeFilename(file.getName());
+ return normalizeFilename(file.getPath());
}
/**
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java
index 2251f2cb4..e499a92a4 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommand.java
@@ -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;
@@ -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
*/
@@ -43,31 +50,36 @@ 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 infoItems = new LinkedList<>();
+ if (fileSet.getFileList().isEmpty()) {
+ infoItems.add(executeInfoCommand(baseCli, parameters, fileSet.getBasedir()));
+ } else {
+ // Insert a separator to make sure that files aren't interpreted as part of the version spec
+ baseCli.createArg().setValue("--");
+ // iterate over files
+ for (File scmFile : fileSet.getFileList()) {
+ Commandline cliClone = (Commandline) baseCli.clone();
+ GitCommandLineUtils.addTarget(cliClone, Collections.singletonList(scmFile));
+ infoItems.add(executeInfoCommand(cliClone, 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();
}
/**
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoConsumer.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoConsumer.java
index 7d8066eff..3c1177983 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoConsumer.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/main/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoConsumer.java
@@ -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 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 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 getInfoItems() {
- return infoItems;
+ /**
+ * The format argument to use with {@code git log}
+ * @return the format argument to use {@code git log} command
+ * @see Pretty Formats
+ */
+ public static Arg getFormatArgument() {
+ Commandline.Argument arg = new Commandline.Argument();
+ arg.setValue("--format=format:%H %aI %aE %aN");
+ return arg;
}
}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitExeInfoCommandTckTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitExeInfoCommandTckTest.java
new file mode 100644
index 000000000..cd1f7a73c
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitExeInfoCommandTckTest.java
@@ -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");
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
index e94b3a3ba..57536280f 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gitexe/src/test/java/org/apache/maven/scm/provider/git/gitexe/command/info/GitInfoCommandTest.java
@@ -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);
}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java
new file mode 100644
index 000000000..65d6048b8
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/command/info/GitInfoCommandTckTest.java
@@ -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 Mark Struberg
+ *
+ */
+public abstract class GitInfoCommandTckTest extends InfoCommandTckTest {
+ /** {@inheritDoc} */
+ public void initRepo() throws Exception {
+ GitScmTestUtils.initRepo("src/test/resources/repository/", getRepositoryRoot(), getWorkingDirectory());
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/JGitUtils.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/JGitUtils.java
index d26f86014..1f6484e59 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/JGitUtils.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/JGitUtils.java
@@ -21,7 +21,6 @@
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
-import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
@@ -29,13 +28,13 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.function.BiConsumer;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.scm.ScmFile;
import org.apache.maven.scm.ScmFileSet;
import org.apache.maven.scm.ScmFileStatus;
import org.apache.maven.scm.provider.git.repository.GitScmProviderRepository;
+import org.apache.maven.scm.util.FilenameUtils;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
@@ -256,7 +255,8 @@ public static List getFilesInCommit(Repository repository, RevCommit co
for (DiffEntry diff : diffs) {
final String path;
if (baseDir != null) {
- path = relativize(baseDir.toURI(), new File(repository.getWorkTree(), diff.getNewPath()));
+ path = relativize(baseDir, new File(repository.getWorkTree(), diff.getNewPath()))
+ .getPath();
} else {
path = diff.getNewPath();
}
@@ -300,16 +300,10 @@ public static ScmFileStatus getScmFileStatus(ChangeType changeType) {
* @throws GitAPIException
*/
public static List addAllFiles(Git git, ScmFileSet fileSet) throws GitAPIException {
- URI workingCopyRootUri = git.getRepository().getWorkTree().toURI();
+ File workingCopyRootDirectory = git.getRepository().getWorkTree();
AddCommand add = git.add();
- callWithRepositoryRelativeFilePath(
- (relativeFile, absoluteFile) -> {
- if (absoluteFile.exists()) {
- add.addFilepattern(relativeFile);
- }
- },
- workingCopyRootUri,
- fileSet);
+ getWorkingCopyRelativePaths(workingCopyRootDirectory, fileSet).stream()
+ .forEach(f -> add.addFilepattern(toNormalizedFilePath(f)));
add.call();
Status status = git.status().call();
@@ -318,7 +312,7 @@ public static List addAllFiles(Git git, ScmFileSet fileSet) throws GitA
allInIndex.addAll(status.getAdded());
allInIndex.addAll(status.getChanged());
return getScmFilesForAllFileSetFilesContainedInRepoPath(
- workingCopyRootUri, fileSet, allInIndex, ScmFileStatus.ADDED);
+ workingCopyRootDirectory, fileSet, allInIndex, ScmFileStatus.ADDED);
}
/**
@@ -331,61 +325,68 @@ public static List addAllFiles(Git git, ScmFileSet fileSet) throws GitA
* @throws GitAPIException
*/
public static List removeAllFiles(Git git, ScmFileSet fileSet) throws GitAPIException {
- URI workingCopyRootUri = git.getRepository().getWorkTree().toURI();
+ File workingCopyRootDirectory = git.getRepository().getWorkTree();
RmCommand remove = git.rm();
- callWithRepositoryRelativeFilePath(
- (relativeFile, absoluteFile) -> remove.addFilepattern(relativeFile), workingCopyRootUri, fileSet);
+ getWorkingCopyRelativePaths(workingCopyRootDirectory, fileSet).stream()
+ .forEach(f -> remove.addFilepattern(toNormalizedFilePath(f)));
remove.call();
Status status = git.status().call();
Set allInIndex = new HashSet<>(status.getRemoved());
return getScmFilesForAllFileSetFilesContainedInRepoPath(
- workingCopyRootUri, fileSet, allInIndex, ScmFileStatus.DELETED);
+ workingCopyRootDirectory, fileSet, allInIndex, ScmFileStatus.DELETED);
}
/**
- * For each file in the {@code fileSet} call the {@code fileCallback} with the file path relative to the repository
- * root (forward slashes as separator) and the absolute file path.
- * @param repoFileCallback the callback to call for each file in the fileset
- * @param git the git repository
- * @param fileSet the file set to traverse
+ * Convert each file in the {@code fileSet} to their relative file path to workingCopyDirectory
+ * and return them in a list.
+ * @param workingCopyDirectory the working copy root directory
+ * @param fileSet the file set to convert
*/
- private static void callWithRepositoryRelativeFilePath(
- BiConsumer fileCallback, URI workingCopyRootUri, ScmFileSet fileSet) {
- for (File file : fileSet.getFileList()) {
- if (!file.isAbsolute()) {
- file = new File(fileSet.getBasedir().getPath(), file.getPath());
+ public static List getWorkingCopyRelativePaths(File workingCopyDirectory, ScmFileSet fileSet) {
+ List repositoryRelativePaths = new ArrayList<>();
+ for (File path : fileSet.getFileList()) {
+ if (!path.isAbsolute()) {
+ path = new File(fileSet.getBasedir().getPath(), path.getPath());
}
- String path = relativize(workingCopyRootUri, file);
- fileCallback.accept(path, file);
+ File repositoryRelativePath = relativize(workingCopyDirectory, path);
+ repositoryRelativePaths.add(repositoryRelativePath);
}
+ return repositoryRelativePaths;
+ }
+
+ /**
+ * Converts the given file to a string only containing forward slashes
+ * @param file
+ * @return the normalized file path
+ */
+ public static String toNormalizedFilePath(File file) {
+ return FilenameUtils.normalizeFilename(file);
}
private static List getScmFilesForAllFileSetFilesContainedInRepoPath(
- URI workingCopyRootUri, ScmFileSet fileSet, Set repoFilePaths, ScmFileStatus fileStatus) {
+ File workingCopyDirectory, ScmFileSet fileSet, Set repoFilePaths, ScmFileStatus fileStatus) {
List files = new ArrayList<>(repoFilePaths.size());
- callWithRepositoryRelativeFilePath(
- (relativeFile, absoluteFile) -> {
- // check if repo relative path is contained
- if (repoFilePaths.contains(relativeFile)) {
- // returned ScmFiles should be relative to given fileset's basedir
- ScmFile scmfile =
- new ScmFile(relativize(fileSet.getBasedir().toURI(), absoluteFile), fileStatus);
- files.add(scmfile);
- }
- },
- workingCopyRootUri,
- fileSet);
+ getWorkingCopyRelativePaths(workingCopyDirectory, fileSet).stream().forEach((relativeFile) -> {
+ // check if repo relative path is contained
+ if (repoFilePaths.contains(toNormalizedFilePath(relativeFile))) {
+ // returned ScmFiles should be relative to given fileset's basedir
+ ScmFile scmfile = new ScmFile(
+ relativize(fileSet.getBasedir(), new File(workingCopyDirectory, relativeFile.getPath()))
+ .getPath(),
+ fileStatus);
+ files.add(scmfile);
+ }
+ });
return files;
}
- private static String relativize(URI baseUri, File f) {
- String path = f.getPath();
- if (f.isAbsolute()) {
- path = baseUri.relativize(new File(path).toURI()).getPath();
+ private static File relativize(File baseDir, File file) {
+ if (file.isAbsolute()) {
+ return baseDir.toPath().relativize(file.toPath()).toFile();
}
- return path;
+ return file;
}
/**
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
index 320b3e589..edf562d81 100644
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/main/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommand.java
@@ -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;
@@ -33,7 +35,16 @@
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.revwalk.RevSort;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.treewalk.filter.TreeFilter;
/**
* @since 1.9.5
@@ -42,25 +53,59 @@ public class JGitInfoCommand extends AbstractCommand implements GitCommand {
@Override
protected ScmResult executeCommand(
ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters) throws ScmException {
- Git git = null;
- try {
- File basedir = fileSet.getBasedir();
+ File basedir = fileSet.getBasedir();
+ try (Git git = JGitUtils.openRepo(basedir); ) {
+ ObjectId objectId = git.getRepository().resolve(Constants.HEAD);
+ if (objectId == null) {
+ throw new ScmException("Cannot resolve HEAD in git repository at " + basedir);
+ }
- git = Git.open(basedir);
+ List infoItems = new LinkedList<>();
+ if (fileSet.getFileList().isEmpty()) {
+ RevCommit headCommit = git.getRepository().parseCommit(objectId);
+ infoItems.add(getInfoItem(headCommit, fileSet.getBasedir()));
+ } else {
+ // iterate over all files
+ for (File file : JGitUtils.getWorkingCopyRelativePaths(
+ git.getRepository().getWorkTree(), fileSet)) {
+ infoItems.add(getInfoItem(git.getRepository(), objectId, file));
+ }
+ }
+ return new InfoScmResult(infoItems, new ScmResult("JGit.resolve(HEAD)", "", objectId.toString(), true));
+ } catch (Exception e) {
+ throw new ScmException("JGit resolve failure!", e);
+ }
+ }
- ObjectId objectId = git.getRepository().resolve("HEAD");
+ protected InfoItem getInfoItem(Repository repository, ObjectId headObjectId, File file) throws IOException {
+ RevCommit commit = getMostRecentCommitForPath(repository, headObjectId, JGitUtils.toNormalizedFilePath(file));
+ return getInfoItem(commit, file);
+ }
- InfoItem infoItem = new InfoItem();
- infoItem.setRevision(StringUtils.trim(objectId.name()));
- infoItem.setURL(basedir.toPath().toUri().toASCIIString());
+ protected InfoItem getInfoItem(RevCommit fileCommit, File file) {
+ InfoItem infoItem = new InfoItem();
+ infoItem.setPath(file.getPath());
+ infoItem.setRevision(StringUtils.trim(fileCommit.name()));
+ infoItem.setURL(file.toPath().toUri().toASCIIString());
+ PersonIdent authorIdent = fileCommit.getAuthorIdent();
+ infoItem.setLastChangedDateTime(authorIdent
+ .getWhen()
+ .toInstant()
+ .atZone(authorIdent.getTimeZone().toZoneId()));
+ infoItem.setLastChangedAuthor(authorIdent.getName() + " <" + authorIdent.getEmailAddress() + ">");
+ return infoItem;
+ }
- return new InfoScmResult(
- Collections.singletonList(infoItem),
- 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, ObjectId headObjectId, String path)
+ throws IOException {
+ RevCommit latestCommit = null;
+ try (RevWalk revWalk = new RevWalk(repository)) {
+ RevCommit headCommit = revWalk.parseCommit(headObjectId);
+ revWalk.markStart(headCommit);
+ revWalk.sort(RevSort.COMMIT_TIME_DESC);
+ revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF));
+ latestCommit = revWalk.next();
}
+ return latestCommit;
}
}
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTckTest.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTckTest.java
new file mode 100644
index 000000000..05f410fb9
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-jgit/src/test/java/org/apache/maven/scm/provider/git/jgit/command/info/JGitInfoCommandTckTest.java
@@ -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.jgit.command.info;
+
+import org.apache.maven.scm.provider.git.GitScmTestUtils;
+import org.apache.maven.scm.provider.git.command.info.GitInfoCommandTckTest;
+
+public class JGitInfoCommandTckTest extends GitInfoCommandTckTest {
+
+ public String getScmUrl() throws Exception {
+ return GitScmTestUtils.getScmUrl(getRepositoryRoot(), "jgit");
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/main/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumer.java b/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/main/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumer.java
index 0a4a71680..453023571 100644
--- a/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/main/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumer.java
+++ b/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/main/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumer.java
@@ -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;
@@ -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("")) {
@@ -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.setLastChangedDateTime(parseDate(getValue(s)));
currentItem.setLastChangedDate(getValue(s));
}
}
@@ -78,4 +83,13 @@ private static String getValue(String s) {
public List 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());
+ }
}
diff --git a/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/test/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumerTest.java b/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/test/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumerTest.java
new file mode 100644
index 000000000..d74c8adad
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svnexe/src/test/java/org/apache/maven/scm/provider/svn/svnexe/command/info/SvnInfoConsumerTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.svn.svnexe.command.info;
+
+import java.time.temporal.ChronoField;
+import java.time.temporal.TemporalAccessor;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class SvnInfoConsumerTest {
+
+ @Test
+ public void testParseDate() {
+ TemporalAccessor date = SvnInfoConsumer.parseDate("2024-01-19 16:33:05 +0100 (Fr, 19 Jan 2024");
+ assertEquals(2024, date.get(ChronoField.YEAR));
+ }
+}
diff --git a/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svntest/src/main/java/org/apache/maven/scm/provider/svn/command/info/SvnInfoCommandTckTest.java b/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svntest/src/main/java/org/apache/maven/scm/provider/svn/command/info/SvnInfoCommandTckTest.java
new file mode 100644
index 000000000..541c6d92c
--- /dev/null
+++ b/maven-scm-providers/maven-scm-providers-svn/maven-scm-provider-svntest/src/main/java/org/apache/maven/scm/provider/svn/command/info/SvnInfoCommandTckTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.svn.command.info;
+
+import java.io.File;
+
+import org.apache.maven.scm.provider.svn.SvnScmTestUtils;
+import org.apache.maven.scm.tck.command.info.InfoCommandTckTest;
+
+public class SvnInfoCommandTckTest extends InfoCommandTckTest {
+ /** {@inheritDoc} */
+ public String getScmUrl() throws Exception {
+ return SvnScmTestUtils.getScmUrl(new File(getRepositoryRoot(), "trunk"));
+ }
+
+ /** {@inheritDoc} */
+ public void initRepo() throws Exception {
+ SvnScmTestUtils.initializeRepository(getRepositoryRoot());
+ }
+}
diff --git a/maven-scm-test/src/main/java/org/apache/maven/scm/tck/command/info/InfoCommandTckTest.java b/maven-scm-test/src/main/java/org/apache/maven/scm/tck/command/info/InfoCommandTckTest.java
new file mode 100644
index 000000000..cb89265b4
--- /dev/null
+++ b/maven-scm-test/src/main/java/org/apache/maven/scm/tck/command/info/InfoCommandTckTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.tck.command.info;
+
+import java.io.File;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+
+import org.apache.maven.scm.ScmFileSet;
+import org.apache.maven.scm.ScmTckTestCase;
+import org.apache.maven.scm.command.info.InfoItem;
+import org.apache.maven.scm.command.info.InfoScmResult;
+import org.apache.maven.scm.provider.ScmProvider;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * This test tests the info command.
+ *
+ */
+public abstract class InfoCommandTckTest extends ScmTckTestCase {
+
+ @Test
+ public void testInfoCommandWithJustBasedir() throws Exception {
+ ScmProvider scmProvider = getScmManager().getProviderByUrl(getScmUrl());
+ InfoScmResult result = scmProvider.info(getScmRepository().getProviderRepository(), getScmFileSet(), null);
+ assertResultIsSuccess(result);
+ assertEquals(1, result.getInfoItems().size());
+ InfoItem item = result.getInfoItems().get(0);
+ assertEquals("Mark Struberg ", item.getLastChangedAuthor());
+ assertEquals("92f139dfec4d1dfb79c3cd2f94e83bf13129668b", item.getRevision());
+ assertEquals(
+ OffsetDateTime.of(2009, 03, 15, 19, 14, 02, 0, ZoneOffset.ofHours(1)), item.getLastChangedDateTime());
+ }
+
+ @Test
+ public void testInfoCommandFromBasedirDifferentFromWorkingCopyDirectory() throws Exception {
+ ScmProvider scmProvider = getScmManager().getProviderByUrl(getScmUrl());
+ ScmFileSet fileSet = new ScmFileSet(new File(getWorkingCopy(), "src/main"), new File("java/Application.java"));
+ InfoScmResult result = scmProvider.info(getScmRepository().getProviderRepository(), fileSet, null);
+ assertResultIsSuccess(result);
+ assertEquals(1, result.getInfoItems().size());
+ InfoItem item = result.getInfoItems().get(0);
+ assertEquals("Mark Struberg ", item.getLastChangedAuthor());
+ assertEquals("92f139dfec4d1dfb79c3cd2f94e83bf13129668b", item.getRevision());
+ assertEquals(
+ OffsetDateTime.of(2009, 03, 15, 19, 14, 02, 0, ZoneOffset.ofHours(1)), item.getLastChangedDateTime());
+ }
+}