Git inspection tool based on Scala under IntelliJ IDEA

picture

This article uses Scala to implement a custom Git inspection tool. Readers can expand and implement based on the examples in this article, and can also try other application directions.

01. Git inspection tool

Before implementing a Git inspection tool, you need to know what exactly the program is going to do. We know that code merging operations can be performed when managing Git branches, so that the content submitted by other developers can be synchronized to the current branch, and when users submit their own branches, they will not conflict with the existing version.

Reverse merging can also be understood as a round, which often occurs when users use version management software such as GitLab, but reverse merging brings a very serious problem: code pollution.

It can be understood that the user branch is a medium between the production branch and the test branch, and it must ensure the compatibility with the two branches, that is, the file difference problem. Usually the user branch is a brand new branch pulled out based on production, and many developers try to use this branch to make changes and submit them to the test branch for test release.

Ideally, the test branch of the project should be consistent with the production branch, so the reverse merge is easy to be modified or corrected, but when the test branch and the production branch are quite different, the reverse merge will merge the contents of the test branch To the user branch, if the user branch is submitted to the production branch, an unrecoverable disaster will occur.

Based on the above reasons, we use Scala to design a simple inspection tool, which can inspect all commit information in a specified branch or branch group, and filter out the history with round operations from these information.

If a reverse merge operation has occurred, the Git commit history usually contains the message Mergeremotetrackingbranch..., but commits with this information do not necessarily cause merge problems.

After the branches that meet the above characteristics are filtered out through the Git inspection tool, you can use multiple methods to ensure the online by judging the number of differences from the production branch and setting a judgment threshold to filter in depth again or directly manually observing the differences of user branches. branch accuracy.

02. Write configuration

As mentioned in the Git version control management chapter, reverse merging will pollute the developer's project branch, so a tool for Git branch checking can be implemented, which can help us quickly during each routine version maintenance Locate the problem of reverse merge.

Tools may not be able to solve all problems, because the emergence of each problem has its own randomness, but tools can improve our efficiency in some ways. After reading this chapter, readers can expand and customize more functions as needed.

First, create a file named config.conf in the resources resource directory, which is used for the basic configuration of the Git inspection tool. The root directory of the local Git project and the branches to be checked are defined in the config.conf configuration file. The code is as follows:

{
  group1 = {
    workDir = "Git项目目录"
  }
  group2 = {
    workDir = "Git项目目录"
    base = master
    branches = [
      user_local_branch
    ]
  }
}

 

In the above configuration, the targets to be checked are grouped. At runtime, the user can pre-define the items and branches to be compared, so that after the project is started, it can dynamically adjust which group of configurations to use for checking the target branch by receiving parameters. and analyse.

In each set of configurations, workDir specifies the root directory of the local Git project. base is used to specify the main branch (master) of the project. branches is a list of branches, which represent the branches to be checked, these branches can be either local branches or remote branches. If it is a remote branch, you usually add the origin/ prefix in front of it.

Next define a configuration file for controlling log output, the code is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <properties>
        <property name="APP_HOME">$${env:APP_HOME}</property>
        <property name="LOG_HOME">${APP_HOME}/logs</property>
        <property name="mainFilename">${LOG_HOME}/vh.log</property>
    </properties>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT" follow="true">
            <PatternLayout pattern="%date{yyyy-MM-dd HH:mm:ss.SSS} %level - %msg%n" />
        </Console>
        <RollingFile name="FileMain" fileName="${mainFilename}"
                     filePattern="${LOG_HOME}/vh%date{yyyyMMdd}_%i.log.gz">
            <PatternLayout>
                <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} %level - %msg%n</pattern>
            </PatternLayout>
            <Policies>
                <CronTriggeringPolicy schedule="0 0 0 * * ?" evaluateOnStartup="true"/>
                <SizeBasedTriggeringPolicy size="20 MB" />
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console" />
            <AppenderRef ref="FileMain" />
        </Root>
    </Loggers>
</Configuration>

03. Write the startup program

Next, write the start-up program of the project. The start-up program can receive the parameters passed in from the outside to realize the switching of different configurations. The code is as follows:

package com.scala.git
import org.slf4j.LoggerFactory

