<commit_before>package org.rundeck.plugin.scm.git

import com.dtolabs.rundeck.core.jobs.JobReference
import com.dtolabs.rundeck.core.plugins.configuration.Validator
import com.dtolabs.rundeck.core.plugins.views.Action
import com.dtolabs.rundeck.core.storage.ResourceMeta
import com.dtolabs.rundeck.plugins.scm.*
import org.apache.log4j.Logger
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PullResult
import org.eclipse.jgit.api.Status
import org.eclipse.jgit.api.TransportCommand
import org.eclipse.jgit.diff.RawTextComparator
import org.eclipse.jgit.lib.BranchConfig
import org.eclipse.jgit.lib.ConfigConstants
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.eclipse.jgit.transport.TrackingRefUpdate
import org.eclipse.jgit.transport.URIish
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
import org.eclipse.jgit.util.FileUtils
import org.rundeck.plugin.scm.git.config.Common
import org.rundeck.storage.api.StorageException

import java.util.regex.Matcher
import java.util.regex.Pattern

/**
 * Common features of the import and export plugins
 */
class BaseGitPlugin {
    public static final String REMOTE_NAME = "origin"
    Git git
    Repository repo
    File workingDir
    String branch
    Map<String, String> input
    Common commonConfig
    JobFileMapper mapper
    RawTextComparator COMP = RawTextComparator.DEFAULT
    Map<String, Map> jobStateMap = Collections.synchronizedMap([:])

    BaseGitPlugin(Common commonConfig) {
        this.input = commonConfig.rawInput
        this.commonConfig=commonConfig
    }

    Map<String, String> getSshConfig() {
        def config = [:]

        if (commonConfig.strictHostKeyChecking in ['yes', 'no']) {
            config['StrictHostKeyChecking'] = commonConfig.strictHostKeyChecking
        }
        config
    }

    def serialize(final JobExportReference job, format) {
        File outfile = mapper.fileForJob(job)
        if (!outfile.parentFile.isDirectory()) {
            if (!outfile.parentFile.mkdirs()) {
                throw new ScmPluginException(
                        "Cannot create necessary dirs to serialize file to path: ${outfile.absolutePath}"
                )
            }
        }
        outfile.withOutputStream { out ->
            job.jobSerializer.serialize(format, out)
        }
    }

    def serializeTemp(final JobExportReference job, format) {
        File outfile = File.createTempFile("${this.class.name}-serializeTemp", ".${format}")
        outfile.deleteOnExit()
        outfile.withOutputStream { out ->
            job.jobSerializer.serialize(format, out)
        }
        return outfile
    }

    def serializeAll(final Set<JobExportReference> jobExportReferences, String format) {
        jobExportReferences.each { serialize(it, format) }
    }

    TrackingRefUpdate fetchFromRemote(ScmOperationContext context, Git git1 = null) {
        def agit = git1 ?: git
        def fetchCommand = agit.fetch()
        fetchCommand.setRemote(REMOTE_NAME)
        setupTransportAuthentication(sshConfig, context, fetchCommand)
        def fetchResult = fetchCommand.call()

        def update = fetchResult.getTrackingRefUpdate("refs/remotes/${REMOTE_NAME}/${this.branch}")

        def fetchMessage = update ? update.toString() : "No changes were found"
        Logger.getLogger(this.class).debug("fetchFromRemote: ${fetchMessage}")
        //make sure tracking is configured for the branch
        if (!remoteTrackingBranch(agit)) {
            agit.repository.config.setString(
                    ConfigConstants.CONFIG_BRANCH_SECTION,
                    branch,
                    ConfigConstants.CONFIG_KEY_REMOTE,
                    REMOTE_NAME
            );
            //if remote branch name exists, track it for merging
            def remoteRef = fetchResult.getAdvertisedRef(branch) ?:
                    fetchResult.getAdvertisedRef(Constants.R_HEADS + branch)
            if (remoteRef) {
                agit.repository.config.setString(
                        ConfigConstants.CONFIG_BRANCH_SECTION,
                        branch,
                        ConfigConstants.CONFIG_KEY_MERGE,
                        remoteRef.name
                );
            }

            agit.repository.config.save()
        }

        return update
    }
    /**
     * @param git1
     * @return remote tracking branch name, or null if tracking has not be set up
     */
    def remoteTrackingBranch(Git git1 = null) {
        def agit = git1 ?: git

        def branchConfig = new BranchConfig(agit.repository.config, branch)
        return branchConfig.getRemoteTrackingBranch()
    }

