Skip to content

Commit

Permalink
Support custom tag parser/writer
Browse files Browse the repository at this point in the history
Not everyone likes the convention of tags being just the raw version
strings. Additionally, users with monorepos often have different version
needs in different projects, where they might want to tag versions like
"project-a/1.2.3" to provide different namespaces for different
projects.

This now exposes that functionality through the ReckonExtension.

Fixes #54
  • Loading branch information
ajoberstar committed Feb 12, 2022
1 parent e2307fa commit 998be79
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 31 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ reckon {
stageFromProp('milestone', 'rc', 'final')
// alternative to stageFromProp
// snapshotFromProp()
// omit this to use the default of parsing tag names of the form 1.2.3 or v1.2.3
// this is a String to Optional<Version> function
// return an empty optional for tags you don't consider a relevant version
tagParser = tagName -> java.util.Optional.of(tagName)
.filter(name -> name.startsWith("project-a/"))
.map(name -> name.replace("project-a/", ""))
.flatMap(name -> org.ajoberstar.reckon.core.Version.parse(name))
// omit this to use the default of writing tag names of the form 1.2.3
// this is a Version to String function
tagWriter = version -> "project-a/" + version
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -39,18 +38,11 @@ final class GitInventorySupplier implements VcsInventorySupplier {
private static final Logger logger = LoggerFactory.getLogger(GitInventorySupplier.class);

private final Repository repo;
private final Function<Ref, Optional<Version>> tagParser;
private final VersionTagParser tagParser;

public GitInventorySupplier(Repository repo) {
this(repo, tagName -> Optional.of(tagName.replaceAll("^v", "")));
}

public GitInventorySupplier(Repository repo, Function<String, Optional<String>> tagSelector) {
public GitInventorySupplier(Repository repo, VersionTagParser tagParser) {
this.repo = repo;
this.tagParser = ref -> {
String tagName = Repository.shortenRefName(ref.getName());
return tagSelector.apply(tagName).flatMap(Version::parse);
};
this.tagParser = tagParser;
}

@Override
Expand All @@ -60,7 +52,7 @@ public VcsInventory getInventory() {
// saves on some performance as we don't really need the commit bodys
walk.setRetainBody(false);

ObjectId headObjectId = repo.getRefDatabase().getRef("HEAD").getObjectId();
ObjectId headObjectId = repo.getRefDatabase().findRef("HEAD").getObjectId();

if (headObjectId == null) {
logger.debug("No HEAD commit. Presuming repo is empty.");
Expand Down Expand Up @@ -88,8 +80,7 @@ public VcsInventory getInventory() {
Set<RevCommit> taggedCommits = taggedVersions.stream().map(TaggedVersion::getCommit).collect(Collectors.toSet());
Set<Version> parallelVersions = parallelCandidates.stream()
.map(version -> findParallel(walk, headCommit, version, taggedCommits))
// TODO Java 9 Optional::stream
.flatMap(opt -> opt.isPresent() ? Stream.of(opt.get()) : Stream.empty())
.flatMap(Optional::stream)
.collect(Collectors.toSet());

Set<Version> claimedVersions = taggedVersions.stream().map(TaggedVersion::getVersion).collect(Collectors.toSet());
Expand Down Expand Up @@ -121,12 +112,15 @@ private boolean isClean() {
private Set<TaggedVersion> getTaggedVersions(RevWalk walk) throws IOException {
Set<TaggedVersion> versions = new HashSet<>();

for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_TAGS).values()) {
Ref tag = repo.peel(ref);
for (Ref ref : repo.getRefDatabase().getRefsByPrefix(Constants.R_TAGS)) {

Ref tag = repo.getRefDatabase().peel(ref);
// only annotated tags return a peeled object id
ObjectId objectId = tag.getPeeledObjectId() == null ? tag.getObjectId() : tag.getPeeledObjectId();
RevCommit commit = walk.parseCommit(objectId);
tagParser.apply(tag).ifPresent(version -> {

String tagName = Repository.shortenRefName(ref.getName());
tagParser.parse(tagName).ifPresent(version -> {
versions.add(new TaggedVersion(version, commit));
});
}
Expand Down Expand Up @@ -198,9 +192,7 @@ private Optional<Version> findParallel(RevWalk walk, RevCommit head, TaggedVersi
walk.reset();
walk.setRevFilter(RevFilter.ALL);
boolean taggedSinceMergeBase = RevWalkUtils.find(walk, head, mergeBase).stream()
.filter(tagged::contains)
.findAny()
.isPresent();
.anyMatch(tagged::contains);

if (mergeBase != null
&& !taggedSinceMergeBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,22 @@ Builder vcs(VcsInventorySupplier inventorySupplier) {
* @return this builder
*/
public Builder git(Repository repo) {
return git(repo, null);
}

/**
* Use the given JGit repository to infer the state of Git.
*
* @param repo repository that the version should be inferred from
* @param tagParser a parser used to find versions from tag names
* @return this builder
*/
public Builder git(Repository repo, VersionTagParser tagParser) {
if (repo == null) {
this.inventorySupplier = () -> new VcsInventory(null, false, null, null, null, 0, Collections.emptySet(), Collections.emptySet());
} else {
this.inventorySupplier = new GitInventorySupplier(repo);
var realParser = Optional.ofNullable(tagParser).orElse(VersionTagParser.getDefault());
this.inventorySupplier = new GitInventorySupplier(repo, realParser);
}
return this;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.ajoberstar.reckon.core;

import java.util.Optional;

@FunctionalInterface
public interface VersionTagParser {
Optional<Version> parse(String tagName);

static VersionTagParser getDefault() {
return tagName -> Version.parse(tagName.replaceAll("^v", ""));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.ajoberstar.reckon.core;

@FunctionalInterface
public interface VersionTagWriter {
String write(Version version);

static VersionTagWriter getDefault() {
return Version::toString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class GitInventorySupplierTest extends Specification {
def 'if no commits, all results are empty'() {
given:
def emptyGrgit = Grgit.init(dir: Files.createTempDirectory('repo2').toFile())
def emptySupplier = new GitInventorySupplier(emptyGrgit.repository.jgit.repository)
def emptySupplier = new GitInventorySupplier(emptyGrgit.repository.jgit.repository, VersionTagParser.getDefault())
expect:
emptySupplier.getInventory() == new VcsInventory(null, true, null, null, null, 0, null, null)
}
Expand Down Expand Up @@ -230,7 +230,7 @@ class GitInventorySupplierTest extends Specification {
}

def setup() {
supplier = new GitInventorySupplier(grgit.repository.jgit.repository)
supplier = new GitInventorySupplier(grgit.repository.jgit.repository, VersionTagParser.getDefault())
}

private void commit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class BaseCompatTest extends Specification {
remote.add(patterns: ['.'])
remote.commit(message: 'first commit')
remote.tag.add(name: '1.0.0', message: '1.0.0')
remote.tag.add(name: 'project-a/9.0.0', message: '9.0.0')
remoteFile('master.txt') << 'contents here2'
remote.add(patterns: ['.'])
remote.commit(message: 'second commit')
Expand Down Expand Up @@ -151,6 +152,38 @@ reckon {
remote.tag.list().find { it.name == '1.1.0-alpha.1' }
}

def 'tag parser/writer can be overridden and reckoned version is significant tag created and pushed'() {
given:
def local = Grgit.clone(dir: projectDir, uri: remote.repository.rootDir)

buildFile << """
plugins {
id 'org.ajoberstar.reckon'
}
reckon {
scopeFromProp()
stageFromProp('alpha','beta', 'final')
tagParser = tagName -> java.util.Optional.of(tagName)
.filter(name -> name.startsWith("project-a/"))
.map(name -> name.replace("project-a/", ""))
.flatMap(name -> org.ajoberstar.reckon.core.Version.parse(name))
tagWriter = version -> "project-a/" + version
}
"""
local.add(patterns: ['build.gradle'])
local.commit(message: 'Build file')
when:
def result = build('reckonTagPush', '-Preckon.stage=alpha', '--configuration-cache')
then:
result.output.contains('Reckoned version: 9.1.0-alpha.1')
result.task(':reckonTagCreate').outcome == TaskOutcome.SUCCESS
result.task(':reckonTagPush').outcome == TaskOutcome.SUCCESS
and:
remote.tag.list().find { it.name == 'project-a/9.1.0-alpha.1' }
}

def 'if reckoned version is rebuild, skip tag create, but push'() {
given:
def local = Grgit.clone(dir: projectDir, uri: remote.repository.rootDir)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import org.ajoberstar.grgit.gradle.GrgitService;
import org.ajoberstar.reckon.core.Version;
import org.ajoberstar.reckon.core.VersionTagWriter;
import org.gradle.api.DefaultTask;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
Expand All @@ -16,28 +17,31 @@
public class ReckonCreateTagTask extends DefaultTask {
private Property<GrgitService> grgitService;
private Property<Version> version;
private Property<VersionTagWriter> tagWriter;

@Inject
public ReckonCreateTagTask(ObjectFactory objectFactory) {
this.grgitService = objectFactory.property(GrgitService.class);
this.version = objectFactory.property(Version.class);
this.tagWriter = objectFactory.property(VersionTagWriter.class);
}

@TaskAction
public void create() {
var git = grgitService.get().getGrgit();;
var v = version.get().toString();
var tagName = tagWriter.get().write(version.get());
var versionString = version.get().toString();

// rebuilds shouldn't trigger a new tag
boolean alreadyTagged = git.getTag().list().stream()
.anyMatch(tag -> tag.getName().equals(v));
.anyMatch(tag -> tag.getName().equals(tagName));

if (alreadyTagged || !version.get().isSignificant()) {
setDidWork(false);
} else {
git.getTag().add(op -> {
op.setName(version.get().toString());
op.setMessage(version.get().toString());
op.setName(tagName);
op.setMessage(versionString);
});
setDidWork(true);
}
Expand All @@ -52,4 +56,9 @@ public Property<GrgitService> getGrgitService() {
public Property<Version> getVersion() {
return version;
}

@Input
public Property<VersionTagWriter> getTagWriter() {
return tagWriter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.ajoberstar.grgit.gradle.GrgitService;
import org.ajoberstar.reckon.core.Reckoner;
import org.ajoberstar.reckon.core.Version;
import org.ajoberstar.reckon.core.VersionTagParser;
import org.ajoberstar.reckon.core.VersionTagWriter;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.model.ObjectFactory;
Expand All @@ -23,6 +25,11 @@ public class ReckonExtension {
private final Property<String> stage;
private final Property<Version> version;

private VersionTagParser tagParser;
private VersionTagWriter tagWriter;
private Provider<VersionTagParser> tagParserProvider;
private Provider<VersionTagWriter> tagWriterProvider;

@Inject
public ReckonExtension(ObjectFactory objectFactory, ProviderFactory providerFactory) {
this.reckoner = Reckoner.builder();
Expand All @@ -35,6 +42,11 @@ public ReckonExtension(ObjectFactory objectFactory, ProviderFactory providerFact
this.version.set(versionProvider);
this.version.disallowChanges();
this.version.finalizeValueOnRead();

this.tagParser = null;
this.tagWriter = null;
this.tagParserProvider = providerFactory.provider(() -> this.tagParser);
this.tagWriterProvider = providerFactory.provider(() -> this.tagWriter);
}

public ReckonExtension scopeFromProp() {
Expand All @@ -54,6 +66,22 @@ public ReckonExtension snapshotFromProp() {
return this;
}

public Provider<VersionTagParser> getTagParser() {
return tagParserProvider;
}

public void setTagParser(VersionTagParser tagParser) {
this.tagParser = tagParser;
}

public Provider<VersionTagWriter> getTagWriter() {
return tagWriterProvider;
}

public void setTagWriter(VersionTagWriter tagWriter) {
this.tagWriter = tagWriter;
}

public Provider<Version> getVersion() {
return version;
}
Expand All @@ -74,7 +102,7 @@ private Version reckonVersion() {
try {
var git = grgitService.get().getGrgit();
var repo = git.getRepository().getJgit().getRepository();
reckoner.git(repo);
reckoner.git(repo, tagParser);
} catch (Exception e) {
// no git repo found
reckoner.git(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.ajoberstar.grgit.gradle.GrgitServiceExtension;
import org.ajoberstar.reckon.core.Version;
import org.ajoberstar.reckon.core.VersionTagParser;
import org.ajoberstar.reckon.core.VersionTagWriter;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.provider.Provider;
Expand All @@ -26,13 +28,15 @@ public void apply(Project project) {

var extension = project.getExtensions().create("reckon", ReckonExtension.class);
extension.getGrgitService().set(grgitService);
extension.setTagParser(VersionTagParser.getDefault());
extension.setTagWriter(VersionTagWriter.getDefault());

// composite builds have a parent Gradle build and can't trust the values of these properties
if (project.getGradle().getParent() == null) {
extension.getScope().set(project.getProviders().gradleProperty(SCOPE_PROP).forUseAtConfigurationTime());
extension.getStage().set(project.getProviders().gradleProperty(STAGE_PROP).forUseAtConfigurationTime());
}


var sharedVersion = new DelayedVersion(extension.getVersion());
project.allprojects(prj -> {
prj.setVersion(sharedVersion);
Expand All @@ -49,6 +53,7 @@ private TaskProvider<ReckonCreateTagTask> createTagTask(Project project, ReckonE
task.setGroup("publishing");
task.getGrgitService().set(extension.getGrgitService());
task.getVersion().set(extension.getVersion());
task.getTagWriter().set(extension.getTagWriter());
});
}

Expand All @@ -58,6 +63,7 @@ private TaskProvider<ReckonPushTagTask> createPushTask(Project project, ReckonEx
task.setGroup("publishing");
task.getGrgitService().set(extension.getGrgitService());
task.getVersion().set(extension.getVersion());
task.getTagWriter().set(extension.getTagWriter());
});
}

Expand Down
Loading

0 comments on commit 998be79

Please sign in to comment.