object MainCheck {
  private val log = LoggerFactory.getLogger(getClass)
  def main(args: Array[String]): Unit = {
    log.info(s"接收外界传递的切换配置: ${args.group}")
    var group = "group2"
    if(args.length > 0){
      group = args(0)
    }
    log.info(s"当前配置为$group")
    group match {
      case "group2" => CheckTask.main(args)
      case _ => log.error(s"not found $group")
    }
  }
}

 

Because Scala programs can be mixed with Java language, it is relatively easy for Java developers to understand when reading Scala programs.

The main method of the MainCheck object receives the group parameter passed from the outside world, which can be dynamically passed to the main method when the program starts and replaces the default configuration group group2.

Next, match the group configuration represented by the group variable through the match operation. If the match is successful, execute the function call to the application. If there is no match, the log prompt will be output.

04. Write verification logic

In the MainCheck.scala application, when the external variable group is successfully matched, the specific execution logic will be called, and this logic is encapsulated in the CheckTask object method.

Before writing the CheckTask object, first write the GitUtil.scala program file. Its function is to call and execute the CMD command to obtain all the submission information of the specified branch. These submission information will be returned in the form of an array. The code is as follows:

package com.scala.util
import java.io.File
import org.slf4j.LoggerFactory

import scala.sys.process.{Process, ProcessLogger}

object GitUtil {
  private val isWin = System.getProperty("os.name").toLowerCase.contains("Windows")
  private val log = LoggerFactory.getLogger(getClass)

  def getCommits(from: String, to: String, workDir: String): String = {
    val cols = Array("%H", "%s", "%an", "%ae", "%ci")
    val tem = from + ".." + to + " --pretty=format:\"" + cols.mkString("/") + "\"";
    val value = cmdCommits(s"git log " + tem, new File(workDir))
    value
  }

  def cmdCommits(cmd: String, workDir: File): String = {
    var commits:Array[String] = null;
    if(!isWin){
      commits = cmd.split("\\s")
    }else{
      commits = Array("cmd", "/c") ++ cmd.split("\\s")
    }
    Process(commits, workDir).!!(ProcessLogger(s => log.error(s"err => $s")))
  }
}

 Next, implement the CheckTask.scala program file, the code is as follows:

package com.scala.git

import com.scala.util.GitUtil
import com.typesafe.config.ConfigFactory
import scala.collection.JavaConverters._

object CheckTask {

  private val config = ConfigFactory.load("config.conf").getConfig("group2")
  private val orderWorkDir = config.getString("workDir");
  private val base = config.getString("base");
  private val branchs = config.getStringList("branchs");

  def main(args: Array[String]): Unit = {
    println(s"参照对比分支[$base]")
    println(s"待检查分支集合$branchs")
    checkBraches(base, asScalaBuffer(branchs).toArray).foreach(b => println(s"发现可疑分支 $b"))
  }
  
  def checkBraches(base: String, brans: Array[String]): Array[String] = {
    brans.filter(b => checkMergeError(base, b))
  }

  private def checkMergeError(base: String, target: String): Boolean = {
        println(s"对比分支:$base,检查分支:$target")
        //取得所有提交信息
        val commits = getDiffCommits(base, target)
        //从历史提交记录过滤出回合过的分支
        val targets = commits.filter(isMergeReverse)
        targets.foreach(c => {println(c.mkString("\t"))})
        println(s"分支[$target]中可疑提交次数: ${targets.length}")
        targets.length != 0
  }

  private def isMergeReverse(messages: Array[String]): Boolean = {
    val msg = messages(1)
    if(msg.startsWith("Merge branch 'int_") || msg.startsWith("Merge remote-tracking branch ")){
      val splits = msg.split("\\s")
      val end = splits(splits.length-1)
      val flag = end.startsWith("int_") || end.startsWith("local_int_")
      return !flag
    }
    false
  }

  private def getDiffCommits(from: String, to: String): Array[Array[String]] = {
    GitUtil.getCommits(from, to, orderWorkDir).lines.map(_.split("/")).toArray
  }
}

Now try to run the tool, randomly select a Git project in the system and modify the config.conf configuration file to correspond to the branch in the Git project, and then run the MainCheck.scala program file, the running effect is shown in Figure 1.

picture

■ Figure 1 Running the Git check tool

 

 

 

 

 

 

Guess you like

Origin blog.csdn.net/qq_41640218/article/details/132404929