Python 3.x基于Svn补丁日志自动生成Java项目补丁包

1. 前言

之前打补丁的方法一直是将项目编译后的文件全部拷贝出来,然后再删除不必要的文件,经常会为了一个小BUG或其它什么的打补丁,然后就一直删删删。然而作为一名爱“偷懒”的程序猿,能让代码做的,就坚决不会自己动手做,正好最近学习了Python,所以决定使用Python开发一个基于Svn补丁日志的自动生成补丁包的脚本。

2. 使用说明

先看看如何使用,再贴源码吧。

为了使用方便,尽可能减少操作,我决定增加一个配置文件,有关配置文件的使用方式将在下面的使用说明中描述。

2.1 生成Svn补丁包日志

右键项目->Team->创建补丁,弹出如下界面。

这里写图片描述

选择所需补丁文件,并选择保存到文件系统,然后点’Finish’,就会在指定位置生成一个txt的文档,这里的名称为patch.txt。

2.2 启动脚本

生成patch.txt后,便可以启动脚本,如果不配置属性文件,则操作如下:

1) 双击start.py文件,或者在cmd窗口输入”python start.py”;
2) 弹出选择框,选择刚刚生成的patch.txt文件;
3) 选择完文件后,需要进一步在弹出框选择项目空间的位置;
4) 生成完成,生成完成后的界面如下图(这里并非2.1中对应选择的文件,另外部分显示由于保密性要求做了模糊处理)。

这里写图片描述

但是对于上面的操作,还是太麻烦了,必须得选择补丁日志文件的位置,竟然还要选择项目空间的位置。好,既然大家都觉得麻烦,那么就需要用上配置文件了,配置文件代码如下:

config.properties

# 这是补丁生成器的配置文件
# 其规则遵循[属性名=属性值]的规则,不允许留空格

# SVN创建补丁日志文件默认路径
DEFAULT_SVN_LOG_PATH=C:/Users/admin/Desktop/patch.txt

# 是否使用SVN补丁日志文件默认位置,1:是,0:否,这关系着是否弹出选择框选择文件
IS_DEFAULT_SVN_LOG_PATH=1

# 工作空间默认位置,可为空
DEFAULT_WORKSPACE_PATH=E:/newWorkSpace/jjzh

# 是否使用工作空间默认位置,1:是,0:否,这关系着是否弹出选择框选择文件
IS_DEFAULT_WORKSPACE_PATH=1

# 是否自动生成补丁包名称,1:是,0:否(此项也废弃,无用)
IS_AUTO_GENERATE_PATCHNAME=1

因为Svn补丁日志文件与项目空间都是极少改动的,并且Svn日志文件只要在生成的时候选择同样一个位置便不需要以后每次启动脚本都去选择它的位置,对于项目空间的位置,尽管项目发生改变的几率很大,但是项目空间则相对发生改变的纪律相对较小,所以使用上面的配置则可以省去选择操作,那么改变配置文件后的操作如下(配置文件的位置路径需要保证正确):

1) 双击start.py文件,或者在cmd窗口输入”python start.py”;
2) 生成完成,其界面如上图。

3. 源码

首先针对此脚本所用的主要知识点作一个总结吧,如下:

  • 读取与写入文件
  • tkinter.filedialog模块的使用
  • 自动生成文件名的简易算法
  • 解析Svn日志的简易算法
  • 全局变量与实例变量的定义和使用
  • 目录的自动生成
  • ……

由于源码中有相应的注释,便不过多的累述了,Start.py源码如下:

# 根据SVN增量补丁日志,自动生成补丁包
import tkinter.filedialog as tkFD
import os, shutil, time, sys
from tkinter.filedialog import askopenfilename
from tkinter import *

# 允许的文件扩展名
FILTER_EXTEN = ['.java', '.class', '.xml', '.properties']

# 目标文件夹生产过滤(比如WebRoot目录是不需要的)
FILTER_TARGET_DIR = ['WebRoot/']

# 补丁生成路径
PATCH_GENERATE_PATH = ''

# 补丁包名称
PATCH_NAME = ''

# 异常
IS_EXCEPTION = False

# -------------------- 以下属性由配置文件读出 --------------------
# SVN补丁日志文件默认位置
DEFAULT_SVN_LOG_PATH = ''

