From 6d409c65c0a9f7a3db524ac550217abf483dc564 Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Tue, 25 Aug 2020 14:45:48 +0200 Subject: [PATCH 1/9] initial implementation --- .../main/java/sonia/scm/repository/Tag.java | 15 ++++ .../java/sonia/scm/repository/GitUtil.java | 40 +++++++++++ .../repository/api/GitHookTagProvider.java | 61 +++++++++------- .../spi/GitHookContextProvider.java | 2 +- .../scm/repository/spi/GitTagsCommand.java | 65 +++++++---------- .../repository/client/spi/GitTagCommand.java | 6 +- .../repository/spi/GitTagsCommandTest.java | 67 ++++++++++++++++++ .../repository/spi/scm-git-spi-test-tags.zip | Bin 0 -> 44688 bytes .../scm/repository/spi/HgTagsCommand.java | 43 ++++------- .../scm/repository/spi/HgTagsCommandTest.java | 45 ++++++++++++ .../DefaultChangesetToChangesetDtoMapper.java | 19 ++++- .../sonia/scm/api/v2/resources/TagDto.java | 6 +- .../api/v2/resources/TagToTagDtoMapper.java | 9 +++ .../v2/resources/TagToTagDtoMapperTest.java | 8 +++ 14 files changed, 284 insertions(+), 102 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test-tags.zip create mode 100644 scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Tag.java b/scm-core/src/main/java/sonia/scm/repository/Tag.java index 3c40c2f38a..bdeec544a4 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Tag.java +++ b/scm-core/src/main/java/sonia/scm/repository/Tag.java @@ -41,6 +41,7 @@ public final class Tag { private final String name; private final String revision; + private final Long date; /** * Constructs a new tag. @@ -49,7 +50,21 @@ public final class Tag { * @param revision tagged revision */ public Tag(String name, String revision) { + this(name, revision, null); + } + + /** + * Constructs a new tag. + * + * @param name name of the tag + * @param revision tagged revision + * @param date the creation timestamp (milliseconds) of the tag + * + * @since 2.5.0 + */ + public Tag(String name, String revision, Long date) { this.name = name; this.revision = revision; + this.date = date; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index b55aec06bd..96e4266a5d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -42,6 +42,8 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; @@ -387,6 +389,44 @@ public final class GitUtil return ref; } + /** + * Method description + * + * + * @param repository + * @param revWalk + * @param ref + * + * @return + * + * @throws IOException + * + * @since 2.5.0 + */ + public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, + RevWalk revWalk, Ref ref) + throws IOException + { + ObjectId id = ref.getObjectId(); + + if (id != null) + { + if (revWalk == null) + { + revWalk = new RevWalk(repository); + } + + final RevObject revObject = revWalk.parseAny(id); + if (revObject instanceof RevTag) { + return ((RevTag) revObject).getTaggerIdent().getWhen().getTime(); + } else if (revObject instanceof RevCommit) { + return getCommitTime((RevCommit) revObject); + } + } + + return null; + } + /** * Method description * diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java index 2a290c6ca9..5bc5069112 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java @@ -21,12 +21,17 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.api; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; + +import java.io.IOException; import java.util.List; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,52 +40,60 @@ import sonia.scm.repository.Tag; /** * Git provider implementation of {@link HookTagProvider}. - * - * @since 1.50 + * * @author Sebastian Sdorra + * @since 1.50 */ public class GitHookTagProvider implements HookTagProvider { - private static final Logger logger = LoggerFactory.getLogger(GitHookTagProvider.class); - + private static final Logger LOG = LoggerFactory.getLogger(GitHookTagProvider.class); + private final List createdTags; private final List deletedTags; /** * Constructs new instance. - * + * * @param commands received commands */ - public GitHookTagProvider(List commands) { + public GitHookTagProvider(List commands, Repository repository) { ImmutableList.Builder createdTagBuilder = ImmutableList.builder(); ImmutableList.Builder deletedTagBuilder = ImmutableList.builder(); - - for ( ReceiveCommand rc : commands ){ + + for (ReceiveCommand rc : commands) { String refName = rc.getRefName(); String tag = GitUtil.getTagName(refName); - - if (Strings.isNullOrEmpty(tag)){ - logger.debug("received ref name {} is not a tag", refName); - } else if (isCreate(rc)) { - createdTagBuilder.add(createTagFromNewId(rc, tag)); - } else if (isDelete(rc)){ - deletedTagBuilder.add(createTagFromOldId(rc, tag)); - } else if (isUpdate(rc)) { - createdTagBuilder.add(createTagFromNewId(rc, tag)); - deletedTagBuilder.add(createTagFromOldId(rc, tag)); + + if (Strings.isNullOrEmpty(tag)) { + LOG.debug("received ref name {} is not a tag", refName); + } else { + Long tagTime = null; + try (RevWalk walk = new RevWalk(repository)) { + tagTime = GitUtil.getTagTime(repository, walk, rc.getRef()); + } catch (IOException e) { + LOG.error("Could not read tag time", e); + } + if (isCreate(rc)) { + createdTagBuilder.add(createTagFromNewId(rc, tag, tagTime)); + } else if (isDelete(rc)) { + deletedTagBuilder.add(createTagFromOldId(rc, tag, tagTime)); + } else if (isUpdate(rc)) { + createdTagBuilder.add(createTagFromNewId(rc, tag, tagTime)); + deletedTagBuilder.add(createTagFromOldId(rc, tag, tagTime)); + } } } - + createdTags = createdTagBuilder.build(); deletedTags = deletedTagBuilder.build(); } - private Tag createTagFromNewId(ReceiveCommand rc, String tag) { - return new Tag(tag, GitUtil.getId(rc.getNewId())); + private Tag createTagFromNewId(ReceiveCommand rc, String tag, Long tagTime) { + return new Tag(tag, GitUtil.getId(rc.getNewId()), tagTime); } - private Tag createTagFromOldId(ReceiveCommand rc, String tag) { - return new Tag(tag, GitUtil.getId(rc.getOldId())); + private Tag createTagFromOldId(ReceiveCommand rc, String tag, Long tagTime) { + return new Tag(tag, GitUtil.getId(rc.getOldId()), tagTime); } private boolean isUpdate(ReceiveCommand rc) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java index 13a11007a2..ce61b85a57 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java @@ -103,7 +103,7 @@ public class GitHookContextProvider extends HookContextProvider @Override public HookTagProvider getTagProvider() { - return new GitHookTagProvider(receiveCommands); + return new GitHookTagProvider(receiveCommands, repository); } @Override diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java index 208c931f63..97b9c91be1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java @@ -31,7 +31,7 @@ import com.google.common.collect.Lists; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,34 +45,28 @@ import java.util.List; //~--- JDK imports ------------------------------------------------------------ /** - * * @author Sebastian Sdorra */ -public class GitTagsCommand extends AbstractGitCommand implements TagsCommand -{ +public class GitTagsCommand extends AbstractGitCommand implements TagsCommand { /** * Constructs ... * - * @param context - * + * @param context */ - public GitTagsCommand(GitContext context) - { + public GitTagsCommand(GitContext context) { super(context); } //~--- get methods ---------------------------------------------------------- @Override - public List getTags() throws IOException - { + public List getTags() throws IOException { List tags = null; RevWalk revWalk = null; - try - { + try { final Git git = new Git(open()); revWalk = new RevWalk(git.getRepository()); @@ -81,13 +75,9 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand tags = Lists.transform(tagList, new TransformFuntion(git.getRepository(), revWalk)); - } - catch (GitAPIException ex) - { + } catch (GitAPIException ex) { throw new InternalRepositoryException(repository, "could not read tags from repository", ex); - } - finally - { + } finally { GitUtil.release(revWalk); } @@ -99,12 +89,10 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand /** * Class description * - * - * @version Enter version here..., 12/07/06 - * @author Enter your name here... + * @author Enter your name here... + * @version Enter version here..., 12/07/06 */ - private static class TransformFuntion implements Function - { + private static class TransformFuntion implements Function { /** * the logger for TransformFuntion @@ -117,13 +105,11 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand /** * Constructs ... * - * * @param repository * @param revWalk */ public TransformFuntion(org.eclipse.jgit.lib.Repository repository, - RevWalk revWalk) - { + RevWalk revWalk) { this.repository = repository; this.revWalk = revWalk; } @@ -133,30 +119,23 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand /** * Method description * - * * @param ref - * * @return */ @Override - public Tag apply(Ref ref) - { + public Tag apply(Ref ref) { Tag tag = null; - try - { - RevCommit commit = GitUtil.getCommit(repository, revWalk, ref); + try { + RevObject revObject = revWalk.parseAny(ref.getObjectId()); - if (commit != null) - { + if (revObject != null) { String name = GitUtil.getTagName(ref); - tag = new Tag(name, commit.getId().name()); + tag = new Tag(name, revObject.getId().name(), GitUtil.getTagTime(repository, revWalk, ref)); } - } - catch (IOException ex) - { + } catch (IOException ex) { logger.error("could not get commit for tag", ex); } @@ -165,10 +144,14 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand //~--- fields ------------------------------------------------------------- - /** Field description */ + /** + * Field description + */ private org.eclipse.jgit.lib.Repository repository; - /** Field description */ + /** + * Field description + */ private RevWalk revWalk; } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java index db44ee5a90..19b93d0775 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java @@ -78,6 +78,7 @@ public class GitTagCommand implements TagCommand String revision = request.getRevision(); RevObject revObject = null; + Long tagTime = null; if (!Strings.isNullOrEmpty(revision)) { @@ -88,6 +89,7 @@ public class GitTagCommand implements TagCommand { walk = new RevWalk(git.getRepository()); revObject = walk.parseAny(id); + tagTime = GitUtil.getTagTime(git.getRepository(), walk, GitUtil.getRefForCommit(git.getRepository(), id)); } finally { @@ -110,9 +112,9 @@ public class GitTagCommand implements TagCommand } if (ref.isPeeled()) { - tag = new Tag(request.getName(), ref.getPeeledObjectId().toString()); + tag = new Tag(request.getName(), ref.getPeeledObjectId().toString(), tagTime); } else { - tag = new Tag(request.getName(), ref.getObjectId().toString()); + tag = new Tag(request.getName(), ref.getObjectId().toString(), tagTime); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java new file mode 100644 index 0000000000..afa3cc4930 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java @@ -0,0 +1,67 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import com.github.sdorra.shiro.ShiroRule; +import com.github.sdorra.shiro.SubjectAware; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import sonia.scm.repository.Tag; + +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret") +public class GitTagsCommandTest extends AbstractGitCommandTestBase { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Rule + public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule(); + + @Rule + public ShiroRule shiro = new ShiroRule(); + + @Test + public void shouldGetDatesCorrectly() throws IOException { + final GitContext gitContext = createContext(); + final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext); + final List tags = tagsCommand.getTags(); + assertThat(tags).hasSize(2); + assertThat(tags.get(0).getName()).isEqualTo("1.0.0"); + assertThat(tags.get(0).getDate()).isEqualTo(1598348105000L); // Annotated - Take tag date + assertThat(tags.get(1).getName()).isEqualTo("test-tag"); + assertThat(tags.get(1).getDate()).isEqualTo(1339416344000L); // Lightweight - Take commit date + } + + @Override + protected String getZippedRepositoryResource() { + return "sonia/scm/repository/spi/scm-git-spi-test-tags.zip"; + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test-tags.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test-tags.zip new file mode 100644 index 0000000000000000000000000000000000000000..05b2f0f7cab59ed00cab488ed71e36611e5f6983 GIT binary patch literal 44688 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 Xyu6rL`jhSIzuVO5qDRyp`F#HeSg)q) literal 0 HcmV?d00001 diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgTagsCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgTagsCommand.java index 309265617c..b0fb323bab 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgTagsCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgTagsCommand.java @@ -29,29 +29,22 @@ package sonia.scm.repository.spi; import com.google.common.base.Function; import com.google.common.base.Strings; import com.google.common.collect.Lists; - import sonia.scm.repository.Tag; import sonia.scm.util.Util; -//~--- JDK imports ------------------------------------------------------------ - import java.util.List; /** - * * @author Sebastian Sdorra */ -public class HgTagsCommand extends AbstractCommand implements TagsCommand -{ +public class HgTagsCommand extends AbstractCommand implements TagsCommand { /** * Constructs ... * - * @param context - * + * @param context */ - public HgTagsCommand(HgCommandContext context) - { + public HgTagsCommand(HgCommandContext context) { super(context); } @@ -60,12 +53,10 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand /** * Method description * - * * @return */ @Override - public List getTags() - { + public List getTags() { com.aragost.javahg.commands.TagsCommand cmd = com.aragost.javahg.commands.TagsCommand.on(open()); @@ -74,13 +65,11 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand List tags = null; // check for empty repository - if (Util.isNotEmpty(tagList) && tagList.get(0).getChangeset() != null) - { + if (Util.isNotEmpty(tagList) && tagList.get(0).getChangeset() != null) { tags = Lists.transform(tagList, new TagTransformer()); } - if (tags == null) - { + if (tags == null) { tags = Lists.newArrayList(); } @@ -92,31 +81,25 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand /** * Class description * - * - * @version Enter version here..., 12/08/03 - * @author Enter your name here... + * @author Enter your name here... + * @version Enter version here..., 12/08/03 */ private static class TagTransformer - implements Function - { + implements Function { /** * Method description * - * * @param f - * * @return */ @Override - public Tag apply(com.aragost.javahg.Tag f) - { + public Tag apply(com.aragost.javahg.Tag f) { Tag t = null; - if ((f != null) &&!Strings.isNullOrEmpty(f.getName()) - && (f.getChangeset() != null)) - { - t = new Tag(f.getName(), f.getChangeset().getNode()); + if ((f != null) && !Strings.isNullOrEmpty(f.getName()) + && (f.getChangeset() != null)) { + t = new Tag(f.getName(), f.getChangeset().getNode(), f.getChangeset().getTimestamp().getDate().getTime()); } return t; diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java new file mode 100644 index 0000000000..f9d3fdd5b2 --- /dev/null +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java @@ -0,0 +1,45 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.repository.spi; + +import org.junit.Test; +import sonia.scm.repository.Tag; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HgTagsCommandTest extends AbstractHgCommandTestBase { + + @Test + public void shouldGetTagDatesCorrectly() { + HgTagsCommand hgTagsCommand = new HgTagsCommand(cmdContext); + final List tags = hgTagsCommand.getTags(); + assertThat(tags).hasSize(1); + assertThat(tags.get(0).getName()).isEqualTo("tip"); + assertThat(tags.get(0).getDate()).isEqualTo(1339586381000L); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java index 384331c6ad..c05547b295 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/DefaultChangesetToChangesetDtoMapper.java @@ -29,13 +29,15 @@ import de.otto.edison.hal.Links; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.ObjectFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sonia.scm.repository.Branch; import sonia.scm.repository.Changeset; import sonia.scm.repository.Contributor; import sonia.scm.repository.Person; import sonia.scm.repository.Repository; import sonia.scm.repository.Signature; -import sonia.scm.repository.Tag; +import sonia.scm.repository.Tags; import sonia.scm.repository.api.Command; import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryServiceFactory; @@ -45,6 +47,7 @@ import sonia.scm.security.gpg.RawGpgKey; import sonia.scm.web.EdisonHalAppender; import javax.inject.Inject; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -57,6 +60,8 @@ import static de.otto.edison.hal.Links.linkingTo; @Mapper public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper, ChangesetToChangesetDtoMapper { + private static Logger LOG = LoggerFactory.getLogger(DefaultChangesetToChangesetDtoMapper.class); + @Inject private RepositoryServiceFactory serviceFactory; @@ -115,8 +120,16 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa try (RepositoryService repositoryService = serviceFactory.create(repository)) { if (repositoryService.isSupported(Command.TAGS)) { - embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, - getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId())))); + Tags tags = null; + try { + tags = repositoryService.getTagsCommand().getTags(); + } catch (IOException e) { + LOG.error("Error while retrieving tags from repository", e); + } + if (tags != null) { + embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name, + getListOfObjects(source.getTags(), tags::getTagByName))); + } } if (repositoryService.isSupported(Command.BRANCHES)) { embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name, diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java index f96417a6bd..b7c6b3bacf 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.api.v2.resources; import de.otto.edison.hal.Embedded; @@ -31,6 +31,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import java.time.Instant; + @Getter @Setter @NoArgsConstructor @@ -40,6 +42,8 @@ public class TagDto extends HalRepresentation { private String revision; + private Instant date; + TagDto(Links links, Embedded embedded) { super(links, embedded); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java index e056162dcc..532eb9be85 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -29,6 +29,7 @@ import de.otto.edison.hal.Links; import org.mapstruct.Context; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.mapstruct.ObjectFactory; import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Tag; @@ -36,6 +37,8 @@ import sonia.scm.web.EdisonHalAppender; import javax.inject.Inject; +import java.time.Instant; + import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Links.linkingTo; @@ -46,6 +49,7 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper { @Inject private ResourceLinks resourceLinks; + @Mapping(target = "date", source = "date", qualifiedByName = "mapDate") @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName); @@ -61,4 +65,9 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper { return new TagDto(linksBuilder.build(), embeddedBuilder.build()); } + + @Named("mapDate") + Instant map(Long value) { + return value == null ? null : Instant.ofEpochMilli(value); + } } diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java index f53a10797b..ebf71f6b85 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/TagToTagDtoMapperTest.java @@ -32,6 +32,7 @@ import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.Tag; import java.net.URI; +import java.time.Instant; import static org.assertj.core.api.Assertions.assertThat; @@ -58,4 +59,11 @@ class TagToTagDtoMapperTest { assertThat(dto.getLinks().getLinkBy("yo").get().getHref()).isEqualTo("http://hitchhiker/hog/1.0.0"); } + @Test + void shouldMapDate() { + final long now = Instant.now().getEpochSecond() * 1000; + TagDto dto = mapper.map(new Tag("1.0.0", "42", now), new NamespaceAndName("hitchhiker", "hog")); + assertThat(dto.getDate()).isEqualTo(Instant.ofEpochMilli(now)); + } + } From 36231d077ac3feeb80a0c0f7553e1c8faa095a5b Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Tue, 25 Aug 2020 18:08:31 +0200 Subject: [PATCH 2/9] cleanup and update tests --- .../main/java/sonia/scm/repository/Tag.java | 11 ++++ .../java/sonia/scm/repository/GitUtil.java | 32 +++++++--- .../repository/api/GitHookTagProvider.java | 4 +- .../api/GitHookTagProviderTest.java | 64 ++++++++++++++----- .../scm/repository/api/HgHookTagProvider.java | 2 +- .../repository/api/HgHookTagProviderTest.java | 8 ++- .../sonia/scm/api/v2/resources/TagDto.java | 2 + 7 files changed, 91 insertions(+), 32 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Tag.java b/scm-core/src/main/java/sonia/scm/repository/Tag.java index bdeec544a4..1c037e4764 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Tag.java +++ b/scm-core/src/main/java/sonia/scm/repository/Tag.java @@ -67,4 +67,15 @@ public final class Tag { this.revision = revision; this.date = date; } + + /** + * The date is retrieved in a best-effort fashion. + * In certain situations it might not be available. + * In these cases, this method returns null. + * + * @since 2.5.0 + */ + public Long getDate() { + return date; + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index 96e4266a5d..97fc29163a 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -392,27 +392,39 @@ public final class GitUtil /** * Method description * + * @param repository + * @param ref + * @return + * @throws IOException + * @since 2.5.0 + */ + public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, Ref ref) throws IOException { + try (RevWalk walk = new RevWalk(repository)) { + return GitUtil.getTagTime(repository, walk, ref); + } + } + + /** + * Method description * * @param repository * @param revWalk * @param ref - * * @return - * * @throws IOException - * * @since 2.5.0 */ public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, - RevWalk revWalk, Ref ref) - throws IOException - { + RevWalk revWalk, Ref ref) + throws IOException { + if (ref == null) { + return null; + } + ObjectId id = ref.getObjectId(); - if (id != null) - { - if (revWalk == null) - { + if (id != null) { + if (revWalk == null) { revWalk = new RevWalk(repository); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java index 5bc5069112..c1a333400b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java @@ -68,8 +68,8 @@ public class GitHookTagProvider implements HookTagProvider { LOG.debug("received ref name {} is not a tag", refName); } else { Long tagTime = null; - try (RevWalk walk = new RevWalk(repository)) { - tagTime = GitUtil.getTagTime(repository, walk, rc.getRef()); + try { + tagTime = GitUtil.getTagTime(repository, rc.getRef()); } catch (IOException e) { LOG.error("Could not read tag time", e); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java index d27f881a13..f40d71378c 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java @@ -26,6 +26,11 @@ package sonia.scm.repository.api; import com.google.common.collect.Lists; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.transport.ReceiveCommand; import org.junit.Before; import org.junit.Rule; @@ -33,13 +38,19 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.repository.GitUtil; import sonia.scm.repository.Tag; import java.util.List; import static org.hamcrest.Matchers.empty; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; /** @@ -48,16 +59,22 @@ import static org.mockito.Mockito.when; * @author Sebastian Sdorra */ @RunWith(MockitoJUnitRunner.class) -public class GitHookTagProviderTest { +public class GitHookTagProviderTest{ private static final String ZERO = ObjectId.zeroId().getName(); - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - @Mock private ReceiveCommand command; - + + @Mock + private Repository repository; + + @Mock + private Ref gitRef; + + @Mock + private ObjectId objectId; + private List commands; /** @@ -73,11 +90,21 @@ public class GitHookTagProviderTest { */ @Test public void testGetCreatedTags() { - String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; - GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/tags/1.0.0", revision, ZERO); - - assertTag("1.0.0", revision, provider.getCreatedTags()); - assertThat(provider.getDeletedTags(), empty()); + try (MockedStatic dummy = Mockito.mockStatic(GitUtil.class)) { + String revision = "86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1"; + Long timestamp = 1339416344000L; + String tagName = "test-tag"; + String ref = "refs/tags/" + tagName; + + dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(timestamp); + dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName); + dummy.when(() -> GitUtil.getId(ObjectId.fromString(revision))).thenReturn(revision); + + GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, ref, revision, ZERO); + + assertTag(tagName, revision, timestamp, provider.getCreatedTags()); + assertThat(provider.getDeletedTags(), empty()); + } } /** @@ -89,14 +116,14 @@ public class GitHookTagProviderTest { GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, "refs/tags/1.0.0", ZERO, revision); assertThat(provider.getCreatedTags(), empty()); - assertTag("1.0.0", revision, provider.getDeletedTags()); + assertTag("1.0.0", revision, null, provider.getDeletedTags()); } /** * Tests {@link GitHookTagProvider} with a branch ref instead of a tag. */ @Test - public void testWithBranch(){ + public void testWithBranch() { String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/heads/1.0.0", revision, revision); @@ -113,25 +140,28 @@ public class GitHookTagProviderTest { String oldId = "e0f2be968b147ff7043684a7715d2fe852553db4"; GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, "refs/tags/1.0.0", newId, oldId); - assertTag("1.0.0", newId, provider.getCreatedTags()); - assertTag("1.0.0", oldId, provider.getDeletedTags()); + assertTag("1.0.0", newId, null, provider.getCreatedTags()); + assertTag("1.0.0", oldId, null, provider.getDeletedTags()); } - private void assertTag(String name, String revision, List tags){ + private void assertTag(String name, String revision, Long date, List tags){ assertNotNull(tags); assertFalse(tags.isEmpty()); assertEquals(1, tags.size()); Tag tag = tags.get(0); assertEquals(name, tag.getName()); assertEquals(revision, tag.getRevision()); + assertEquals(date, tag.getDate()); } - private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId){ + private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId) { when(command.getNewId()).thenReturn(ObjectId.fromString(newId)); when(command.getOldId()).thenReturn(ObjectId.fromString(oldId)); when(command.getType()).thenReturn(type); when(command.getRefName()).thenReturn(ref); - return new GitHookTagProvider(commands); + when(command.getRef()).thenReturn(gitRef); + when(gitRef.getObjectId()).thenReturn(objectId); + return new GitHookTagProvider(commands, repository); } } diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java index ed140ee4b6..7b8ecef67d 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/api/HgHookTagProvider.java @@ -92,7 +92,7 @@ public class HgHookTagProvider implements HookTagProvider { if (tagNames != null){ for ( String tagName : tagNames ){ logger.trace("found tag {} at changeset {}", tagName, c.getId()); - tags.add(new Tag(tagName, c.getId())); + tags.add(new Tag(tagName, c.getId(), c.getDate())); } } } diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java index a2486732a0..f557fec51e 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java @@ -26,6 +26,8 @@ package sonia.scm.repository.api; import com.google.common.collect.Lists; import java.util.List; + +import org.assertj.core.api.Assertions; import org.junit.Test; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @@ -71,7 +73,7 @@ public class HgHookTagProviderTest { public void testGetCreatedTags(){ Changeset c1 = new Changeset("1", Long.MIN_VALUE, null); c1.getTags().add("1.0.0"); - Changeset c2 = new Changeset("2", Long.MIN_VALUE, null); + Changeset c2 = new Changeset("2", Long.MAX_VALUE, null); c2.getTags().add("2.0.0"); Changeset c3 = new Changeset("3", Long.MIN_VALUE, null); prepareChangesets(c1, c2, c3); @@ -83,10 +85,12 @@ public class HgHookTagProviderTest { Tag t1 = tags.get(0); assertEquals("1", t1.getRevision()); assertEquals("1.0.0", t1.getName()); - + Assertions.assertThat(t1.getDate()).isEqualTo(Long.MIN_VALUE); + Tag t2 = tags.get(1); assertEquals("2", t2.getRevision()); assertEquals("2.0.0", t2.getName()); + Assertions.assertThat(t2.getDate()).isEqualTo(Long.MAX_VALUE); } private void prepareChangesets(Changeset... changesets){ diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java index b7c6b3bacf..7d4fd6dada 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagDto.java @@ -24,6 +24,7 @@ package sonia.scm.api.v2.resources; +import com.fasterxml.jackson.annotation.JsonInclude; import de.otto.edison.hal.Embedded; import de.otto.edison.hal.HalRepresentation; import de.otto.edison.hal.Links; @@ -42,6 +43,7 @@ public class TagDto extends HalRepresentation { private String revision; + @JsonInclude(JsonInclude.Include.NON_NULL) private Instant date; TagDto(Links links, Embedded embedded) { From 8d613effeea14f2f6e9ee74189f48cebf3de912e Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Tue, 25 Aug 2020 18:10:03 +0200 Subject: [PATCH 3/9] update mockito dependency version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 189584d1fd..3110c769fc 100644 --- a/pom.xml +++ b/pom.xml @@ -903,7 +903,7 @@ - 2.28.2 + 3.5.6 2.1 5.6.2 From ecfc70eb77192cfc6c4ca28de97e30ea5be5d5a6 Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Wed, 26 Aug 2020 10:01:51 +0200 Subject: [PATCH 4/9] wip refactoring --- .../main/java/sonia/scm/repository/Tag.java | 6 +- .../api/GitHookTagProviderTest.java | 81 ++++++++++++------- .../repository/spi/GitTagsCommandTest.java | 4 +- .../repository/api/HgHookTagProviderTest.java | 4 +- .../scm/repository/spi/HgTagsCommandTest.java | 2 +- .../api/v2/resources/TagToTagDtoMapper.java | 5 +- 6 files changed, 62 insertions(+), 40 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Tag.java b/scm-core/src/main/java/sonia/scm/repository/Tag.java index 1c037e4764..0262888e55 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Tag.java +++ b/scm-core/src/main/java/sonia/scm/repository/Tag.java @@ -28,6 +28,8 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; +import java.util.Optional; + /** * Represents a tag in a repository. * @@ -75,7 +77,7 @@ public final class Tag { * * @since 2.5.0 */ - public Long getDate() { - return date; + public Optional getDate() { + return Optional.ofNullable(date); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java index f40d71378c..e0a83e1272 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java @@ -21,21 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.api; import com.google.common.collect.Lists; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.transport.ReceiveCommand; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockedStatic; @@ -47,19 +42,19 @@ import sonia.scm.repository.Tag; import java.util.List; import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.mockito.Mockito.when; /** * Unit tests for {@link GitHookTagProvider}. - * + * * @author Sebastian Sdorra */ @RunWith(MockitoJUnitRunner.class) -public class GitHookTagProviderTest{ +public class GitHookTagProviderTest { private static final String ZERO = ObjectId.zeroId().getName(); @@ -76,12 +71,12 @@ public class GitHookTagProviderTest{ private ObjectId objectId; private List commands; - + /** * Set up mocks for upcoming tests. */ @Before - public void setUpMocks(){ + public void setUpMocks() { commands = Lists.newArrayList(command); } @@ -93,7 +88,7 @@ public class GitHookTagProviderTest{ try (MockedStatic dummy = Mockito.mockStatic(GitUtil.class)) { String revision = "86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1"; Long timestamp = 1339416344000L; - String tagName = "test-tag"; + String tagName = "1.0.0"; String ref = "refs/tags/" + tagName; dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(timestamp); @@ -106,19 +101,29 @@ public class GitHookTagProviderTest{ assertThat(provider.getDeletedTags(), empty()); } } - + /** * Tests {@link GitHookTagProvider#getDeletedTags()}. */ @Test public void testGetDeletedTags() { - String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; - GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, "refs/tags/1.0.0", ZERO, revision); - - assertThat(provider.getCreatedTags(), empty()); - assertTag("1.0.0", revision, null, provider.getDeletedTags()); + try (MockedStatic dummy = Mockito.mockStatic(GitUtil.class)) { + String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; + Long timestamp = 1339416344000L; + String tagName = "1.0.0"; + String ref = "refs/tags/" + tagName; + + dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(timestamp); + dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName); + dummy.when(() -> GitUtil.getId(ObjectId.fromString(revision))).thenReturn(revision); + + GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, ref, ZERO, revision); + + assertThat(provider.getCreatedTags(), empty()); + assertTag("1.0.0", revision, 1339416344000L, provider.getDeletedTags()); + } } - + /** * Tests {@link GitHookTagProvider} with a branch ref instead of a tag. */ @@ -126,7 +131,7 @@ public class GitHookTagProviderTest{ public void testWithBranch() { String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/heads/1.0.0", revision, revision); - + assertThat(provider.getCreatedTags(), empty()); assertThat(provider.getDeletedTags(), empty()); } @@ -136,24 +141,38 @@ public class GitHookTagProviderTest{ */ @Test public void testUpdateTags() { - String newId = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; - String oldId = "e0f2be968b147ff7043684a7715d2fe852553db4"; + try (MockedStatic dummy = Mockito.mockStatic(GitUtil.class)) { + String newRevision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; + Long newTimestamp = 1339416344000L; + String newTagName = "1.0.0"; + String newRef = "refs/tags/" + newTagName; - GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, "refs/tags/1.0.0", newId, oldId); - assertTag("1.0.0", newId, null, provider.getCreatedTags()); - assertTag("1.0.0", oldId, null, provider.getDeletedTags()); + String oldRevision = "e0f2be968b147ff7043684a7715d2fe852553db4"; + String oldTagName = "0.9.0"; + + dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(newTimestamp); + dummy.when(() -> GitUtil.getTagName(newRef)).thenReturn(newTagName); + dummy.when(() -> GitUtil.getId(ObjectId.fromString(newRevision))).thenReturn(newRevision); + + dummy.when(() -> GitUtil.getId(ObjectId.fromString(oldRevision))).thenReturn(oldRevision); + + GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, newRef, newRevision, oldRevision); + + assertTag(newTagName, newRevision, newTimestamp, provider.getCreatedTags()); + assertTag(oldTagName, oldRevision, null, provider.getDeletedTags()); + } } - private void assertTag(String name, String revision, Long date, List tags){ + private void assertTag(String name, String revision, Long date, List tags) { assertNotNull(tags); assertFalse(tags.isEmpty()); assertEquals(1, tags.size()); Tag tag = tags.get(0); assertEquals(name, tag.getName()); assertEquals(revision, tag.getRevision()); - assertEquals(date, tag.getDate()); + assertEquals(date, tag.getDate().orElse(null)); } - + private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId) { when(command.getNewId()).thenReturn(ObjectId.fromString(newId)); when(command.getOldId()).thenReturn(ObjectId.fromString(oldId)); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java index afa3cc4930..3152b36040 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java @@ -55,9 +55,9 @@ public class GitTagsCommandTest extends AbstractGitCommandTestBase { final List tags = tagsCommand.getTags(); assertThat(tags).hasSize(2); assertThat(tags.get(0).getName()).isEqualTo("1.0.0"); - assertThat(tags.get(0).getDate()).isEqualTo(1598348105000L); // Annotated - Take tag date + assertThat(tags.get(0).getDate()).contains(1598348105000L); // Annotated - Take tag date assertThat(tags.get(1).getName()).isEqualTo("test-tag"); - assertThat(tags.get(1).getDate()).isEqualTo(1339416344000L); // Lightweight - Take commit date + assertThat(tags.get(1).getDate()).contains(1339416344000L); // Lightweight - Take commit date } @Override diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java index f557fec51e..18e7fb8c48 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java @@ -85,12 +85,12 @@ public class HgHookTagProviderTest { Tag t1 = tags.get(0); assertEquals("1", t1.getRevision()); assertEquals("1.0.0", t1.getName()); - Assertions.assertThat(t1.getDate()).isEqualTo(Long.MIN_VALUE); + Assertions.assertThat(t1.getDate()).contains(Long.MIN_VALUE); Tag t2 = tags.get(1); assertEquals("2", t2.getRevision()); assertEquals("2.0.0", t2.getName()); - Assertions.assertThat(t2.getDate()).isEqualTo(Long.MAX_VALUE); + Assertions.assertThat(t2.getDate()).contains(Long.MAX_VALUE); } private void prepareChangesets(Changeset... changesets){ diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java index f9d3fdd5b2..e9bb65bf89 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/spi/HgTagsCommandTest.java @@ -39,7 +39,7 @@ public class HgTagsCommandTest extends AbstractHgCommandTestBase { final List tags = hgTagsCommand.getTags(); assertThat(tags).hasSize(1); assertThat(tags.get(0).getName()).isEqualTo("tip"); - assertThat(tags.get(0).getDate()).isEqualTo(1339586381000L); + assertThat(tags.get(0).getDate()).contains(1339586381000L); } } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java index 532eb9be85..940b33dd05 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/TagToTagDtoMapper.java @@ -38,6 +38,7 @@ import sonia.scm.web.EdisonHalAppender; import javax.inject.Inject; import java.time.Instant; +import java.util.Optional; import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Link.link; @@ -67,7 +68,7 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper { } @Named("mapDate") - Instant map(Long value) { - return value == null ? null : Instant.ofEpochMilli(value); + Instant map(Optional value) { + return value.map(Instant::ofEpochMilli).orElse(null); } } From 15fac8ba2c7518a3780ab154e636f0c0e7ef3769 Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Wed, 26 Aug 2020 15:06:00 +0200 Subject: [PATCH 5/9] remove ref usage and directly use objectId --- .../main/java/sonia/scm/repository/Tag.java | 2 +- .../java/sonia/scm/repository/GitUtil.java | 28 ++------ .../repository/api/GitHookTagProvider.java | 26 ++++---- .../scm/repository/spi/GitTagsCommand.java | 2 +- .../api/GitHookTagProviderTest.java | 64 ++++++++++++------- .../repository/client/spi/GitTagCommand.java | 2 +- 6 files changed, 59 insertions(+), 65 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Tag.java b/scm-core/src/main/java/sonia/scm/repository/Tag.java index 0262888e55..49f81c7516 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Tag.java +++ b/scm-core/src/main/java/sonia/scm/repository/Tag.java @@ -73,7 +73,7 @@ public final class Tag { /** * The date is retrieved in a best-effort fashion. * In certain situations it might not be available. - * In these cases, this method returns null. + * In these cases, this method returns an empty optional. * * @since 2.5.0 */ diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index 97fc29163a..5426422def 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -390,45 +390,27 @@ public final class GitUtil } /** - * Method description - * - * @param repository - * @param ref - * @return - * @throws IOException * @since 2.5.0 */ - public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, Ref ref) throws IOException { + public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, ObjectId objectId) throws IOException { try (RevWalk walk = new RevWalk(repository)) { - return GitUtil.getTagTime(repository, walk, ref); + return GitUtil.getTagTime(repository, walk, objectId); } } /** - * Method description - * - * @param repository - * @param revWalk - * @param ref - * @return - * @throws IOException * @since 2.5.0 */ public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, - RevWalk revWalk, Ref ref) + RevWalk revWalk, ObjectId objectId) throws IOException { - if (ref == null) { - return null; - } - ObjectId id = ref.getObjectId(); - - if (id != null) { + if (objectId != null) { if (revWalk == null) { revWalk = new RevWalk(repository); } - final RevObject revObject = revWalk.parseAny(id); + final RevObject revObject = revWalk.parseAny(objectId); if (revObject instanceof RevTag) { return ((RevTag) revObject).getTaggerIdent().getWhen().getTime(); } else if (revObject instanceof RevCommit) { diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java index c1a333400b..7889badc30 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/api/GitHookTagProvider.java @@ -26,18 +26,16 @@ package sonia.scm.repository.api; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; - -import java.io.IOException; -import java.util.List; - import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.GitUtil; import sonia.scm.repository.Tag; +import java.io.IOException; +import java.util.List; + /** * Git provider implementation of {@link HookTagProvider}. * @@ -67,20 +65,18 @@ public class GitHookTagProvider implements HookTagProvider { if (Strings.isNullOrEmpty(tag)) { LOG.debug("received ref name {} is not a tag", refName); } else { - Long tagTime = null; try { - tagTime = GitUtil.getTagTime(repository, rc.getRef()); + if (isCreate(rc)) { + createdTagBuilder.add(createTagFromNewId(rc, tag, GitUtil.getTagTime(repository, rc.getNewId()))); + } else if (isDelete(rc)) { + deletedTagBuilder.add(createTagFromOldId(rc, tag, GitUtil.getTagTime(repository, rc.getOldId()))); + } else if (isUpdate(rc)) { + createdTagBuilder.add(createTagFromNewId(rc, tag, GitUtil.getTagTime(repository, rc.getNewId()))); + deletedTagBuilder.add(createTagFromOldId(rc, tag, GitUtil.getTagTime(repository, rc.getOldId()))); + } } catch (IOException e) { LOG.error("Could not read tag time", e); } - if (isCreate(rc)) { - createdTagBuilder.add(createTagFromNewId(rc, tag, tagTime)); - } else if (isDelete(rc)) { - deletedTagBuilder.add(createTagFromOldId(rc, tag, tagTime)); - } else if (isUpdate(rc)) { - createdTagBuilder.add(createTagFromNewId(rc, tag, tagTime)); - deletedTagBuilder.add(createTagFromOldId(rc, tag, tagTime)); - } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java index 97b9c91be1..b8c000ed03 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java @@ -132,7 +132,7 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand { if (revObject != null) { String name = GitUtil.getTagName(ref); - tag = new Tag(name, revObject.getId().name(), GitUtil.getTagTime(repository, revWalk, ref)); + tag = new Tag(name, revObject.getId().name(), GitUtil.getTagTime(repository, revWalk, ref.getObjectId())); } } catch (IOException ex) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java index e0a83e1272..ccf21b83c9 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/api/GitHookTagProviderTest.java @@ -26,7 +26,6 @@ package sonia.scm.repository.api; import com.google.common.collect.Lists; import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ReceiveCommand; import org.junit.Before; @@ -64,12 +63,6 @@ public class GitHookTagProviderTest { @Mock private Repository repository; - @Mock - private Ref gitRef; - - @Mock - private ObjectId objectId; - private List commands; /** @@ -91,7 +84,7 @@ public class GitHookTagProviderTest { String tagName = "1.0.0"; String ref = "refs/tags/" + tagName; - dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(timestamp); + dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(revision))).thenReturn(timestamp); dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName); dummy.when(() -> GitUtil.getId(ObjectId.fromString(revision))).thenReturn(revision); @@ -113,7 +106,7 @@ public class GitHookTagProviderTest { String tagName = "1.0.0"; String ref = "refs/tags/" + tagName; - dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(timestamp); + dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(revision))).thenReturn(timestamp); dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName); dummy.when(() -> GitUtil.getId(ObjectId.fromString(revision))).thenReturn(revision); @@ -137,29 +130,54 @@ public class GitHookTagProviderTest { } /** - * Tests {@link GitHookTagProvider} with update command. + * Tests {@link GitHookTagProvider} with update command pre receive. */ @Test - public void testUpdateTags() { + public void testUpdateTagsPreReceive() { try (MockedStatic dummy = Mockito.mockStatic(GitUtil.class)) { - String newRevision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; - Long newTimestamp = 1339416344000L; - String newTagName = "1.0.0"; - String newRef = "refs/tags/" + newTagName; - String oldRevision = "e0f2be968b147ff7043684a7715d2fe852553db4"; - String oldTagName = "0.9.0"; + String newRevision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; - dummy.when(() -> GitUtil.getTagTime(repository, gitRef)).thenReturn(newTimestamp); - dummy.when(() -> GitUtil.getTagName(newRef)).thenReturn(newTagName); + Long timestamp = 1339416344000L; + String tagName = "1.0.0"; + String ref = "refs/tags/" + tagName; + + dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(oldRevision))).thenReturn(timestamp); + dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(newRevision))).thenReturn(null); + dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName); + dummy.when(() -> GitUtil.getId(ObjectId.fromString(oldRevision))).thenReturn(oldRevision); dummy.when(() -> GitUtil.getId(ObjectId.fromString(newRevision))).thenReturn(newRevision); + GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, ref, newRevision, oldRevision); + + assertTag(tagName, newRevision, null, provider.getCreatedTags()); + assertTag(tagName, oldRevision, timestamp, provider.getDeletedTags()); + } + } + + /** + * Tests {@link GitHookTagProvider} with update command post receive. + */ + @Test + public void testUpdateTagsPostReceive() { + try (MockedStatic dummy = Mockito.mockStatic(GitUtil.class)) { + String oldRevision = "e0f2be968b147ff7043684a7715d2fe852553db4"; + String newRevision = "b2002b64013e54b78eac251df0672bd5d6a83aa7"; + + Long timestamp = 1339416344000L; + String tagName = "1.0.0"; + String ref = "refs/tags/" + tagName; + + dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(newRevision))).thenReturn(timestamp); + dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(oldRevision))).thenReturn(null); + dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName); dummy.when(() -> GitUtil.getId(ObjectId.fromString(oldRevision))).thenReturn(oldRevision); + dummy.when(() -> GitUtil.getId(ObjectId.fromString(newRevision))).thenReturn(newRevision); - GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, newRef, newRevision, oldRevision); + GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, ref, newRevision, oldRevision); - assertTag(newTagName, newRevision, newTimestamp, provider.getCreatedTags()); - assertTag(oldTagName, oldRevision, null, provider.getDeletedTags()); + assertTag(tagName, newRevision, timestamp, provider.getCreatedTags()); + assertTag(tagName, oldRevision, null, provider.getDeletedTags()); } } @@ -178,8 +196,6 @@ public class GitHookTagProviderTest { when(command.getOldId()).thenReturn(ObjectId.fromString(oldId)); when(command.getType()).thenReturn(type); when(command.getRefName()).thenReturn(ref); - when(command.getRef()).thenReturn(gitRef); - when(gitRef.getObjectId()).thenReturn(objectId); return new GitHookTagProvider(commands, repository); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java index 19b93d0775..28664c463d 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java @@ -89,7 +89,7 @@ public class GitTagCommand implements TagCommand { walk = new RevWalk(git.getRepository()); revObject = walk.parseAny(id); - tagTime = GitUtil.getTagTime(git.getRepository(), walk, GitUtil.getRefForCommit(git.getRepository(), id)); + tagTime = GitUtil.getTagTime(git.getRepository(), walk, id); } finally { From bf1ff26f19352914d85343e8e396dc651d0a6848 Mon Sep 17 00:00:00 2001 From: Konstantin Schaper Date: Wed, 26 Aug 2020 15:35:37 +0200 Subject: [PATCH 6/9] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d8fd79db9..e5e7320d5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Tags now have date information attached ([#1305](https://github.com/scm-manager/scm-manager/pull/1305)) + ## [2.4.0] - 2020-08-14 ### Added - Introduced merge detection for receive hooks ([#1278](https://github.com/scm-manager/scm-manager/pull/1278)) From 565ec3ff3c089fcb3db3bcc54bfd48aab48e6a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 27 Aug 2020 10:48:54 +0200 Subject: [PATCH 7/9] Cleanup --- .../java/sonia/scm/repository/GitUtil.java | 11 ++----- .../scm/repository/spi/GitTagsCommand.java | 2 +- .../repository/client/spi/GitTagCommand.java | 4 +-- .../repository/api/HgHookTagProviderTest.java | 31 ++++++++++--------- 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index 5426422def..6baa48111b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -394,22 +394,15 @@ public final class GitUtil */ public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, ObjectId objectId) throws IOException { try (RevWalk walk = new RevWalk(repository)) { - return GitUtil.getTagTime(repository, walk, objectId); + return GitUtil.getTagTime(walk, objectId); } } /** * @since 2.5.0 */ - public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, - RevWalk revWalk, ObjectId objectId) - throws IOException { - + public static Long getTagTime(RevWalk revWalk, ObjectId objectId) throws IOException { if (objectId != null) { - if (revWalk == null) { - revWalk = new RevWalk(repository); - } - final RevObject revObject = revWalk.parseAny(objectId); if (revObject instanceof RevTag) { return ((RevTag) revObject).getTaggerIdent().getWhen().getTime(); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java index b8c000ed03..bc0a8c95a8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java @@ -132,7 +132,7 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand { if (revObject != null) { String name = GitUtil.getTagName(ref); - tag = new Tag(name, revObject.getId().name(), GitUtil.getTagTime(repository, revWalk, ref.getObjectId())); + tag = new Tag(name, revObject.getId().name(), GitUtil.getTagTime(revWalk, ref.getObjectId())); } } catch (IOException ex) { diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java index 28664c463d..8c4eb3f89f 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitTagCommand.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.client.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -89,7 +89,7 @@ public class GitTagCommand implements TagCommand { walk = new RevWalk(git.getRepository()); revObject = walk.parseAny(id); - tagTime = GitUtil.getTagTime(git.getRepository(), walk, id); + tagTime = GitUtil.getTagTime(walk, id); } finally { diff --git a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java index 18e7fb8c48..b756b4097d 100644 --- a/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java +++ b/scm-plugins/scm-hg-plugin/src/test/java/sonia/scm/repository/api/HgHookTagProviderTest.java @@ -21,17 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.api; import com.google.common.collect.Lists; -import java.util.List; - -import org.assertj.core.api.Assertions; import org.junit.Test; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import static org.hamcrest.Matchers.*; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -43,9 +37,16 @@ import sonia.scm.repository.spi.HookChangesetProvider; import sonia.scm.repository.spi.HookChangesetRequest; import sonia.scm.repository.spi.HookChangesetResponse; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + /** * Unit tests for {@link HgHookTagProvider}. - * + * * @author Sebastian Sdorra */ @RunWith(MockitoJUnitRunner.class) @@ -53,7 +54,7 @@ public class HgHookTagProviderTest { @Mock private HookChangesetProvider changesetProvider; - + @InjectMocks private HgHookTagProvider tagProvider; @@ -63,9 +64,9 @@ public class HgHookTagProviderTest { @Test public void testGetDeletedTags() { prepareChangesets(new Changeset("1", Long.MIN_VALUE, null)); - assertThat(tagProvider.getDeletedTags(), empty()); + assertThat(tagProvider.getDeletedTags()).isEmpty(); } - + /** * Tests {@link HgHookTagProvider#getCreatedTags()}. */ @@ -77,20 +78,20 @@ public class HgHookTagProviderTest { c2.getTags().add("2.0.0"); Changeset c3 = new Changeset("3", Long.MIN_VALUE, null); prepareChangesets(c1, c2, c3); - + List tags = tagProvider.getCreatedTags(); assertNotNull(tags); assertEquals(2, tags.size()); - + Tag t1 = tags.get(0); assertEquals("1", t1.getRevision()); assertEquals("1.0.0", t1.getName()); - Assertions.assertThat(t1.getDate()).contains(Long.MIN_VALUE); + assertThat(t1.getDate()).contains(Long.MIN_VALUE); Tag t2 = tags.get(1); assertEquals("2", t2.getRevision()); assertEquals("2.0.0", t2.getName()); - Assertions.assertThat(t2.getDate()).contains(Long.MAX_VALUE); + assertThat(t2.getDate()).contains(Long.MAX_VALUE); } private void prepareChangesets(Changeset... changesets){ From 96d16fe4cce7901eacfb727cdfa5bef330891930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 27 Aug 2020 11:15:13 +0200 Subject: [PATCH 8/9] "Unpeel" annotated tags to get correct changeset If we just parse the object id of the reference for annotated tags, we get the annotated tag object and not the reference the tag refers to. --- .../sonia/scm/repository/spi/GitTagsCommand.java | 2 +- .../scm/repository/spi/GitTagsCommandTest.java | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java index bc0a8c95a8..60b1105547 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitTagsCommand.java @@ -127,7 +127,7 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand { Tag tag = null; try { - RevObject revObject = revWalk.parseAny(ref.getObjectId()); + RevObject revObject = GitUtil.getCommit(repository, revWalk, ref); if (revObject != null) { String name = GitUtil.getTagName(ref); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java index 3152b36040..9112aa3133 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitTagsCommandTest.java @@ -54,10 +54,16 @@ public class GitTagsCommandTest extends AbstractGitCommandTestBase { final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext); final List tags = tagsCommand.getTags(); assertThat(tags).hasSize(2); - assertThat(tags.get(0).getName()).isEqualTo("1.0.0"); - assertThat(tags.get(0).getDate()).contains(1598348105000L); // Annotated - Take tag date - assertThat(tags.get(1).getName()).isEqualTo("test-tag"); - assertThat(tags.get(1).getDate()).contains(1339416344000L); // Lightweight - Take commit date + + Tag annotatedTag = tags.get(0); + assertThat(annotatedTag.getName()).isEqualTo("1.0.0"); + assertThat(annotatedTag.getDate()).contains(1598348105000L); // Annotated - Take tag date + assertThat(annotatedTag.getRevision()).isEqualTo("fcd0ef1831e4002ac43ea539f4094334c79ea9ec"); + + Tag lightweightTag = tags.get(1); + assertThat(lightweightTag.getName()).isEqualTo("test-tag"); + assertThat(lightweightTag.getDate()).contains(1339416344000L); // Lightweight - Take commit date + assertThat(lightweightTag.getRevision()).isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1"); } @Override From 4d4834d115bfda61ca9c8074e743694f95b87838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Thu, 27 Aug 2020 11:30:04 +0200 Subject: [PATCH 9/9] Add some more documentation --- .../src/main/java/sonia/scm/repository/Tag.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Tag.java b/scm-core/src/main/java/sonia/scm/repository/Tag.java index 49f81c7516..cdfac1c0e6 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Tag.java +++ b/scm-core/src/main/java/sonia/scm/repository/Tag.java @@ -71,9 +71,18 @@ public final class Tag { } /** - * The date is retrieved in a best-effort fashion. - * In certain situations it might not be available. - * In these cases, this method returns an empty optional. + * Depending on the underlying source code management system + * (like git or hg) and depending on the type of this tag + * (for example git has lightweight and annotated + * tags), this date has different meaning. For annotated tags + * in git, this is the date the tag was created. In other cases + * (for lightweight tags in git or all tags in hg) this is the + * date of the referenced changeset. + *

+ * Please note, that the date is retrieved in a best-effort fashion. + * In certain situations (for example if this tag is announced in + * a pre or post receive hook), it might not be available. + * In these cases, this method returns an empty {@link Optional}. * * @since 2.5.0 */