Source Control 2

This version of source control for Caché inherits of the default class and add functionalities:
 * can control either class code or routine code
 * contains a method to build the whole project from the file sources
 * source control can shut off (in production mode)
 * source files can be modified outside Caché without calculating a checksum (case of xml export)

We use it with CVS version 2.2.

Include macros

/// Contenu de ^["USER"]MPLSources /// ^["USER"]MySources(0,InternalName)= save date time in Horolog format /// ^["USER"]MySources(1,InternalName)= save date time in ODBC format /// ^["USER"]MySources(2,InternalName)= $LB( user, ? ) Class Roche.SourceControl Extends %Studio.SourceControl.Base [ ProcedureBlock ] {

/// Check this routine/class/csp file into source control. Method CheckIn(InternalName As %String, Description As %String) As %Status {	q ..ExportSource(InternalName) }

/// Check this routine/class/csp file out of source control. Method CheckOut(InternalName As %String, Description As %String) As %Status {	q ..ImportSource(InternalName) }

/// Compare two date times in Horolog format /// returns -1 if dh1dh2 ClassMethod DTCompare(dh1, dh2) As %Integer {	s d1=+dh1 s d2=+dh2 q:d1d2 1 s d1=$P(dh1,",",2) s d2=$P(dh2,",",2) q:d1d2 1 q 0 }

/// Export the whole project to file. Use it in terminal window ClassMethod ExportProject(projectName) {	s class="" f { s class=$O(^oddPROJECT(projectName,"Items",class)) q:class="" s ext=$O(^oddPROJECT(projectName,"Items",class,"")) i ext'="MAC" s class=class_"."_ext w class," -> ",..ExportSource(class,1),! } }

/// Export a routine in RSA format to a file ClassMethod ExportRoutine(routName, extension, fileName) As %Status {	s path=$P(fileName,"\",1,$L(fileName,"\")-1) d ##class(%File).CreateDirectoryChain(path) i '##class(%File).DirectoryExists(path) w !,"Path ",path," not created, skipping import" q $$$OK k ^%utility($J) s ^%utility($J,1)=routName s ^%utility($J,2)="" q $$rsave^%Wr("."_extension,fileName_":(""RWN"")","Source Control Export","Cache",0,0,1) }

/// Export a source code ClassMethod ExportSource(InternalName As %String, force = 0) As %Status {	// verify that source control is allowed on this server s sc=$G(^["USER"]MySources("sourcecontrol"),0) q:sc=0 $$$OK

//InternalName: [package]. [classe]. CLS s filename=..GetClassFileName(InternalName) q:filename="" $$$OK i 'force {		//quit if the file time is the same as the time we backed up for this source q:##class(%RoutineMgr).TS(InternalName)=$G(^["USER"]MPLSources(1,InternalName)) $$$OK }	s className=$P(InternalName,".",1,$L(InternalName,".")-1), ext=$P(InternalName,".",$L(InternalName,".")) i ext="CLS" {	//export in CDL for classes s sc=$SYSTEM.OBJ.ExportCDL(className,filename,"-d") }	else {		i ext="PRJ" {			//export project in XML s sc=$SYSTEM.OBJ.Export(InternalName,filename) }		else {			//export routines in RSA d ..ExportRoutine(InternalName,ext,filename) s sc=$$$OK }	}	i $$$ISOK(sc) {		w !,"Exported '",InternalName,"' to file '",filename,"'" s ^["USER"]MPLSources(0,InternalName)=##class(%File).GetFileDateModified(filename) s ^["USER"]MPLSources(1,InternalName)=##class(%RoutineMgr).TS(InternalName) }	else {		w "Error ExportSource: "_sc,! d DecomposeStatus^%apiOBJ(sc,,"d") }	q $$$OK }

Method ExternalName(InternalName As %String) As %String {	q ..GetClassFileName(InternalName) }