# 是否使用SVN补丁日志文件默认位置
IS_DEFAULT_SVN_LOG_PATH = 0

# 工作空间默认位置,可为空
DEFAULT_WORKSPACE_PATH = ''

# 是否使用工作空间默认位置
IS_DEFAULT_WORKSPACE_PATH = 0

# 是否自动生成补丁包名称
IS_AUTO_GENERATE_PATCHNAME = 1


# 创建一个补丁生成类
class Patch():
    patchFile           = ''
    projectName         = ''
    projectPath         = ''
    patchFileList       = []  # 补丁文件列表
    filterPatchFileList = []  # 被过滤的文件列表
    patchLog            = []  # 日志信息列表

    # 构造函数
    def __init__(self):
        # 解析配置文件
        self.parseConfigFile()

    # 开始打补丁
    def start(self):
        # 选择SVN补丁日志文件
        self.selectFile()
        # 生成补丁包名称
        self.generatePatchName()
        # 解析文件
        self.parseSvnPatchFile()
        # 打印补丁包信息
        self.printPatchFile()
        # 生成补丁包
        self.generatePatch()

    # 选择文件
    def selectFile(self):
        global IS_DEFAULT_SVN_LOG_PATH
        global DEFAULT_SVN_LOG_PATH

        # 根据配置文件中的信息,判断是否需要使用默认的地址
        if IS_DEFAULT_SVN_LOG_PATH != '1':
            root = Tk()
            root.withdraw()
            self.patchFile = tkFD.askopenfilename(defaultextension=".txt",initialdir="/home/",title="选择SVN补丁文件")
        else:
            self.patchFile = DEFAULT_SVN_LOG_PATH

    # 解析SVN补丁日志文件
    def parseSvnPatchFile(self):
        global FILTER_EXTEN


        print(self.patchFile)

        # 如果文件不存在
        if not os.path.exists(self.patchFile):
            self.patchLog.append('\r\n')
            self.patchLog.append(' >> 不存在的SVN补丁日志文件:' + self.patchFile)
            print(' >> 不存在的SVN补丁日志文件:' + self.patchFile)
            IS_EXCEPTION = True
            return

        patchFile = open(self.patchFile, 'r')

        '''
        # SVN补丁文件规则描述(仅目前所知,非正规文档描述)
        # 对于有修改或增加的文件,大致为如下格式(0, 1, 2, 3行):
        0 Index: src/gnnt/service/ApplyAndAuditService.java
        1 ===================================================================
        2 --- src/gnnt/service/ApplyAndAuditService.java    (revision 9003)
        3 +++ src/gnnt/service/ApplyAndAuditService.java    (working copy)
        4 ...
        # 将根据以上规则获取增量文件名
        '''
        prepare          = False  # 准备阶段
        possibleFilename = '' # 可能的文件名

        for line in patchFile.readlines():

            # 解析被记录的文件
            prefixIden = line[0: 6]  # 获取前6个字符
            if prefixIden == 'Index:':
                line = line.replace('\t', ' ')         # 将制表符转换为空格
                line = line.replace('\n', '')         # 将换行符去掉
                lineList = line.split(' ')
                if len(lineList) >= 2:
                    possibleFilename = lineList[1]

                    # 保留java源文件
                    oldFilename = possibleFilename;

                    # 将java文件替换成class文件
                    possibleFilename = possibleFilename.replace('src/', 'WebRoot/WEB-INF/classes/')
                    possibleFilename = possibleFilename.replace('.java', '.class')

                    self.addPatchFile(possibleFilename)

                    # 保留java源文件
                    if oldFilename != possibleFilename:
                        self.addPatchFile(oldFilename)

                continue

            # 解析项目名称
            prefixIden = line[0: 2]  # 获取前2个字符
            if prefixIden == '#P':
                lineList = line.split(' ')
                if len(lineList) >= 2:
                    self.projectName = lineList[1].replace('\n', '')
                    # print()
                    # print('解析到的项目名称:', self.projectName)
                    self.patchLog.append('\n')
                    self.patchLog.append('解析到的项目名称:' + self.projectName)

        patchFile.close()

    # 添加文件至解析完成的补丁文件
    def addPatchFile(self, patchFilename):
        isAllow = False
        for exten in FILTER_EXTEN:
            if patchFilename[0 - len(exten):] == exten:
                isAllow = True
                continue

        if not isAllow:
            # self.patchLog.append('被过滤的文件:' + patchFilename)
            self.filterPatchFileList.append(patchFilename)
        else:
            self.patchFileList.append(patchFilename)


    # 生成补丁包
    def generatePatch(self):
        global PATCH_GENERATE_PATH
        global FILTER_TARGET_DIR
        global PATCH_NAME
        global IS_DEFAULT_WORKSPACE_PATH
        global DEFAULT_WORKSPACE_PATH

        # 设置项目地址
        if IS_DEFAULT_WORKSPACE_PATH != '1':
            projectPath = tkFD.askdirectory(initialdir="/home/",title="选择工作空间") + '/' + self.projectName
        else:
            projectPath = DEFAULT_WORKSPACE_PATH + '/' + self.projectName

        self.projectPath = projectPath

        # 补丁生产目录添加上补丁名称与项目名称
        if (PATCH_GENERATE_PATH == ''):
            patchDir = PATCH_NAME + self.projectName + "/"
        else:
            patchDir = PATCH_NAME + PATCH_GENERATE_PATH + '/' + self.projectName + "/"

        # print()
        # print('补丁包生成详细信息:')
        self.patchLog.append('\r\n')
        self.patchLog.append('补丁包生成详细信息:')
        for patchFile in self.patchFileList:
            targetPatchFile = patchFile

            # 将目标文件夹中不需要的文件夹名称去掉
            for filterDir in FILTER_TARGET_DIR:
                if patchFile[:len(filterDir)] == filterDir:
                    targetPatchFile = patchFile[len(filterDir):]    # 目标文件位置

            dirList = targetPatchFile.split('/')
            targetDirPath = targetPatchFile[0: len(targetPatchFile) - len(dirList[len(dirList) - 1])]

            # 如果源文件存在
            if os.path.exists(projectPath + '/' + patchFile):
                # 如果目录不存在则创建目录
                if os.path.exists(patchDir + targetDirPath) == False:
                    os.makedirs(patchDir + targetDirPath)
                    # print('  >> 创建目录: ' + patchDir + targetDirPath)
                    self.patchLog.append('  >> 创建目录:' + self.projectName + '/' + targetDirPath)  # patchDir

                # 拷贝文件
                shutil.copyfile(projectPath + '/' + patchFile,  patchDir + targetPatchFile)

                # print('  >> 拷贝文件:', projectPath)
                self.patchLog.append('  >> 拷贝文件:' + projectPath + '/' + patchFile)
            else:
                # print('  >> 不存在的:', projectPath + '/' + patchFile)
                self.patchLog.append('  >> 不存在的:' + projectPath + '/' + patchFile)

        self.projectPath = projectPath

    # 打印解析到的补丁文件
    def printPatchFile(self):
        # print()
        # print("-----------------------------------补丁包文件-----------------------------------")
        # print('解析到的补丁文件:')
        self.patchLog.append('\r\n')
        self.patchLog.append('解析到的补丁文件:')
        for filename in self.patchFileList:
            # print('  >>', filename)
            self.patchLog.append('  >>' + filename)
        # print("--------------------------------------------------------------------------------")

        self.patchLog.append('\r\n')
        self.patchLog.append('被过滤的补丁文件:')
        for filename in self.filterPatchFileList:
            self.patchLog.append('  >>' + filename)

    # 获取补丁名称
    def inputPatchName(self):
        global PATCH_NAME

        patchName = input("请输入补丁名称:")

        if (patchName != ''):
            patchName = patchName + '/'

        PATCH_NAME = patchName

    # 解析配置文件
    def parseConfigFile(self):
        global DEFAULT_SVN_LOG_PATH
        global IS_DEFAULT_SVN_LOG_PATH
        global DEFAULT_WORKSPACE_PATH
        global IS_DEFAULT_WORKSPACE_PATH

        configFile = open('config.properties')

        for line in configFile.readlines():
            line = line.replace('\t', ' ')         # 将制表符转换为空格
            line = line.replace('\n', '')          # 将换行符去掉

            if len(line) > 0:
                # 判断是否为注释
                prefixIden = line[0 : 1]
                if prefixIden == '#':
                    continue

                # 获取属性
                if '=' in line:
                    attrMap = line.split('=')
                    if len(attrMap) >= 2:
                        key   = attrMap[0]    # 获取键
                        value = attrMap[1]  # 获取值

                        if key == 'DEFAULT_SVN_LOG_PATH':
                            DEFAULT_SVN_LOG_PATH = value
                        elif key == 'IS_DEFAULT_SVN_LOG_PATH':
                            IS_DEFAULT_SVN_LOG_PATH = value
                        elif key == 'DEFAULT_WORKSPACE_PATH':
                            DEFAULT_WORKSPACE_PATH = value
                        elif key == 'IS_DEFAULT_WORKSPACE_PATH':
                            IS_DEFAULT_WORKSPACE_PATH = value
                        elif key == 'IS_AUTO_GENERATE_PATCHNAME':
                            IS_AUTO_GENERATE_PATCHNAME = value
            configFile.close()
        # pass

    # 生成补丁包名称
    def generatePatchName(self):
        global IS_AUTO_GENERATE_PATCHNAME
        global PATCH_NAME

        patchName = ''

        if IS_AUTO_GENERATE_PATCHNAME == 1:
            patchnameList = []
            nowDate   = time.strftime('%Y%m%d', time.gmtime(time.time()))
            # 遍历当前目录
            pathDir = os.listdir('./')
            for pdir in pathDir:
                if pdir[0: len(nowDate)] == nowDate:
                    patchnameList.append(pdir[len(nowDate):])
                    # print(pdir[len(nowDate):])

            temp = 0
            for patchname in patchnameList:
                if int(patchname) > temp:
                    temp = int(patchname)

            PATCH_NAME = nowDate + ('%05d' %(temp + 1)) + '/'

        else:
            self.inputPatchName()

    # 生成日志
    def generateLog(self):
        if not IS_EXCEPTION:
            if os.path.exists(PATCH_GENERATE_PATH + PATCH_NAME) == False:
                    os.makedirs(PATCH_GENERATE_PATH + PATCH_NAME)

            logFile = open(PATCH_GENERATE_PATH + PATCH_NAME + 'log.txt', 'w')

            for log in self.patchLog:
                logFile.write(log)
                logFile.write('\r\n')

                time.sleep(0.01)

                print(log)

            logFile.close()

            print('  >> 生成文件:补丁日志文件完成')

