• 编译时进行版本控制


    文章目录

    编译时进行版本控制

    概述

    自动备份本地库程序并自动进行版本控制发出不久,有同学提出建议,能不能实时备份,同时设置2个任务的方式有点繁琐。而且每天定点提交,并不能完全解决时差带来的版本不一致问题。

    之前讲Git与VSCode与M的时候,实际上利用VSCode连接IRIS库可以实现Git版本控制,但是也得需要去手动的导出,提交等操作。

    经过一段时间思考琢磨后,我们能不能利用原生的Studio环境在编译时进行备份呢,因为每次编译会自动保存最新的代码,从而达到实时备份还能省去手动的导出,提交等操作。

    实时备份可以解决如下问题:

    • 能记录每次编译时改动的内容。
    • 可恢复任意一次编译时的版本。
    • 防止类覆盖,代码丢失的情况,及时止损。
    • 也可作为DevOps中的一环。

    那么我们该如何实现编译时进行备份呢?请看如下过程:

    解决方案

    原理

    通过IRIS调用PythonGit库,再利用M的代码生成器在编译时运行导出方法,继而再调用Git库的命令进行版本控制。

    实现过程

    编写M相关代码。

    1. 定义导出文件路径。
    Parameter classPath = "E:\m\cls\";
    
    • 1
    1. 编写导出程序。
    ClassMethod ExportCls(cls = "", classPath = {..#classPath})
    {
    	#; 判断路径文件夹是否存在
        if ('##class(%File).DirectoryExists(classPath)){
    	    #; 不存在创建文件夹
            d ##class(%File).CreateNewDir(classPath, "")
        }
        #; 导出类
    	d $system.OBJ.Export(cls _ ".cls", classPath _ cls _ ".xml")
    	q $$$OK
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 编写编译时导出当前类的方法。
    • 给要编译时导出的类声明个标志autoBackup,有此标志的类在编译时进行导出。
    ClassMethod CompileBackUp() [ CodeMode = objectgenerator ]
    {
    	q:(%class.ClientName '= "autoBackup") $$$OK
    	d ..ExportCls(%class.Name)
    	q $$$OK
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 给需要进行版本控制的类添加如下属性与父类。
    • M.Git,用于实现编译时进行版本控制。
    • ClientName = autoBackup,用于标识该类是否自动备份。
    Class M.Test Extends (%RegisteredObject, M.Git) [ ClientName = autoBackup ]
    {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    1. M.Test进行编译,发现在E:\m\cls\文件夹内成功导出该类文件。

    在这里插入图片描述

    编写Python相关代码

    1. 安装gitpython库,调用Git命令进行版本控制。
    • 在安装路径C:\InterSystems\IRISHealth\bin,输入cmd,进入命令行控制台输入命令。安装gitpython库。
    irispip install --target C:\InterSystems\IRISHealth\mgr\python gitpython
    
    • 1
    C:\InterSystems\IRISHealth\bin>irispip install --target C:\InterSystems\IRISHealth\mgr\python gitpython
    Collecting gitpython
      Using cached GitPython-3.1.29-py3-none-any.whl (182 kB)
    Collecting gitdb<5,>=4.0.1
      Using cached gitdb-4.0.10-py3-none-any.whl (62 kB)
    Collecting smmap<6,>=3.0.1
      Using cached smmap-5.0.0-py3-none-any.whl (24 kB)
    Installing collected packages: smmap, gitdb, gitpython
    Successfully installed gitdb-4.0.10 gitpython-3.1.29 smmap-5.0.0
    
    [notice] A new release of pip available: 22.2.2 -> 22.3.1
    [notice] To update, run: python.exe -m pip install --upgrade pip
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    1. 我们已经成功在编译时把类导出,要想进行版本控制,首先需要进行初始化仓库。
    /// desc:初始化仓库
    ClassMethod GitInit(path) As %String [ Language = python ]
    {
    	from git import Repo
    	Repo.init(path)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 因为是Language = python无法给参数进行设置默认值,所以需要回调一下。
    /// desc:调用初始化
    ClassMethod RunInit(path = {..#classPath}) As %String
    {
    	d ..GitInit(path)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 执行方法后E:\m\cls\文件夹初始化仓库成功。
    d ##class(M.Git).RunInit()
    
    • 1

    在这里插入图片描述

    1. 初始化成功后,我们需要对导出的类进行添加暂存区提交工作区。
    /// desc:添加暂存区提交工作区
    ClassMethod GitCommit(path) As %String [ Language = python ]
    {
    	import os
    	from git.repo.fun import is_git_dir
    	from git.repo import Repo
    	repo = Repo(path)
    	commit_message = '自动备份'
    	repo.index.add("*.xml")
    	repo.index.commit(commit_message)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 按照如下,修改方法CompileBackUp()方法。
    /// desc:编译时备份,备份前要确保文件夹初始化Git仓库
    /// d ##class(M.Git).ExportCls("M.BackUp")
    ClassMethod CompileBackUp() [ CodeMode = objectgenerator ]
    {
    	q:(%class.ClientName '= "autoBackup") $$$OK
    	d ..ExportCls(%class.Name)
    	d ..GitCommit(..#classPath)
    	q $$$OK
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 回到M.Test再次编译该类。
    • 可以看到我们成功将导出的文件提交到了本地的工作区。

    在这里插入图片描述

    • 修改M.Test类添加方法再次编译,测试一下类变化进行版本控制。
    Class M.Test Extends (%RegisteredObject, M.Git) [ ClientName = autoBackup ]
    {
    
    /// desc:查询全局变量
    /// w ##class(M.Test).QueryGlobalDetail1().%ToJSON()
    ClassMethod QueryGlobalDetail1(pJson As %Library.DynamicObject)
    {
    	s array = []
    	s rs = ##class(%ResultSet).%New("%Global:Get")
    	d rs.Execute("USER","^yx(99:100)","",2,2,1)
        while(rs.Next()){
    
    		s obj = {}
    		s obj.name = rs.Data("Name")
    		s obj.value = rs.Data("Value")
    		s obj.nameFormat = rs.Data("Name Format")
    		s obj.valueFormat = rs.Data("Value Format")
    		s obj.permissions = rs.Data("Permissions")
    		d array.%Push(obj)
    	
        }
        q array
    }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 查看Git日志。

    在这里插入图片描述

    • 进行文件对比。

    总结

    按照上述方法,我们就实现在服务端或本地的一个版本控制。

    可能有小伙伴想提交到本地库之后,还想Push到远程仓库。

    其实也是可以的,下面我们来实现一下。

    远程仓库

    • 首先要创建一个远程仓库。这里我用Gitlab创建了一个名为python的远程仓库。复制远程库的链接放到代码里。

    在这里插入图片描述

    /// desc:先拉取再推送,确保本地版本与远程仓库是一致的再,提交并推送远程仓库
    ClassMethod GitCommitPush(path) As %String [ Language = python ]
    {
    	import os
    	from git.repo.fun import is_git_dir
    	from git.repo import Repo
    	
    	repo = Repo(path)
    	commit_message = '自动备份'
    	repo.index.add("*.xml")
    	repo.index.commit(commit_message)
    	
    	git = repo.git
    	
    	# 如果是你私有仓库能确保版本一致,可直接Push。
    	git.config("user.name","yx")
    	git.config("user.email","454115408@qq.com")
    	git.pull("http://username:password@8.142.29.250:7091/root/python.git","master","--allow-unrelated-histori")
    	git.push("http://username:password@8.142.29.250:7091/root/python.git","master")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 按照如下,修改方法CompileBackUp()方法。
    ClassMethod CompileBackUp() [ CodeMode = objectgenerator ]
    {
    	q:(%class.ClientName '= "autoBackup") $$$OK
    	d ..ExportCls(%class.Name)
    	
    	#; 没有远程仓库使用GitCommit
    	#; d ..GitCommit(..#classPath)
    	
    	#; 有远程仓库例如GitlabGitCommitPush
    	d ..GitCommitPush(..#classPath)
    	q $$$OK
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 回到M.Test再次编译该类,查看GitLab发现提交成功。

    在这里插入图片描述

    • 打开M.Test文件查看文件。

    在这里插入图片描述

    • 在添加一个方法。
    ClassMethod PythonMath() As %String
    {
    	s math = ##class(%SYS.Python).Import("math")
    	q math.pi
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 进行编译。

    在这里插入图片描述

    • 打开M.Test文件查看文件。

    在这里插入图片描述

    • 查看Git日志。

    在这里插入图片描述

    在这里插入图片描述

    这样我们就可以在远程仓库与本地库进行版本控制,进行双保险。

    错误提示

    如果提交失败,Studio会提示出具体报错信息。

    • 例如,输入不正确的库连接,把连接后面加了个1

    在这里插入图片描述

    最后

    综上所诉:只需要对类继承M.Git,就可以实现编译时版本控制了。

    • 如果想手动备份,可在Terminal中去执行ExportCls方法。
    • 如果编译太频繁,不想对类进行控制版本可以去掉标志autoBackup
    • 如果编译太频繁,不想每次编译都备份,可以设置个定时器方法。例如2次编译时间小于5分钟不进行版本控制。

    注:此方案仅适用于IRIS,因为老版Caché无法使用嵌入式Python


    • 另外还可以判断根据类是否发生改变,来判断是否导出。
    SELECT TimeChanged,TimeCreated FROM   %Dictionary.CompiledClass WHERE ID = "M.M91"
    
    • 1
    • TimeChanged 每次编译时会变化。
    • TimeCreated 每次类文件有修改时变化。

    参考

    GitPython Tutorial — GitPython 3.1.29 documentation

    完整代码

    • M.Git
    Class M.Git Extends %RegisteredObject
    {
    
    Parameter classPath = "E:\m\cls\";
    
    /// desc:编译时备份,备份前要确保文件夹初始化Git仓库
    /// d ##class(M.Git).ExportCls("M.BackUp")
    ClassMethod CompileBackUp() [ CodeMode = objectgenerator ]
    {
    	q:(%class.ClientName '= "autoBackup") $$$OK
    	d ..ExportCls(%class.Name)
    	
    	#; 没有远程仓库使用GitCommit
    	#; d ..GitCommit(..#classPath)
    	
    	#; 有远程仓库例如GitlabGitCommitPush
    	d ..GitCommitPush(..#classPath)
    	q $$$OK
    }
    
    ClassMethod RunBackUp()
    {
    	q ..ExportCls($this)
    }
    
    /// d ##class(M.Git).ExportCls("M.BackUp")
    ClassMethod ExportCls(cls = "", classPath = {..#classPath})
    {
    	#; 判断路径文件夹是否存在
        if ('##class(%File).DirectoryExists(classPath)){
    	    #; 不存在创建文件夹
            d ##class(%File).CreateNewDir(classPath, "")
        }
        #; 导出类
    	d $system.OBJ.Export(cls _ ".cls", classPath _ cls _ ".xml")
    	q $$$OK
    }
    
    /// desc:调用初始化
    /// d ##class(M.Git).RunInit()
    ClassMethod RunInit(path = {..#classPath}) As %String
    {
    	d ..GitInit(path)
    }
    
    /// desc:初始化
    /// w ##class(M.Git).GitInit({..#classPath})
    ClassMethod GitInit(path) As %String [ Language = python ]
    {
    	from git import Repo
    	Repo.init(path)
    }
    
    /// desc:添加暂存区提交工作区
    ClassMethod GitCommit(path) As %String [ Language = python ]
    {
    	import os
    	from git.repo.fun import is_git_dir
    	from git.repo import Repo
    	repo = Repo(path)
    	commit_message = '自动备份'
    	repo.index.add("*.xml")
    	repo.index.commit(commit_message)
    }
    
    /// desc:先拉取再推送,确保本地版本与远程仓库是一致的再,提交并推送远程仓库
    ClassMethod GitCommitPush(path) As %String [ Language = python ]
    {
    	import os
    	from git.repo.fun import is_git_dir
    	from git.repo import Repo
    	
    	repo = Repo(path)
    	commit_message = '自动备份'
    	repo.index.add("*.xml")
    	repo.index.commit(commit_message)
    	
    	git = repo.git
    	
    	# 如果是你私有仓库能确保版本一致,可直接Push。
    	git.config("user.name","yx")
    	git.config("user.email","454115408@qq.com")
    	git.pull("http://username:password@8.142.29.250:7091/root/python.git","master","--allow-unrelated-histori")
    	git.push("http://username:password@8.142.29.250:7091/root/python.git","master")
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • M.Test
    Class M.Test Extends (%RegisteredObject, M.Git) [ ClientName = autoBackup ]
    {
    
    /// desc:查询全局变量
    /// w ##class(M.Test).QueryGlobalDetail1().%ToJSON()
    ClassMethod QueryGlobalDetail1(pJson As %Library.DynamicObject)
    {
    	s array = []
    	s rs = ##class(%ResultSet).%New("%Global:Get")
    	d rs.Execute("USER","^yx(99:100)","",2,2,1)
        while(rs.Next()){
    
    		s obj = {}
    		s obj.name = rs.Data("Name")
    		s obj.value = rs.Data("Value")
    		s obj.nameFormat = rs.Data("Name Format")
    		s obj.valueFormat = rs.Data("Value Format")
    		s obj.permissions = rs.Data("Permissions")
    		d array.%Push(obj)
    	
        }
        q array
    }
    
    ClassMethod PythonMath() As %String
    {
    	s math = ##class(%SYS.Python).Import("math")
    	q math.pi
    }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    干货 | 接口自动化测试分层设计与实践总结
    计算机网络——物理层(互联网接入技术)
    【JavaEE基础学习打卡07】JDBC之应用分层设计浅尝!
    推荐几个 vue后台框架
    服务器选择独享还是共享带宽
    ysoserial CommonsCollections3 分析
    研发流程不只是一个流程
    opencv3.4源码编译、安装
    最短路径专题5 最短路径
    如何评估以及优化谷歌ads
  • 原文地址:https://blog.csdn.net/yaoxin521123/article/details/128089320