From 67f2158f53fdbaf7492d80b42a6947b3a88466b0 Mon Sep 17 00:00:00 2001 From: Andrew Oberstar Date: Sat, 5 Oct 2024 17:19:03 -0500 Subject: [PATCH] major: Use git cli instead of grgit grgit is not really necessary for this plugin, decoupling it will improve a lot of things. The expectation is for behavior equivalence, but I expect there will be some quirks from switching so this is considered a major version. --- .github/workflows/ci.yaml | 5 + build.gradle.kts | 7 +- gradle.lockfile | 15 +- .../gradle/git/publish/BaseCompatTest.groovy | 68 ++--- .../publish/MultiPublicationCompatTest.groovy | 20 +- .../gradle/git/publish/GitCliValueSource.java | 36 +++ .../gradle/git/publish/GitPublishPlugin.java | 62 ++-- .../git/publish/tasks/GitPublishCommit.java | 102 ++++--- .../git/publish/tasks/GitPublishPush.java | 72 ++--- .../git/publish/tasks/GitPublishReset.java | 272 +++++++++--------- 10 files changed, 346 insertions(+), 313 deletions(-) create mode 100644 src/main/java/org/ajoberstar/gradle/git/publish/GitCliValueSource.java diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2bc93d4..84962cc 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,6 +21,11 @@ jobs: 17 21 + - name: Set committer info (for tests) + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "noreply@github.com" + - name: Validate Gradle Wrapper uses: gradle/wrapper-validation-action@v1 - name: Setup Gradle diff --git a/build.gradle.kts b/build.gradle.kts index e05f14f..19a55d4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,14 +22,9 @@ java { } dependencies { - // grgit - api("org.ajoberstar.grgit:grgit-core:[5.0,6.0[") - api("org.ajoberstar.grgit:grgit-gradle:[5.0,6.0[") - compatTestImplementation("org.ajoberstar.grgit:grgit-core:[5.0,6.0[") - - // testing compatTestImplementation(gradleTestKit()) compatTestImplementation("org.spockframework:spock-core:2.3-groovy-3.0") + compatTestImplementation("org.ajoberstar.grgit:grgit-core:[5.0,6.0[") } tasks.named("jar") { diff --git a/gradle.lockfile b/gradle.lockfile index 3d44d95..e34fb05 100644 --- a/gradle.lockfile +++ b/gradle.lockfile @@ -1,18 +1,17 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -com.googlecode.javaewah:JavaEWAH:1.2.3=compatTestCompileClasspath,compatTestRuntimeClasspath,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -commons-codec:commons-codec:1.16.0=compatTestCompileClasspath,compatTestRuntimeClasspath,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.ajoberstar.grgit:grgit-core:5.2.2=compatTestCompileClasspath,compatTestRuntimeClasspath,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.ajoberstar.grgit:grgit-gradle:5.2.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +com.googlecode.javaewah:JavaEWAH:1.2.3=compatTestCompileClasspath,compatTestRuntimeClasspath +commons-codec:commons-codec:1.17.0=compatTestCompileClasspath,compatTestRuntimeClasspath +org.ajoberstar.grgit:grgit-core:5.3.0=compatTestCompileClasspath,compatTestRuntimeClasspath org.apiguardian:apiguardian-api:1.1.2=compatTestCompileClasspath -org.codehaus.groovy:groovy:3.0.20=compatTestCompileClasspath,compatTestRuntimeClasspath,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath -org.eclipse.jgit:org.eclipse.jgit:6.8.0.202311291450-r=compatTestCompileClasspath,compatTestRuntimeClasspath,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy:3.0.22=compatTestCompileClasspath,compatTestRuntimeClasspath +org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r=compatTestCompileClasspath,compatTestRuntimeClasspath org.hamcrest:hamcrest:2.2=compatTestCompileClasspath,compatTestRuntimeClasspath org.junit.platform:junit-platform-commons:1.9.0=compatTestCompileClasspath,compatTestRuntimeClasspath org.junit.platform:junit-platform-engine:1.9.0=compatTestCompileClasspath,compatTestRuntimeClasspath org.junit:junit-bom:5.9.0=compatTestCompileClasspath,compatTestRuntimeClasspath org.opentest4j:opentest4j:1.2.0=compatTestCompileClasspath,compatTestRuntimeClasspath -org.slf4j:slf4j-api:1.7.36=compatTestCompileClasspath,compatTestRuntimeClasspath,compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.36=compatTestCompileClasspath,compatTestRuntimeClasspath org.spockframework:spock-core:2.3-groovy-3.0=compatTestCompileClasspath,compatTestRuntimeClasspath -empty=annotationProcessor,compatTestAnnotationProcessor,signatures,testAnnotationProcessor +empty=annotationProcessor,compatTestAnnotationProcessor,compileClasspath,runtimeClasspath,signatures,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath diff --git a/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/BaseCompatTest.groovy b/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/BaseCompatTest.groovy index daaef94..6210c4c 100644 --- a/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/BaseCompatTest.groovy +++ b/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/BaseCompatTest.groovy @@ -1,14 +1,12 @@ package org.ajoberstar.gradle.git.publish -import spock.lang.IgnoreIf -import spock.lang.Specification -import spock.lang.TempDir - import org.ajoberstar.grgit.Grgit -import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.gradle.testkit.runner.UnexpectedBuildFailure +import spock.lang.Specification +import spock.lang.TempDir class BaseCompatTest extends Specification { @TempDir File tempDir @@ -25,7 +23,7 @@ class BaseCompatTest extends Specification { remoteFile('master.txt') << 'contents here' remote.add(patterns: ['.']) - remote.commit(message: 'first commit') + remote.commit(message: 'first commit', sign: false) // handle different init branches to keep existing tests the same if (remote.branch.current().name != 'master') { @@ -37,7 +35,7 @@ class BaseCompatTest extends Specification { remoteFile('index.md') << '# This Page is Awesome!' remoteFile('1.0.0/index.md') << '# Version 1.0.0 is the Best!' remote.add(patterns: ['.']) - remote.commit(message: 'first pages commit') + remote.commit(message: 'first pages commit', sign: false) remote.checkout(branch: 'master') } @@ -52,7 +50,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'my-pages' contents.from 'src' } @@ -77,7 +75,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' } @@ -97,7 +95,7 @@ gitPublish { remote.checkout(branch: 'gh-pages') remoteFile('index.md') << 'And has great content' remote.add(patterns: ['.']) - remote.commit(message: 'second pages commit') + remote.commit(message: 'second pages commit', sign: false) remote.checkout(branch: 'master') projectFile('src/content.txt') << 'published content here' @@ -108,7 +106,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' fetchDepth = 1 contents.from 'src' @@ -130,14 +128,14 @@ gitPublish { def 'reset pulls from reference repo if available before pulling from remote'() { given: def referenceDir = new File(tempDir, 'reference') - def reference = Grgit.clone(dir: referenceDir, uri: remote.repository.rootDir.toURI()) + def reference = Grgit.clone(dir: referenceDir, uri: repoPath(remote)) reference.checkout(branch: 'gh-pages', createBranch: true) // add a file that will get fetched but not pushed def refFile = new File(reference.repository.rootDir, 'src/newFile.txt') refFile.parentFile.mkdirs() refFile.text = 'Some content' reference.add(patterns: ['.']) - reference.commit(message: 'This wont get pushed') + reference.commit(message: 'This wont get pushed', sign: false) projectFile('src/content.txt') << 'published content here' @@ -147,8 +145,8 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' - referenceRepoUri = '${reference.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' + referenceRepoUri = '${repoPath(reference)}' branch = 'gh-pages' contents.from 'src' } @@ -159,7 +157,7 @@ gitPublish { remote.checkout(branch: 'gh-pages') then: result.task(':gitPublishPush').outcome == TaskOutcome.SUCCESS - result.output.contains('Fetching from reference repo') + result.output.contains('gh-pages -> reference/gh-pages') remote.log().size() == 2 remoteFile('content.txt').text == 'published content here' !remoteFile('newFile.txt').exists() @@ -175,7 +173,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' repoDir = file('build/this-is-custom') branch = 'gh-pages' contents.from 'src' @@ -197,7 +195,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' @@ -229,7 +227,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents { from('src1') { @@ -263,7 +261,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' } @@ -274,16 +272,16 @@ gitPublish { remote.checkout(branch: 'gh-pages') then: result.task(':gitPublishCommit').outcome == TaskOutcome.UP_TO_DATE - result.task(':gitPublishPush').outcome == TaskOutcome.SKIPPED + result.task(':gitPublishPush').outcome == TaskOutcome.UP_TO_DATE } def 'existing working repo is reused if valid'() { given: - def working = Grgit.clone(dir: "${projectDir}/build/gitPublish", uri: remote.repository.rootDir.toURI()) + def working = Grgit.clone(dir: "${projectDir}/build/gitPublish", uri: repoPath(remote)) working.checkout(branch: 'master') new File(projectDir, 'build/gitPublish/master.txt') << 'working repo was here' working.add(patterns: ['.']) - working.commit(message: 'working repo was here') + working.commit(message: 'working repo was here', sign: false) working.checkout(branch: 'gh-pages', startPoint: 'origin/gh-pages', createBranch: 'true') working.close() @@ -293,7 +291,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents { from 'src' @@ -320,7 +318,7 @@ gitPublish { new File(badRemoteDir, 'master.txt') << 'bad contents here' badRemote.add(patterns: ['.']) - badRemote.commit(message: 'bad first commit') + badRemote.commit(message: 'bad first commit', sign: false) // handle different init branches to keep existing tests the same if (badRemote.branch.current().name != 'master') { @@ -339,7 +337,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' } @@ -384,7 +382,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' commitMessage = "Deploy docs to gh-pages (\${project.name})" @@ -398,7 +396,7 @@ gitPublish { result.task(':gitPublishPush').outcome == TaskOutcome.SUCCESS remote.log().size() == 2 remoteFile('content.txt').text == 'published content here' - remote.head().fullMessage == "Deploy docs to gh-pages (${projectFile('.').canonicalFile.name})" + remote.head().fullMessage == "Deploy docs to gh-pages (${projectFile('.').canonicalFile.name})\n" } def 'can activate signing'() { @@ -415,7 +413,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' sign = true @@ -425,7 +423,7 @@ gitPublish { def result = buildAndFail() then: - result.output.contains("org.eclipse.jgit.api.errors.ServiceUnavailableException") + result.output.contains("gpg: signing failed: No secret key") } def 'can deactivate signing'() { @@ -442,7 +440,7 @@ plugins { } gitPublish { - repoUri = '${remote.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote)}' branch = 'gh-pages' contents.from 'src' sign = false @@ -455,11 +453,11 @@ gitPublish { result.task(':gitPublishPush').outcome == TaskOutcome.SUCCESS } - private BuildResult build(String... args = ['gitPublishPush', '--stacktrace', '--info', '--configuration-cache']) { + private BuildResult build(String... args = ['gitPublishPush', '--stacktrace', '--configuration-cache']) { return runner(args).build() } - private BuildResult buildAndFail(String... args = ['gitPublishPush', '--stacktrace', '--info', '--configuration-cache']) { + private BuildResult buildAndFail(String... args = ['gitPublishPush', '--stacktrace', '--configuration-cache']) { return runner(args).buildAndFail() } @@ -483,4 +481,8 @@ gitPublish { file.parentFile.mkdirs() return file } + + private String repoPath(Grgit repo) { + return repo.repository.rootDir.toPath().toAbsolutePath().toString().replace('\\', '\\\\') + } } diff --git a/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/MultiPublicationCompatTest.groovy b/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/MultiPublicationCompatTest.groovy index 297e1d5..9639a23 100644 --- a/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/MultiPublicationCompatTest.groovy +++ b/src/compatTest/groovy/org/ajoberstar/gradle/git/publish/MultiPublicationCompatTest.groovy @@ -25,7 +25,7 @@ class MultiPublicationCompatTest extends Specification { remote1File('master.txt') << 'contents here' remote1.add(patterns: ['.']) - remote1.commit(message: 'first commit') + remote1.commit(message: 'first commit', sign: false) // handle different init branches to keep existing tests the same if (remote1.branch.current().name != 'master') { @@ -37,7 +37,7 @@ class MultiPublicationCompatTest extends Specification { remote1File('index.md') << '# This Page is Awesome!' remote1File('1.0.0/index.md') << '# Version 1.0.0 is the Best!' remote1.add(patterns: ['.']) - remote1.commit(message: 'first pages commit') + remote1.commit(message: 'first pages commit', sign: false) remote1.checkout(branch: 'master') @@ -46,7 +46,7 @@ class MultiPublicationCompatTest extends Specification { remote2File('master.txt') << 'contents here' remote2.add(patterns: ['.']) - remote2.commit(message: 'first commit') + remote2.commit(message: 'first commit', sign: false) // handle different init branches to keep existing tests the same if (remote2.branch.current().name != 'master') { @@ -58,7 +58,7 @@ class MultiPublicationCompatTest extends Specification { remote2File('index.md') << '# This Page is Awesomest!' remote2File('1.0.0/index.md') << '# Version 1.0.0 is the Best!' remote2.add(patterns: ['.']) - remote2.commit(message: 'first pages commit') + remote2.commit(message: 'first pages commit', sign: false) remote2.checkout(branch: 'master') } @@ -75,7 +75,7 @@ plugins { gitPublish { // can configure main at top-level - repoUri = '${remote1.repository.rootDir.toURI()}' + repoUri = '${repoPath(remote1)}' contents.from 'src' publications { @@ -85,7 +85,7 @@ gitPublish { } second { - repoUri.set('${remote2.repository.rootDir.toURI()}') + repoUri.set('${repoPath(remote2)}') branch.set('gh-pages') contents.from 'src2' } @@ -107,11 +107,11 @@ gitPublish { remote2File('content.txt').text == 'second published content here' } - private BuildResult build(String... args = ['gitPublishPushAll', '--stacktrace', '--info', '--configuration-cache']) { + private BuildResult build(String... args = ['gitPublishPushAll', '--stacktrace', '--configuration-cache']) { return runner(args).build() } - private BuildResult buildAndFail(String... args = ['gitPublishPushAll', '--stacktrace', '--info', '--configuration-cache']) { + private BuildResult buildAndFail(String... args = ['gitPublishPushAll', '--stacktrace', '--configuration-cache']) { return runner(args).buildAndFail() } @@ -141,4 +141,8 @@ gitPublish { file.parentFile.mkdirs() return file } + + private String repoPath(Grgit repo) { + return repo.repository.rootDir.toPath().toAbsolutePath().toString().replace('\\', '\\\\') + } } diff --git a/src/main/java/org/ajoberstar/gradle/git/publish/GitCliValueSource.java b/src/main/java/org/ajoberstar/gradle/git/publish/GitCliValueSource.java new file mode 100644 index 0000000..664c4ae --- /dev/null +++ b/src/main/java/org/ajoberstar/gradle/git/publish/GitCliValueSource.java @@ -0,0 +1,36 @@ +package org.ajoberstar.gradle.git.publish; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; + +import javax.inject.Inject; + +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.ValueSource; +import org.gradle.api.provider.ValueSourceParameters; +import org.gradle.process.ExecOperations; +import org.jetbrains.annotations.Nullable; + +public abstract class GitCliValueSource implements ValueSource { + public interface Params extends ValueSourceParameters { + ListProperty getGitArguments(); + } + + @Inject + protected abstract ExecOperations getExecOperations(); + + @Override + public @Nullable String obtain() { + try { + var output = new ByteArrayOutputStream(); + getExecOperations().exec(spec -> { + spec.executable("git"); + spec.setArgs(getParameters().getGitArguments().get()); + spec.setStandardOutput(output); + }); + return output.toString(StandardCharsets.UTF_8).trim(); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/org/ajoberstar/gradle/git/publish/GitPublishPlugin.java b/src/main/java/org/ajoberstar/gradle/git/publish/GitPublishPlugin.java index 7a1d90f..eb4023c 100644 --- a/src/main/java/org/ajoberstar/gradle/git/publish/GitPublishPlugin.java +++ b/src/main/java/org/ajoberstar/gradle/git/publish/GitPublishPlugin.java @@ -1,14 +1,14 @@ package org.ajoberstar.gradle.git.publish; +import java.nio.file.Path; + import org.ajoberstar.gradle.git.publish.tasks.GitPublishCommit; import org.ajoberstar.gradle.git.publish.tasks.GitPublishPush; import org.ajoberstar.gradle.git.publish.tasks.GitPublishReset; -import org.ajoberstar.grgit.Grgit; -import org.ajoberstar.grgit.gradle.GrgitService; -import org.ajoberstar.grgit.gradle.GrgitServiceExtension; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.provider.Provider; +import org.gradle.api.provider.ProviderFactory; import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.TaskProvider; import org.gradle.util.GradleVersion; @@ -25,18 +25,10 @@ public void apply(Project project) { extension.getPublications().configureEach(publication -> { configurePublicationDefaults(project, publication); - var grgitService = project.getGradle().getSharedServices().registerIfAbsent(String.format("git-publish-%s-grgit", publication.getName()), GrgitService.class, spec -> { - spec.parameters(parameters -> { - parameters.getDirectory().set(publication.getRepoDir()); - parameters.getInitIfNotExists().set(true); - }); - spec.getMaxParallelUsages().set(1); - }); - - var reset = createResetTask(project, publication, grgitService); + var reset = createResetTask(project, publication); var copy = createCopyTask(project, publication); - var commit = createCommitTask(project, publication, grgitService); - var push = createPushTask(project, publication, grgitService); + var commit = createCommitTask(project, publication); + var push = createPushTask(project, publication); push.configure(t -> t.dependsOn(commit)); commit.configure(t -> t.dependsOn(copy)); @@ -53,24 +45,16 @@ public void apply(Project project) { private void configurePublicationDefaults(Project project, GitPublication publication) { publication.getCommitMessage().set("Generated by gradle-git-publish."); - - // if using the grgit-service plugin, default to the repo's origin - project.getPluginManager().withPlugin("org.ajoberstar.grgit.service", plugin -> { - var grgitExt = project.getExtensions().getByType(GrgitServiceExtension.class); - // TODO should this be based on tracking branch instead of assuming origin? - publication.getRepoUri().set(grgitExt.getService().map(service -> getOriginUri(service.getGrgit()))); - publication.getReferenceRepoUri().set(grgitExt.getService().map(service -> service.getGrgit().getRepository().getRootDir().toURI().toString())); - }); - + publication.getRepoUri().set(getOriginUriProvider(project.getProviders())); + publication.getReferenceRepoUri().set(getGitDirProvider(project.getProviders())); publication.getRepoDir().set(project.getLayout().getBuildDirectory().dir("gitPublish/" + publication.getName())); } - private TaskProvider createResetTask(Project project, GitPublication publication, Provider grgitService) { + private TaskProvider createResetTask(Project project, GitPublication publication) { return project.getTasks().register(getTaskName(publication, "Reset"), GitPublishReset.class, task -> { task.setGroup("publishing"); task.setDescription("Prepares a git repo for " + publication.getName() + " publication content to be generated."); - task.usesService(grgitService); - task.getGrgitService().set(grgitService); + task.getRepoDir().set(publication.getRepoDir()); task.getRepoUri().set(publication.getRepoUri()); task.getReferenceRepoUri().set(publication.getReferenceRepoUri()); task.getBranch().set(publication.getBranch()); @@ -93,33 +77,35 @@ private TaskProvider createCopyTask(Project project, GitPublication public }); } - private TaskProvider createCommitTask(Project project, GitPublication publication, Provider grgitService) { + private TaskProvider createCommitTask(Project project, GitPublication publication) { return project.getTasks().register(getTaskName(publication, "Commit"), GitPublishCommit.class, task -> { task.setGroup("publishing"); task.setDescription("Commits " + publication.getName() + " publication changes to be published to git."); - task.usesService(grgitService); - task.getGrgitService().set(grgitService); + task.getRepoDir().set(publication.getRepoDir()); task.getMessage().set(publication.getCommitMessage()); task.getSign().set(publication.getSign()); }); } - private TaskProvider createPushTask(Project project, GitPublication publication, Provider grgitService) { + private TaskProvider createPushTask(Project project, GitPublication publication) { return project.getTasks().register(getTaskName(publication, "Push"), GitPublishPush.class, task -> { task.setGroup("publishing"); task.setDescription("Pushes " + publication.getName() + " publication changes to git."); - task.usesService(grgitService); - task.getGrgitService().set(grgitService); + task.getRepoDir().set(publication.getRepoDir()); task.getBranch().set(publication.getBranch()); }); } - private String getOriginUri(Grgit grgit) { - return grgit.getRemote().list().stream() - .filter(remote -> remote.getName().equals("origin")) - .map(remote -> remote.getUrl()) - .findAny() - .orElse(null); + private Provider getOriginUriProvider(ProviderFactory providers) { + return providers.of(GitCliValueSource.class, spec -> { + spec.getParameters().getGitArguments().addAll("remote", "get-url", "origin"); + }); + } + + private Provider getGitDirProvider(ProviderFactory providers) { + return providers.of(GitCliValueSource.class, spec -> { + spec.getParameters().getGitArguments().addAll("rev-parse", "--absolute-git-dir"); + }); } private String getTaskName(GitPublication publication, String task) { diff --git a/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishCommit.java b/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishCommit.java index 44a5071..2451c17 100644 --- a/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishCommit.java +++ b/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishCommit.java @@ -1,64 +1,82 @@ package org.ajoberstar.gradle.git.publish.tasks; -import java.io.File; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; import javax.inject.Inject; -import org.ajoberstar.grgit.gradle.GrgitService; import org.gradle.api.DefaultTask; -import org.gradle.api.model.ObjectFactory; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileSystemLocationProperty; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.*; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; +import org.gradle.process.ExecOperations; @UntrackedTask(because = "Git tracks the state") -public class GitPublishCommit extends DefaultTask { - private final Property grgitService; - private final Property message; - private final Property sign; - - @Inject - public GitPublishCommit(ObjectFactory objectFactory) { - this.grgitService = objectFactory.property(GrgitService.class); - this.message = objectFactory.property(String.class); - this.sign = objectFactory.property(Boolean.class); - } - - @Internal - public Property getGrgitService() { - return grgitService; - } +public abstract class GitPublishCommit extends DefaultTask { + @OutputDirectory + public abstract DirectoryProperty getRepoDir(); @Input - public Property getMessage() { - return message; - } + public abstract Property getMessage(); @Input @Optional - public Property getSign() { - return sign; - } + public abstract Property getSign(); + + @Inject + protected abstract ExecOperations getExecOperations(); @TaskAction public void commit() { - var git = getGrgitService().get().getGrgit(); - git.add(op -> { - op.setPatterns(Stream.of(".").collect(Collectors.toSet())); + // add changed files + getExecOperations().exec(spec -> { + spec.commandLine("git", "add", "-A"); + spec.workingDir(getRepoDir().get()); + spec.setStandardOutput(OutputStream.nullOutputStream()); }); - // check if anything has changed - if (git.status().isClean()) { - setDidWork(false); - } else { - git.commit(op -> { - op.setMessage(getMessage().get()); - if (getSign().isPresent()) { - op.setSign(getSign().get()); - } - }); - setDidWork(true); + // check for changes to commit + var status = new ByteArrayOutputStream(); + getExecOperations().exec(spec -> { + spec.commandLine("git", "status", "--porcelain"); + spec.workingDir(getRepoDir().get()); + spec.setStandardOutput(status); + }); + + if (status.toString(StandardCharsets.UTF_8).isEmpty()) { + this.setDidWork(false); + return; } + + // commit changes + getExecOperations().exec(spec -> { + spec.executable("git"); + spec.args("commit"); + + // signing + if (getSign().isPresent()) { + spec.args(getSign().get() ? "--gpg-sign" : "--no-gpg-sign"); + } + + // message + spec.args("--file", "-"); + var msg = getMessage().get().getBytes(StandardCharsets.UTF_8); + spec.setStandardInput(new ByteArrayInputStream(msg)); + + spec.workingDir(getRepoDir().get()); + + spec.setStandardOutput(OutputStream.nullOutputStream()); + }); + + this.setDidWork(true); } } diff --git a/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishPush.java b/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishPush.java index ab4b2ae..ae12108 100644 --- a/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishPush.java +++ b/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishPush.java @@ -1,64 +1,46 @@ package org.ajoberstar.gradle.git.publish.tasks; -import java.io.File; -import java.util.Arrays; +import java.io.ByteArrayOutputStream; +import java.nio.charset.StandardCharsets; import javax.inject.Inject; -import org.ajoberstar.grgit.gradle.GrgitService; -import org.ajoberstar.grgit.operation.BranchChangeOp; import org.gradle.api.DefaultTask; -import org.gradle.api.model.ObjectFactory; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileSystemLocationProperty; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.*; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; +import org.gradle.process.ExecOperations; @UntrackedTask(because = "Git tracks the state") -public class GitPublishPush extends DefaultTask { - private final Property grgitService; - private final Property branch; - - @Inject - public GitPublishPush(ObjectFactory objectFactory) { - this.grgitService = objectFactory.property(GrgitService.class); - this.branch = objectFactory.property(String.class); - - this.onlyIf(t -> { - try { - var git = getGrgitService().get().getGrgit(); - var status = git.getBranch().status(op -> { - op.setName(getBranch().get()); - }); - return status.getAheadCount() > 0; - } catch (IllegalStateException e) { - // if we're not tracking anything yet (i.e. orphan) we need to push - return true; - } - }); - } - - @Internal - public Property getGrgitService() { - return grgitService; - } +public abstract class GitPublishPush extends DefaultTask { + @OutputDirectory + public abstract DirectoryProperty getRepoDir(); @Input - public Property getBranch() { - return branch; - } + public abstract Property getBranch(); + + @Inject + protected abstract ExecOperations getExecOperations(); @TaskAction public void push() { - var git = getGrgitService().get().getGrgit(); var pubBranch = getBranch().get(); - git.push(op -> { - op.setRefsOrSpecs(Arrays.asList(String.format("refs/heads/%s:refs/heads/%s", pubBranch, pubBranch))); + var output = new ByteArrayOutputStream(); + getExecOperations().exec(spec -> { + var refSpec = String.format("refs/heads/%s:refs/heads/%s", pubBranch, pubBranch); + spec.commandLine("git", "push", "--porcelain", "--set-upstream", "origin", refSpec); + spec.workingDir(getRepoDir().get()); + spec.setStandardOutput(output); }); - // ensure tracking is set - git.getBranch().change(op -> { - op.setName(pubBranch); - op.setStartPoint("origin/" + pubBranch); - op.setMode(BranchChangeOp.Mode.TRACK); - }); + var result = output.toString(StandardCharsets.UTF_8); + this.setDidWork(!result.contains("[up to date]")); } } diff --git a/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishReset.java b/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishReset.java index c44224d..691f5e1 100644 --- a/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishReset.java +++ b/src/main/java/org/ajoberstar/gradle/git/publish/tasks/GitPublishReset.java @@ -1,72 +1,50 @@ package org.ajoberstar.gradle.git.publish.tasks; +import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.io.UncheckedIOException; -import java.net.URISyntaxException; import java.nio.file.Files; -import java.util.Arrays; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.inject.Inject; -import org.ajoberstar.grgit.Grgit; -import org.ajoberstar.grgit.Ref; -import org.ajoberstar.grgit.gradle.GrgitService; -import org.eclipse.jgit.transport.URIish; import org.gradle.api.DefaultTask; -import org.gradle.api.file.*; +import org.gradle.api.file.Directory; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileSystemLocationProperty; +import org.gradle.api.file.FileVisitDetails; +import org.gradle.api.file.FileVisitor; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; -import org.gradle.api.tasks.*; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.UntrackedTask; import org.gradle.api.tasks.util.PatternFilterable; +import org.gradle.process.ExecOperations; @UntrackedTask(because = "Git tracks the state") -public class GitPublishReset extends DefaultTask { - private final Property grgitService; - private final Property repoUri; - private final Property referenceRepoUri; - private final Property branch; - private final Property fetchDepth; +public abstract class GitPublishReset extends DefaultTask { private PatternFilterable preserve; - private final ObjectFactory objectFactory; - @Inject - public GitPublishReset(ObjectFactory objectFactory) { - this.grgitService = objectFactory.property(GrgitService.class); - this.repoUri = objectFactory.property(String.class); - this.referenceRepoUri = objectFactory.property(String.class); - this.branch = objectFactory.property(String.class); - this.fetchDepth = objectFactory.property(Integer.class); - this.objectFactory = objectFactory; - } - - @Internal - public Property getGrgitService() { - return grgitService; - } + @OutputDirectory + public abstract DirectoryProperty getRepoDir(); @Internal - public Property getReferenceRepoUri() { - return referenceRepoUri; - } + public abstract Property getReferenceRepoUri(); @Input - public Property getRepoUri() { - return repoUri; - } + public abstract Property getRepoUri(); @Input - public Property getBranch() { - return branch; - } + public abstract Property getBranch(); @Input @Optional - public Property getFetchDepth() { - return fetchDepth; - } + public abstract Property getFetchDepth(); @Internal public PatternFilterable getPreserve() { @@ -77,99 +55,145 @@ public void setPreserve(PatternFilterable preserve) { this.preserve = preserve; } + @Inject + protected abstract ObjectFactory getObjectFactory(); + + @Inject + protected abstract ExecOperations getExecOperations(); + @TaskAction public void reset() throws IOException { - var git = grgitService.get().getGrgit(); + var repoDir = getRepoDir().get().getAsFile(); + var pubBranch = getBranch().get(); - if (!isRemoteUriMatch(git, "origin", repoUri.get())) { - git.getRemote().remove(op -> { - op.setName("origin"); - }); - git.getRemote().add(op -> { - op.setName("origin"); - op.setUrl(repoUri.get()); + // initialize git repo + if (!new File(repoDir, ".git").exists()) { + getExecOperations().exec(spec -> { + spec.commandLine("git", "init"); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); }); } - if (referenceRepoUri.isPresent() && !isRemoteUriMatch(git, "reference", referenceRepoUri.get())) { - git.getRemote().remove(op -> { - op.setName("reference"); + // set origin + try { + getExecOperations().exec(spec -> { + spec.commandLine("git", "remote", "add", "origin", getRepoUri().get()); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + spec.setErrorOutput(OutputStream.nullOutputStream()); }); - - git.getRemote().add(op -> { - op.setName("reference"); - op.setUrl(referenceRepoUri.get()); + } catch (Exception e) { + getExecOperations().exec(spec -> { + spec.commandLine("git", "remote", "set-url", "origin", getRepoUri().get()); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); }); } - var pubBranch = getBranch().get(); + // set reference + if (getReferenceRepoUri().isPresent()) { + try { + getExecOperations().exec(spec -> { + spec.commandLine("git", "remote", "add", "reference", getReferenceRepoUri().get()); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + spec.setErrorOutput(OutputStream.nullOutputStream()); + }); + } catch (Exception e) { + getExecOperations().exec(spec -> { + spec.commandLine("git", "remote", "set-url", "reference", getReferenceRepoUri().get()); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + }); + } - if (!pubBranch.equals(git.getBranch().current().getName())) { - // create a new orphan branch - git.checkout(op -> { - op.setBranch(pubBranch); - op.setOrphan(true); - }); - } + // check reference for branch + boolean referenceHasBranch; + try { + getExecOperations().exec(spec -> { + spec.commandLine("git", "ls-remote", "--exit-code", "reference", pubBranch); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + }); + referenceHasBranch = true; + } catch (Exception e) { + referenceHasBranch = false; + } - if (referenceRepoUri.isPresent()) { - Map referenceBranches = git.lsremote(op -> { - op.setRemote("reference"); - op.setHeads(true); - }); + if (referenceHasBranch) { + // get local branch reset to remote state + getExecOperations().exec(spec -> { + var refSpec = String.format("+refs/heads/%s:refs/remotes/reference/%s", pubBranch, pubBranch); - boolean referenceBranchExists = referenceBranches.keySet().stream() - .anyMatch(ref -> ref.getFullName().equals("refs/heads/" + pubBranch)); + spec.executable("git"); + spec.args("fetch"); + if (getFetchDepth().isPresent()) { + spec.args("--depth", getFetchDepth().get()); + } + spec.args("reference", refSpec); - if (referenceBranchExists) { - getLogger().info("Fetching from reference repo: " + referenceRepoUri.get()); - git.fetch(op -> { - op.setRefSpecs(Arrays.asList(String.format("+refs/heads/%s:refs/remotes/reference/%s", pubBranch, pubBranch))); - op.setTagMode("none"); - op.setDepth(getFetchDepth().getOrNull()); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); }); } } - Map remoteBranches = git.lsremote(op -> { - op.setRemote("origin"); - op.setHeads(true); - }); + // check origin for branch + boolean hasBranch; + try { + getExecOperations().exec(spec -> { + spec.commandLine("git", "ls-remote", "--exit-code", "origin", pubBranch); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + }); + hasBranch = true; + } catch (Exception e) { + hasBranch = false; + } - boolean remoteBranchExists = remoteBranches.keySet().stream() - .anyMatch(ref -> ref.getFullName().equals("refs/heads/" + pubBranch)); + if (hasBranch) { + // get local branch reset to remote state + getExecOperations().exec(spec -> { + var refSpec = String.format("+refs/heads/%s:refs/remotes/origin/%s", pubBranch, pubBranch); - if (remoteBranchExists) { - // fetch only the existing pages branch - git.fetch(op -> { - getLogger().info("Fetching from remote repo: " + repoUri.get()); - op.setRefSpecs(Arrays.asList(String.format("+refs/heads/%s:refs/remotes/origin/%s", pubBranch, pubBranch))); - op.setTagMode("none"); - op.setDepth(getFetchDepth().getOrNull()); - }); + spec.executable("git"); + spec.args("fetch"); + if (getFetchDepth().isPresent()) { + spec.args("--depth", getFetchDepth().get()); + } + spec.args("origin", refSpec); - // make sure local branch exists - if (!git.getBranch().list().stream().anyMatch(branch -> branch.getName().equals(pubBranch))) { - git.getBranch().add(op -> { - op.setName(pubBranch); - op.setStartPoint("origin/" + pubBranch); - }); - } + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + }); - // get to the state the remote has - git.clean(op -> { - op.setDirectories(true); - op.setIgnore(false); + getExecOperations().exec(spec -> { + spec.commandLine("git", "switch", "--force-create", pubBranch, String.format("origin/%s", pubBranch)); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + spec.setErrorOutput(OutputStream.nullOutputStream()); }); - git.checkout(op -> op.setBranch(pubBranch)); - git.reset(op -> { - op.setCommit("origin/" + pubBranch); - op.setMode("hard"); + } else { + // start with a fresh branch + getExecOperations().exec(spec -> { + spec.commandLine("git", "switch", "--orphan", pubBranch); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + spec.setErrorOutput(OutputStream.nullOutputStream()); }); } - var repoTree = objectFactory.fileTree(); - repoTree.from(git.getRepository().getRootDir()); + // clean repository + getExecOperations().exec(spec -> { + spec.commandLine("git", "clean", "-fdx"); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); + }); + + // remove all files not marked in the preserve + var repoTree = getObjectFactory().fileTree(); + repoTree.from(repoDir); var preservedTree = repoTree.matching(getPreserve()); var unwantedTree = repoTree.minus(preservedTree).getAsFileTree(); unwantedTree.visit(new FileVisitor() { @@ -189,28 +213,10 @@ public void visitFile(FileVisitDetails fileVisitDetails) { }); // stage the removals, relying on dirs not being tracked by git - git.add(op -> { - op.setPatterns(Stream.of(".").collect(Collectors.toSet())); - op.setUpdate(true); + getExecOperations().exec(spec -> { + spec.commandLine("git", "add", "-A"); + spec.workingDir(repoDir); + spec.setStandardOutput(OutputStream.nullOutputStream()); }); } - - private boolean isRemoteUriMatch(Grgit grgit, String remoteName, String remoteUri) { - try { - var maybeCurrentRemoteUri = grgit.getRemote().list().stream() - .filter(remote -> remote.getName().equals(remoteName)) - .map(remote -> remote.getUrl()) - .findAny(); - - // need to use the URIish to normalize them and ensure we support all Git compatible URI-ishs (URL - // is too limiting) - if (maybeCurrentRemoteUri.isPresent()) { - return new URIish(remoteUri).equals(new URIish(maybeCurrentRemoteUri.get())); - } else { - return false; - } - } catch (URISyntaxException e) { - throw new RuntimeException("Invalid URI.", e); - } - } }