    PullResult gitPull(ScmOperationContext context, Git git1 = null) {
        def pullCommand = (git1 ?: git).pull().setRemote(REMOTE_NAME).setRemoteBranchName(branch)
        setupTransportAuthentication(sshConfig, context, pullCommand)
        pullCommand.call()
    }

    ScmExportResult gitResolve(
            final ScmOperationContext context,
            boolean rebase,
            String mergeResolutionStrategy
    )
    {
        if (rebase) {
            def pullCommand = git.pull().setRemote(REMOTE_NAME).setRemoteBranchName(branch)
            pullCommand.setRebase(true)
            setupTransportAuthentication(sshConfig, context, pullCommand)
            def pullResult = pullCommand.call()

            def result = new ScmExportResultImpl()
            result.success = pullResult.successful
            result.message = result.success?"Rebase was successful":"Rebase failed"
            result.extendedMessage = pullResult.toString()
            return result
        } else {
            //fetch, then
            //merge
            fetchFromRemote(context)

            def strategy = MergeStrategy.get(mergeResolutionStrategy)
            def mergebuild = git.merge().setStrategy(strategy)
            def commit = git.repository.resolve("refs/remotes/${REMOTE_NAME}/${branch}")

            mergebuild.include(commit)

            def mergeresult = mergebuild.call()

            def result = new ScmExportResultImpl()<commit_msg>fix #1493 rebase result message is more informative<commit_after>package org.rundeck.plugin.scm.git

import com.dtolabs.rundeck.core.jobs.JobReference
import com.dtolabs.rundeck.core.plugins.configuration.Validator
import com.dtolabs.rundeck.core.plugins.views.Action
import com.dtolabs.rundeck.core.storage.ResourceMeta
import com.dtolabs.rundeck.plugins.scm.*
import org.apache.log4j.Logger
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.api.PullResult
import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.RebaseResult
import org.eclipse.jgit.api.Status
import org.eclipse.jgit.api.TransportCommand
import org.eclipse.jgit.diff.RawTextComparator
import org.eclipse.jgit.lib.BranchConfig
import org.eclipse.jgit.lib.ConfigConstants
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Repository
import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import org.eclipse.jgit.transport.TrackingRefUpdate
import org.eclipse.jgit.transport.URIish
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider
import org.eclipse.jgit.util.FileUtils
import org.rundeck.plugin.scm.git.config.Common
import org.rundeck.storage.api.StorageException

import java.util.regex.Matcher
import java.util.regex.Pattern

/**
 * Common features of the import and export plugins
 */
class BaseGitPlugin {
    public static final String REMOTE_NAME = "origin"
    Git git
    Repository repo
    File workingDir
    String branch
    Map<String, String> input
    Common commonConfig
    JobFileMapper mapper
    RawTextComparator COMP = RawTextComparator.DEFAULT
    Map<String, Map> jobStateMap = Collections.synchronizedMap([:])

    BaseGitPlugin(Common commonConfig) {
        this.input = commonConfig.rawInput
        this.commonConfig=commonConfig
    }

    Map<String, String> getSshConfig() {
        def config = [:]

        if (commonConfig.strictHostKeyChecking in ['yes', 'no']) {
            config['StrictHostKeyChecking'] = commonConfig.strictHostKeyChecking
        }
        config
    }

    def serialize(final JobExportReference job, format) {
        File outfile = mapper.fileForJob(job)
        if (!outfile.parentFile.isDirectory()) {
            if (!outfile.parentFile.mkdirs()) {
                throw new ScmPluginException(
                        "Cannot create necessary dirs to serialize file to path: ${outfile.absolutePath}"
                )
            }
        }
        outfile.withOutputStream { out ->
            job.jobSerializer.serialize(format, out)
        }
    }

    def serializeTemp(final JobExportReference job, format) {
        File outfile = File.createTempFile("${this.class.name}-serializeTemp", ".${format}")
        outfile.deleteOnExit()
        outfile.withOutputStream { out ->
            job.jobSerializer.serialize(format, out)
        }
        return outfile
    }

    def serializeAll(final Set<JobExportReference> jobExportReferences, String format) {
        jobExportReferences.each { serialize(it, format) }
    }

    TrackingRefUpdate fetchFromRemote(ScmOperationContext context, Git git1 = null) {
        def agit = git1 ?: git
        def fetchCommand = agit.fetch()
        fetchCommand.setRemote(REMOTE_NAME)
        setupTransportAuthentication(sshConfig, context, fetchCommand)
        def fetchResult = fetchCommand.call()

        def update = fetchResult.getTrackingRefUpdate("refs/remotes/${REMOTE_NAME}/${this.branch}")

        def fetchMessage = update ? update.toString() : "No changes were found"
        Logger.getLogger(this.class).debug("fetchFromRemote: ${fetchMessage}")
        //make sure tracking is configured for the branch
        if (!remoteTrackingBranch(agit)) {
            agit.repository.config.setString(
                    ConfigConstants.CONFIG_BRANCH_SECTION,
                    branch,
                    ConfigConstants.CONFIG_KEY_REMOTE,
                    REMOTE_NAME
            );
            //if remote branch name exists, track it for merging
            def remoteRef = fetchResult.getAdvertisedRef(branch) ?:
                    fetchResult.getAdvertisedRef(Constants.R_HEADS + branch)
            if (remoteRef) {
                agit.repository.config.setString(
                        ConfigConstants.CONFIG_BRANCH_SECTION,
                        branch,
                        ConfigConstants.CONFIG_KEY_MERGE,
                        remoteRef.name
                );
            }

            agit.repository.config.save()
        }

        return update
    }
    /**
     * @param git1
     * @return remote tracking branch name, or null if tracking has not be set up
     */
    def remoteTrackingBranch(Git git1 = null) {
        def agit = git1 ?: git

        def branchConfig = new BranchConfig(agit.repository.config, branch)
        return branchConfig.getRemoteTrackingBranch()
    }

    PullResult gitPull(ScmOperationContext context, Git git1 = null) {
        def pullCommand = (git1 ?: git).pull().setRemote(REMOTE_NAME).setRemoteBranchName(branch)
        setupTransportAuthentication(sshConfig, context, pullCommand)
        pullCommand.call()
    }

    ScmExportResult gitResolve(
            final ScmOperationContext context,
            boolean rebase,
            String mergeResolutionStrategy
    )
    {
        if (rebase) {
            def pullCommand = git.pull().setRemote(REMOTE_NAME).setRemoteBranchName(branch)
            pullCommand.setRebase(true)
            setupTransportAuthentication(sshConfig, context, pullCommand)
            def pullResult = pullCommand.call()

            def result = new ScmExportResultImpl()
            result.success = pullResult.successful
            result.message = result.success?"Rebase was successful":"Rebase did not succeed"
            if(pullResult.rebaseResult){
                result.extendedMessage = "Rebase result was: "+pullResult.rebaseResult.status?.toString()
                //get status
                boolean rebasestat=false
                if(pullResult.rebaseResult.conflicts) {
                    rebasestat = true

                    result.extendedMessage = result.extendedMessage + " Conflicts: " + pullResult.rebaseResult.conflicts
                }
                if(pullResult.rebaseResult.failingPaths){
                    rebasestat = true
                    result.extendedMessage = result.extendedMessage+" Failures: "+pullResult.rebaseResult.failingPaths
                }
                if(pullResult.rebaseResult.uncommittedChanges){
                    rebasestat = true
                    result.extendedMessage = result.extendedMessage+" Uncommitted changes: "+pullResult.rebaseResult.uncommittedChanges
                }
                if(!rebasestat) {
                    //rebase does not seem to actually include conflict info in result
                    def status = git.status().call()
                    if (status.conflicting || status.conflictingStageState) {
                        result.extendedMessage = result.extendedMessage + " Conflicts: " + status.conflictingStageState
                    }
                }
            }
            return result
        } else {
            //fetch, then
            //merge
            fetchFromRemote(context)

            def strategy = MergeStrategy.get(mergeResolutionStrategy)
            def mergebuild = git.merge().setStrategy(strategy)
            def commit = git.repository.resolve("refs/remotes/${REMOTE_NAME}/${branch}")

            mergebuild.include(commit)

            def mergeresult = mergebuild.call()

            def result = new ScmExportResultImpl()
{'fraction_non_alphanumeric': 0.0852575488454707, 'fraction_numerical': 0.0014209591474245115, 'mean_word_length': 2.428989037758831, 'pattern_counts': {'":': 2, '<': 11, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, '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': '5220870', 'n_tokens_mistral': 3841, 'n_tokens_neox': 3683, 'n_words': 748}