All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.dkirrane.gitflow.groovy.conflicts.FixPomMergeConflicts.groovy Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Desmond Kirrane
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package com.dkirrane.gitflow.groovy.conflicts

import com.dkirrane.gitflow.groovy.GitflowInit
import com.dkirrane.gitflow.groovy.ex.GitflowException
import com.dkirrane.gitflow.groovy.ex.GitflowMergeConflictException
import groovy.util.logging.Slf4j

import org.apache.maven.artifact.versioning.ComparableVersion

/**
 * Script to resolve merge conflicts of version numbers in Maven POMs after a merge
 * This would always happen after merging a release branch back into develop
 *
 * Based on https://gist.github.com/brettporter/1723108/raw/368dd34131e2cec0874c99464f7453fe4ad7c07a/fix_maven_conflicts.rb
 */
@Slf4j
class FixPomMergeConflicts {

    @Delegate GitflowInit init
    def msgPrefix = ""
    def msgSuffix = ""

    enum State {
        NORMAL_LINE, OUR_LINE, THEIR_LINE
    }

    // (?ms) makes it multiline
    // ^<{7} matches conflict start marker
    // .*? non-greedy match
    // ^>{7} matches conflict end marker
    // [^\n]+ macthes any characters til the end of the line
    //    static final CONFLICT_REGEX = ~/(?ms)^<{7}.*?^>{7}[^\n]+/

    void resolveConflicts() throws GitflowException, GitflowMergeConflictException {

        def conflicts = init.gitMergeConflicts()

        def pomFiles = conflicts.findAll { it.name =~ /pom\.xml$/ }

        pomFiles.each(){ pom, index ->

            def fileTxt = pom.text
            def matcher = fileTxt =~ /(?ms)^<{7}.*?^>{7}[^\n]+/
            log.warn "Conflicts:"
            matcher.each {
                log.warn it
                String resolvedVersionConflict = parseVersionConflict(it)
                log.info "Resolved Version Conflict: ${resolvedVersionConflict}"
            }
        }
    }

    def String parseVersionConflict(text) {
        def matcher = text =~ /(?ms)^\s*(.*?)<\/version>\s*$/

        log.debug "Versions:"

        def ourVersionTag = matcher[0][0]
        log.debug "Ours Tag "  + ourVersionTag
        def ourVersion = matcher[0][1]
        log.info "Ours [" + ourVersion + "]"

        def theirVersionTag = matcher[1][0]
        log.debug "Theirs Tag "  + theirVersionTag
        def theirVersion = matcher[1][1]
        log.info "Theirs ["  + theirVersion + "]"

        ComparableVersion ours = new ComparableVersion(ourVersion)
        log.debug "Our ComparableVersion [" + ours.toString() + "]"
        ComparableVersion theirs = new ComparableVersion(theirVersion)
        log.debug "Their ComparableVersion [" + theirs.toString() + "]"

        int compared = ours.compareTo(theirs)
        log.debug "Compared: " + compared
        if(compared >= 0){
            return ourVersionTag
        } else {
            return theirVersionTag
        }
    }

