Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the grgit-service plugin #354

Merged
merged 2 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Introduce the grgit-service plugin
The org.ajoberstar.grgit-service is the new underlying behavior for
org.ajoberstar.grgit, building on the work from @runningcode and
@abelom.

The service plugin only registers a GrgitService, but leaves it up to
plugins (or builds) to grab the service and try to use it.

Alternatively, the existing grgit plugin applies the service plugin and
eagerly resolves it to provide the prior grgit extension property.

A breaking change is that any project which wants to access a
pre-initialized grgit instance now must apply the grgit plugin. This
reduces the amount of cross-project logic going on, which Gradle has
discouraged for a while (but can be hard to avoid).

Some plugins may have use cases to register their own GrgitService
instances that are used for their own behavior (the gradle-git-publish
plugin will take advantage of this once it upgrades to use grgit 5).

All projects using grgit-service plugin will share a Grgit instance,
which is also controlled to avoid concurrent access. (That doesn't
affect other plugins/builds registering their own GrgitService
instances).
  • Loading branch information
ajoberstar committed Feb 8, 2022
commit 2aba52c87eec9378fbdaf7b8a81bd5191022e8b7
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,78 @@ It also provides a Gradle plugin to easily get a Grgit instance for the build's
- [Documentation Site](http://ajoberstar.org/grgit/index.html)
- [Release Notes](https://github.com/ajoberstar/grgit/releases)

## Simple Usage in Gradle

Apply the `org.ajoberstar.grgit` plugin in any project that needs to access a `Grgit` instance.

NOTE: This plugin eagerly opens a Grgit instance, which may not be needed depending on the tasks you want to run. If this is not desired, see the next section.

```
plugins {
id 'org.ajoberstar.grgit' version '<version>'
}

// adds a grgit property to the project (will silently be null if there's no git repo)
tasks.register("describe") {
doFirst {
println grgit.describe()
}
}
```

## More Performant Usage in Gradle

Apply the `org.ajoberstar.grgit-service` plugin instead of `org.ajoberstar.grgit` to avoid eagerly resolving the `Grgit` instance. This works best with custom tasks that accept a `Property<GrgitService>`.

This approach ensures you only open a `Grgit` instance when a task is run that uses it.

```
import org.ajoberstar.grgit.gradle.GrgitService

plugins {
id 'org.ajoberstar.grgit-service' version '<version>'
}

tasks.register("describe", DescribeTask) {
service = grgitService.service
}

class DescribeTask extends DefaultTask {
@Input
final Property<GrgitService> service

@Inject
DoStuffTask(ObjectFactory objectFactory) {
this.service = objectFactory.property(GrgitService.class);
}

@TaskAction
void execute() {
println service.get().grgit.describe()
}
}
```

### Custom Gradle Plugins

If you are writing a custom Gradle plugin, you'll want to use one or both of the following approaches:

- If you need a `Grgit` instance representing the repository the project is in, use `org.ajoberstar.grgit-service` and use the `GrgitServiceExtension` to access the shared `GrgitService`. Wire this into any tasks or whatever needs to use the service via `Property<GrgitService>` for full lazy evaluation benefits.
- If you need a `Grgit` instance that's separate from the project's repository, declare your own `GrgitService` naming it something _not_ prefixed with `grgit*`.

```
Provider<GrgitService> serviceProvider = project.getGradle().getSharedServices().registerIfAbsent("grgit", GrgitService.class, spec -> {
// use getCurrentDirectory() if you need to search upwards from the provided directory
spec.getParameters().getCurrentDirectory().set(project.getLayout().getProjectDirectory());
// or use getDirectory() if you want to specify a specific directory and not search
spec.getParameters().getDirectory().set(project.getLayout().getBuildDirectory().dir("my-custom-repo"));
// generally, this should be false, unless you're using getDirectory() choose to have the repo initialized if the directory does not exist
spec.getParameters().getInitIfNotExists().set(false);
// I recommend setting this to 1 unless you know better, this will avoid multiple parallel tasks editing the repo at the same time
spec.getMaxParallelUsages().set(1);
});
```

## Questions, Bugs, and Features

Please use the repo's [issues](https://github.com/ajoberstar/grgit/issues)
Expand Down
5 changes: 5 additions & 0 deletions grgit-gradle/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ pluginBundle {
displayName = "The Groovy way to use Git"
tags = listOf("git", "groovy")
}
create("grgitServicePlugin") {
id = "org.ajoberstar.grgit-service"
displayName = "The Groovy way to use Git (BuildService edition)"
tags = listOf("git", "groovy")
}
}
mavenCoordinates {
groupId = project.group as String
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package org.ajoberstar.grgit.gradle

import spock.lang.Specification
import spock.lang.Unroll

import org.ajoberstar.grgit.Grgit
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.TempDir

class BaseCompatTest extends Specification {
class GrgitPluginCompatTest extends Specification {
@TempDir File tempDir
File projectDir
File buildFile

def setup() {
projectDir = new File(tempDir, 'project')
buildFile = projectFile('build.gradle')

}

def 'with no repo, plugin sets grgit to null'() {
Expand All @@ -34,7 +32,7 @@ task doStuff {
}
'''
when:
def result = build('doStuff')
def result = build('doStuff', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
}
Expand All @@ -59,7 +57,7 @@ task doStuff {
}
'''
when:
def result = build('doStuff', '--quiet')
def result = build('doStuff', '--quiet', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.normalize() == '1.0.0\n'
Expand All @@ -85,7 +83,7 @@ task doStuff {
}
'''
when:
def result = build('doStuff', '--info')
def result = build('doStuff', '--info', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.contains('Closing Git repo')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.ajoberstar.grgit.gradle

import spock.lang.Specification

import org.ajoberstar.grgit.Grgit
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.TaskOutcome
import spock.lang.TempDir

class GrgitServicePluginCompatTest extends Specification {
@TempDir File tempDir
File projectDir
File buildFile

def setup() {
projectDir = new File(tempDir, 'project')
buildFile = projectFile('build.gradle')
buildFile << '''\
import org.ajoberstar.grgit.gradle.GrgitService

plugins {
id 'org.ajoberstar.grgit-service'
}

tasks.register("doStuff", DoStuffTask.class) {
service = grgitService.service
}

class DoStuffTask extends DefaultTask {
@Input
final Property<GrgitService> service

@Inject
DoStuffTask(ObjectFactory objectFactory) {
this.service = objectFactory.property(GrgitService.class);
}

@TaskAction
void execute() {
println service.get().grgit.describe()
}
}
'''
}

def 'with no repo, accessing service fails'() {
given:
// nothing
when:
def result = buildAndFail('doStuff', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.FAILED
}

def 'with repo, plugin opens the repo as grgit'() {
given:
Grgit git = Grgit.init(dir: projectDir)
projectFile('1.txt') << '1'
git.add(patterns: ['1.txt'])
git.commit(message: 'yay')
git.tag.add(name: '1.0.0')
when:
def result = build('doStuff', '--quiet', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.normalize() == '1.0.0\n'
}

def 'with repo, plugin closes the repo after build is finished'() {
given:
Grgit git = Grgit.init(dir: projectDir)
projectFile('1.txt') << '1'
git.add(patterns: ['1.txt'])
git.commit(message: 'yay')
git.tag.add(name: '1.0.0')
when:
def result = build('doStuff', '--info', '--configuration-cache')
then:
result.task(':doStuff').outcome == TaskOutcome.SUCCESS
result.output.contains('Closing Git repo')
}

private BuildResult build(String... args) {
return GradleRunner.create()
.withGradleVersion(System.properties['compat.gradle.version'])
.withPluginClasspath()
.withProjectDir(projectDir)
.forwardOutput()
.withArguments((args + '--stacktrace') as String[])
.build()
}

private BuildResult buildAndFail(String... args) {
return GradleRunner.create()
.withGradleVersion(System.properties['compat.gradle.version'])
.withPluginClasspath()
.withProjectDir(projectDir)
.forwardOutput()
.withArguments((args + '--stacktrace') as String[])
.buildAndFail()
}

private File projectFile(String path) {
File file = new File(projectDir, path)
file.parentFile.mkdirs()
return file
}
}

This file was deleted.

Loading