/// Returns the path and filename to export a source /// Argument: InternalName: name of the class as [package]. [classe]. [extension] /// Retour Path and filename as: /// Root path: from de ^["USER"]MySources("base") /// / [Directory from extension] cls, mac, int /// / [Directory from package] /// / [Class Name].txt ClassMethod GetClassFileName(InternalName As %String) As %String {	s name=$P(InternalName,".",1,$L(InternalName,".")-1),ext=$ZCONVERT($P(InternalName,".",$L(InternalName,".")),"l") q:name="" "" s fileExt=".txt" i ext="cls" s fileExt=".cdl" i ext="prj" s fileExt=".xml" s filename=ext_"\"_$TRANSLATE(name,".","\")_fileExt q $G(^["USER"]MySources("base"),"D:\")_filename }

Method GetStatus(InternalName As %String, ByRef IsInSourceControl As %Boolean, ByRef Editable As %Boolean, ByRef IsCheckedOut As %Boolean, ByRef UserCheckedOut As %String) As %Status {	s Editable=0,IsCheckedOut=0,UserCheckedOut="" s filename=..ExternalName(InternalName) s IsInSourceControl=(filename'=""&&(##class(%File).Exists(filename))) i 'IsInSourceControl s Editable=1 q $$$OK s UserCheckedOut=..Username s Editable=1 q ..ImportSource(InternalName) }

/// Import all classes and sources related to a project from files. Run it from terminal ClassMethod ImportProject(projectName) {	s class="" f { s class=$O(^oddPROJECT(projectName,"Items",class)) q:class="" s ext=$O(^oddPROJECT(projectName,"Items",class,"")) i ext'="MAC" s class=class_"."_ext w class," -> ",..ImportSource(class,1),! } }

/// Import a routine from a RSA formatted file ClassMethod ImportRoutine(fileName) As %Status {	i '##class(%File).Exists(fileName) w !,"File ",fileName," not found, skipping import" q $$$OK //args: file,mode "Cache", selection: 0=all, overwrite: 2:always, compile: 1=yes, syntax, backup, showstat, langmode, pasting: 0:=no message q $$rload^%Wr(fileName,"Cache",0,2,1,1,0,0) }

ClassMethod ImportSource(InternalName As %String, force = 0) As %Status {	i 'force {		//verify source control is enabled on this server s sc=$G(^["USER"]MPLSources("sourcecontrol"),0) q:sc=0 $$$OK }	//System file, continue i $E(InternalName,1)="%" q $$$OK

s filename=..GetClassFileName(InternalName) q:filename="" $$$OK

//file doen't exist, quit i '##class(%File).Exists(filename) w !,"File ",filename," not found, skipping import" q $$$OK i 'force {		s comp=..DTCompare(##class(%File).GetFileDateModified(filename),$G(^["USER"]MySources(0,InternalName))) i comp=0 {			w !,InternalName,"same version, skipping import" q $$$OK }		i comp<0 {			w !,InternalName,"file older, skipping import" q $$$OK }	}	//loading s ext=$P(InternalName,".",$L(InternalName,".")) i (ext="CLS")||(ext="PRJ") {	//import a class s sc=$SYSTEM.OBJ.Load(filename,"k-l-d") //-l-d }	else {		//import a routine s sc=..ImportRoutine(filename) }	i $$$ISOK(sc) {		w !,"Imported '",InternalName,"' from file '",filename,"'" s ^["USER"]MySources(0,InternalName)=##class(%File).GetFileDateModified(filename) s ^["USER"]MySources(1,InternalName)=##class(%RoutineMgr).TS(InternalName) }	else {		w "ImportSource Error: "_sc,! d DecomposeStatus^%apiOBJ(sc,.errors,"d") }	q sc }

Method IsInSourceControl(InternalName As %String) As %Boolean {	q 1 }

/// Export the file if modified after compilation Method OnAfterCompile(InternalName As %String) As %Status {	q ..ExportSource(InternalName) }

Method OnAfterSave(InternalName As %String, Object As %RegisteredObject) As %Status {	q ..ExportSource(InternalName) }

Method OnBeforeLoad(InternalName As %String) As %Status {	q ..ImportSource(InternalName) }

}