def cur_file_dir():
     #获取脚本路径
     path = sys.path[0]

     #判断为脚本文件还是py2exe编译后的文件,如果是脚本文件,则返回的是脚本的目录,如果是py2exe编译后的文件,则返回的是编译后的文件路径
     if os.path.isdir(path):
         return path
     elif os.path.isfile(path):
         return os.path.dirname(path)


if __name__ == "__main__":
    print()
    print("正在生成补丁...")
    print()
    print("正在解析文件...")

    patch = Patch()
    patch.start()

    patch.generateLog()

    if not IS_EXCEPTION:
        print()
        print('生成补丁包完成:')
        print('  >> 补丁包名称:', PATCH_NAME[0: -1])
        print('  >> 补丁包路径:', cur_file_dir().replace('\\', '/') + '/' + PATCH_NAME)

    # input()
    print()
    os.system("pause")

需要说明的是,FILTER_EXTEN的值将影响生成补丁的文件,只有后缀名符合的文件才会被生成,而FILTER_TARGET_DIR的值将影响生成的文件夹,即以这个数组中值开头的文件夹将去掉此值为名称的文件夹(仅限开头),这是因为Java源文件编译后的目录可能在WebRoot目录下,但是Tomcat部署中却不需要以此文件夹开头。

以上。由于初学Python,难免会在代码设计与实现上出现纰漏,如有更好的实现方式,欢迎指正,谢谢。

说明:此Pyhton脚本基于Python 3.5 + Windows开发,未测试在Python 2.x及linux或其他平台上的是否能实现,同时此脚本仅针对Java Web项目,对于其他项目可自行修改源码。

本脚本完整版下载地址:http://download.csdn.net/detail/t894690230/9421868


2016.01.29更新

由于之前的代码不支持生成Java源文件,今日特作更新,文中源代码和下载地址均已被更新,目前已支持文件过滤、生成源文件等。

github网站下载地址:https://github.com/tanliang199/aAutoPackJw

后续更新将放在github上。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值