    void resolveConflicts2() throws GitflowException, GitflowMergeConflictException {
        log.debug "Checking for conflicts to resolve in ${init.repoDir}"

        def conflicts = init.gitMergeConflicts()        
        log.debug "Merge conflicts ${conflicts}"

        def pomFiles = conflicts.findAll { it.name =~ /pom\.xml$/ }
        log.debug "POM files ${pomFiles}"

        pomFiles.each() { pom ->

            def tmpFile = File.createTempFile('fixPomVersionConflicts', '.xml')
            log.info tmpFile.absolutePath

            List lines = pom.readLines()

            State state = State.NORMAL_LINE
            def conflict
            def outstandingConflicts = false

            lines.eachWithIndex { line, lineNo ->
                log.debug "${lineNo}: ${line}"

                if (line =~ /^<{7}/) {

                    if(state != State.NORMAL_LINE) {
                        throw new GitflowException("Encountered conflict start marker at line ${lineNo + 1}, but conflict at line ${conflict[lineNum]}")
                    }

                    state = State.OUR_LINE

                    conflict = [ lineNum: lineNo + 1 ]

                } else if (line =~ /^={7}/) {

                    if(state != State.OUR_LINE) {
                        throw new GitflowException("Encountered conflict separator at line ${lineNo}, but no conflict start marker found on previous lines")
                    }
                    state = State.THEIR_LINE

                } else if (line =~ /^>{7}/) {

                    if(state != State.THEIR_LINE) {
                        throw new GitflowException("Encountered conflict end marker at line ${lineNo}, but no conflict separator on previous lines")
                    }

                    def ourChange = conflict[State.OUR_LINE.name()]
                    def theirChange = conflict[State.THEIR_LINE.name()]

                    def leftVersion = parseVersion(ourChange)
                    log.debug "leftVersion = [" + leftVersion + "]"
                    def rightVersion = parseVersion(theirChange)
                    log.debug "rightVersion = [" + rightVersion + "]"

                    if (!leftVersion?.trim() || !rightVersion?.trim()) {
                        log.debug "Skipping conflict at line ${conflict[lineNum]} as it contains more than conflicting versions"

                        def format = formatConflict(conflict)
                        log.debug "Conflict " + format
                        tmpFile.append format + "\n"
                        outstandingConflicts = true
                    } else {
                        Integer compared = compareVersions(leftVersion, rightVersion)
                        if(compared >= 0){
                            log.debug "Resolved conflict using ours: " + ourChange
                            tmpFile.append ourChange + "\n"
                        } else {
                            log.debug "Resolved conflict using theirs: " + theirChange
                            tmpFile.append theirChange + "\n"
                        }
                    }

                    state = State.NORMAL_LINE
                } else {
                    if(state != State.NORMAL_LINE){
                        log.debug "State = ${state.name()}"
                        def key = state.name()
                        if(!conflict[key]) {
                            conflict[key] = ""
                        }
                        conflict[key] += line
                    }
                    else {
                        tmpFile.append line + "\n"
                    }
                }
            }

            log.debug "Temp file:\n" + tmpFile.text
            if(state != State.NORMAL_LINE) {
                throw new GitflowException("Unclosed conflict marker at line ${conflict[lineNo]}")
            }

            if (null != conflict) {
                pom.renameTo(new File(pom.parent, "pom.xml.orig"))
                tmpFile.renameTo(new File(pom.parent, "pom.xml"))
                if(!outstandingConflicts) {
                    init.executeLocal("git add ${pom.path}")
                    def resolveMsg = "${msgPrefix}Auto-resolve pom version conflicts${msgSuffix}"
                    init.executeLocal(["git", "commit", "-m", "\"${resolveMsg}\""])
                }
            } else {
                tmpFile.delete()
            }

        }
    }

    def String parseVersion(text) {
        def matcher = text =~ /^\s*\s*(.*?)\s*<\/version>\s*$/

        log.debug "Versions:"

        def versionTag = null
        def version = null
        if(matcher[0]){
            versionTag = matcher[0][0]
            log.debug "Version Tag "  + versionTag
            version = matcher[0][1]
            log.debug "Version [" + version + "]"
        }

        return version
    }

    def Integer compareVersions(String ourVersion, String theirVersion) {
        ComparableVersion ours = new ComparableVersion(ourVersion)
        log.debug "Our ComparableVersion [" + ours.toString() + "]"
        ComparableVersion theirs = new ComparableVersion(theirVersion)
        log.debug "Their ComparableVersion [" + theirs.toString() + "]"

        int compared = ours.compareTo(theirs)
        log.debug "Compared: " + compared
        return compared
    }

    def String formatConflict(conflict){
        def format =\
            "<<<<<<<\n" +
            "${conflict[State.OUR_LINE.name()]}\n" +
            "=======\n" +
            "${conflict[State.THEIR_LINE.name()]}\n" +
            ">>>>>>>\n"
        return format
    }

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy