<commit_before>/*
 * Copyright 2016-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ajoberstar.gradle.git.publish

import java.nio.file.Files

import groovy.transform.PackageScope

import org.ajoberstar.grgit.Grgit
import org.ajoberstar.grgit.operation.FetchOp
import org.ajoberstar.grgit.operation.ResetOp
import org.eclipse.jgit.errors.RepositoryNotFoundException
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.Copy
import org.gradle.api.file.FileTree
import org.eclipse.jgit.transport.URIish

class GitPublishPlugin implements Plugin<Project> {
  @PackageScope static final String RESET_TASK = 'gitPublishReset'
  @PackageScope static final String COPY_TASK = 'gitPublishCopy'
  @PackageScope static final String COMMIT_TASK = 'gitPublishCommit'
  @PackageScope static final String PUSH_TASK = 'gitPublishPush'

  @Override
  void apply(Project project) {
    GitPublishExtension extension = project.extensions.create('gitPublish', GitPublishExtension, project)

    // if using the grgit plugin, default to the repo's origin
    project.pluginManager.withPlugin('org.ajoberstar.grgit') {
      // TODO should this be based on tracking branch instead of assuming origin?
      extension.repoUri = project.grgit?.remote?.list()?.find { it.name == 'origin' }?.url
    }

    extension.repoDir = project.file("${project.buildDir}/gitPublish")

    Task reset = createResetTask(project, extension)
    Task copy = createCopyTask(project, extension)
    Task commit = createCommitTask(project, extension)
    Task push = createPushTask(project, extension, commit)
    push.dependsOn commit
    commit.dependsOn copy
    copy.dependsOn reset

    // always close the repo at the end of the build
    project.gradle.buildFinished {
      if (extension.ext.has('repo')) {
        project.logger.info('Closing Git publish repo: {}', extension.repo.repository.rootDir)
        extension.repo.close()
      }
    }
  }

  private Task createResetTask(Project project, GitPublishExtension extension) {
    Task task = project.tasks.create(RESET_TASK)
    task.with {
      group = 'publishing'
      description = 'Prepares a git repo for new content to be generated.'

      // creates/clones the repo
      outputs.dir(extension.repoDir)

      // get the repo in place
      doFirst {
        Grgit repo = findExistingRepo(project, extension).orElseGet { freshRepo(extension) }

        // TODO replace with grgit.lsremote when added to Grgit
        def cmd = repo.repository.jgit.lsRemote().setRemote('origin').setHeads(true)
        org.ajoberstar.grgit.auth.TransportOpUtil.configure(cmd, null)
        def branchExists = cmd.call().find { it.name == "refs/heads/${extension.branch}" }

        if (branchExists) {
          // fetch only existing pages branch
          repo.fetch(refSpecs: ["+refs/heads/${extension.branch}:refs/remotes/origin/${extension.branch}"], tagMode: FetchOp.TagMode.NONE)

          // make sure local branch exists and tracks the correct remote branch
          if (repo.branch.list().find { it.name == extension.branch }) {
            // branch already exists, set new startPoint
            repo.branch.change(name: extension.branch, startPoint: "origin/${extension.branch}")
          } else {
            // branch doesn't exist, create
            repo.branch.add(name: extension.branch, startPoint: "origin/${extension.branch}")
          }

          // get to the state the remote has
          repo.clean(directories: true, ignore: false)
          repo.checkout(branch: extension.branch)
          repo.reset(commit: "origin/${extension.branch}", mode: ResetOp.Mode.HARD)
        } else {
          repo.checkout(branch: extension.branch, orphan: true)
        }
        extension.ext.repo = repo
      }
      // clean up unwanted files
      doLast {
        FileTree repoTree = project.fileTree(extension.repoDir)
        FileTree preservedTree = repoTree.matching(extension.preserve)
        FileTree unwantedTree = repoTree.minus(preservedTree).asFileTree
        unwantedTree.visit { details ->
          def file = details.file.toPath()
          if (Files.isRegularFile(file)) {
            Files.delete(file)
          }
        }
        // stage the removals, relying on dirs not being tracked by git
        extension.repo.add(patterns: ['.'], update: true)
      }
    }
    return task
  }

  private Task createCopyTask(Project project, GitPublishExtension extension) {
    Task task = project.tasks.create(COPY_TASK, Copy)
    task.with {
      group = 'publishing'
      description = 'Copy contents to be published to git.'
      with extension.contents
      into { extension.repoDir }
    }
    return task
  }

  private Task createCommitTask(Project project, GitPublishExtension extension) {
    Task task = project.tasks.create(COMMIT_TASK)
    task.with {
      group = 'publishing'
      description = 'Commits changes to be published to git.'
      // creates commit in the repo
      outputs.dir(extension.repoDir)
      doLast {
        Grgit repo = extension.repo
        repo.add(patterns: ['.'])
        // check if anything has changed
        if (repo.status().clean) {
          didWork = false
        } else {
          repo.commit(message: extension.commitMessage)
          didWork = true
        }
      }
    }
    return task
  }

  private Task createPushTask(Project project, GitPublishExtension extension, Task commit) {
    Task task = project.tasks.create(PUSH_TASK)
    task.with {
      group = 'publishing'
      description = 'Pushes changes to git.'
      // if we didn't commit anything, don't push anything
      onlyIf { commit.didWork }
      doLast {
        extension.repo.push()
      }
    }
    return task<commit_msg>Don't use input/outputs for tasks<commit_after>/*
 * Copyright 2016-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.ajoberstar.gradle.git.publish

import java.nio.file.Files

import groovy.transform.PackageScope

import org.ajoberstar.grgit.Grgit
import org.ajoberstar.grgit.operation.FetchOp
import org.ajoberstar.grgit.operation.ResetOp
import org.eclipse.jgit.errors.RepositoryNotFoundException
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.tasks.Copy
import org.gradle.api.file.FileTree
import org.eclipse.jgit.transport.URIish

class GitPublishPlugin implements Plugin<Project> {
  @PackageScope static final String RESET_TASK = 'gitPublishReset'
  @PackageScope static final String COPY_TASK = 'gitPublishCopy'
  @PackageScope static final String COMMIT_TASK = 'gitPublishCommit'
  @PackageScope static final String PUSH_TASK = 'gitPublishPush'

  @Override
  void apply(Project project) {
    GitPublishExtension extension = project.extensions.create('gitPublish', GitPublishExtension, project)

    // if using the grgit plugin, default to the repo's origin
    project.pluginManager.withPlugin('org.ajoberstar.grgit') {
      // TODO should this be based on tracking branch instead of assuming origin?
      extension.repoUri = project.grgit?.remote?.list()?.find { it.name == 'origin' }?.url
    }

    extension.repoDir = project.file("${project.buildDir}/gitPublish")

    Task reset = createResetTask(project, extension)
    Task copy = createCopyTask(project, extension)
    Task commit = createCommitTask(project, extension)
    Task push = createPushTask(project, extension, commit)
    push.dependsOn commit
    commit.dependsOn copy
    copy.dependsOn reset

    // always close the repo at the end of the build
    project.gradle.buildFinished {
      if (extension.ext.has('repo')) {
        project.logger.info('Closing Git publish repo: {}', extension.repo.repository.rootDir)
        extension.repo.close()
      }
    }
  }

  private Task createResetTask(Project project, GitPublishExtension extension) {
    Task task = project.tasks.create(RESET_TASK)
    task.with {
      group = 'publishing'
      description = 'Prepares a git repo for new content to be generated.'

      // get the repo in place
      doFirst {
        Grgit repo = findExistingRepo(project, extension).orElseGet { freshRepo(extension) }

        // TODO replace with grgit.lsremote when added to Grgit
        def cmd = repo.repository.jgit.lsRemote().setRemote('origin').setHeads(true)
        org.ajoberstar.grgit.auth.TransportOpUtil.configure(cmd, null)
        def branchExists = cmd.call().find { it.name == "refs/heads/${extension.branch}" }

        if (branchExists) {
          // fetch only existing pages branch
          repo.fetch(refSpecs: ["+refs/heads/${extension.branch}:refs/remotes/origin/${extension.branch}"], tagMode: FetchOp.TagMode.NONE)

          // make sure local branch exists and tracks the correct remote branch
          if (repo.branch.list().find { it.name == extension.branch }) {
            // branch already exists, set new startPoint
            repo.branch.change(name: extension.branch, startPoint: "origin/${extension.branch}")
          } else {
            // branch doesn't exist, create
            repo.branch.add(name: extension.branch, startPoint: "origin/${extension.branch}")
          }

          // get to the state the remote has
          repo.clean(directories: true, ignore: false)
          repo.checkout(branch: extension.branch)
          repo.reset(commit: "origin/${extension.branch}", mode: ResetOp.Mode.HARD)
        } else {
          repo.checkout(branch: extension.branch, orphan: true)
        }
        extension.ext.repo = repo
      }
      // clean up unwanted files
      doLast {
        FileTree repoTree = project.fileTree(extension.repoDir)
        FileTree preservedTree = repoTree.matching(extension.preserve)
        FileTree unwantedTree = repoTree.minus(preservedTree).asFileTree
        unwantedTree.visit { details ->
          def file = details.file.toPath()
          if (Files.isRegularFile(file)) {
            Files.delete(file)
          }
        }
        // stage the removals, relying on dirs not being tracked by git
        extension.repo.add(patterns: ['.'], update: true)
      }
    }
    return task
  }

  private Task createCopyTask(Project project, GitPublishExtension extension) {
    Task task = project.tasks.create(COPY_TASK)
    task.with {
      group = 'publishing'
      description = 'Copy contents to be published to git.'
      doLast {
        project.copy {
          with extension.contents
          into { extension.repoDir }
        }
      }
    }
    return task
  }

  private Task createCommitTask(Project project, GitPublishExtension extension) {
    Task task = project.tasks.create(COMMIT_TASK)
    task.with {
      group = 'publishing'
      description = 'Commits changes to be published to git.'
      doLast {
        Grgit repo = extension.repo
        repo.add(patterns: ['.'])
        // check if anything has changed
        if (repo.status().clean) {
          didWork = false
        } else {
          repo.commit(message: extension.commitMessage)
          didWork = true
        }
      }
    }
    return task
  }

  private Task createPushTask(Project project, GitPublishExtension extension, Task commit) {
    Task task = project.tasks.create(PUSH_TASK)
    task.with {
      group = 'publishing'
      description = 'Pushes changes to git.'
      // if we didn't commit anything, don't push anything
      onlyIf { commit.didWork }
      doLast {
        extension.repo.push()
      }
    }
    return task
{'fraction_non_alphanumeric': 0.09579659949622166, 'fraction_numerical': 0.001889168765743073, 'mean_word_length': 2.8430127041742286, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '984275', 'n_tokens_mistral': 3542, 'n_tokens_neox': 3465, 'n_words': 980}