Source Control

Cache Studio can communicate with source control systems via special class with Studio hooks.

Here is an example class which is used in Dimas ltd. to integrate with Subversion. It is based on Intersystems ClearCase integration class. This is definetely not an example of good Cache code, but it just works.

This hook just export new version of class and routine into filesystem after each Save and check for a new version before each Open. All checkouts, commits and other work with Subversion is done via external tools (we use console svn and GUI SmartSVN Foundation).

There is also "Build version" routine which just delete all classes and routines from namespace and then load them from disk.

Subversion path is hard-coded into "c:\VSSDB\"_$ZN

All code here have no license, use it in any way.

There is no upload function on this wiki, so get it in plain text.

p5.SourceControl.xml

    Организация работы с MS VSSDB Dimas 2003, logist, shu 031018 - cache 5.04 - добавлены параметры в методы 1 %Studio.SourceControl.Base 60173,55314.083424 59078,62120 0

 <![CDATA[ The username of this source control user. This just calls the Login to perform an initialisation. Note that you must call SourceControlCreate to create an instance of the source control class, do not call %New directly. Perform any login step here.]]></Description> <FormalSpec>Name:%String,Password:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Quit $$$OK ]]></Implementation> </Method>

<Method name="OnBeforeLoad">  Import the file from the external format into Cache. This is called before the actual load of data to give the chance to load the item from an external format.</Description> <FormalSpec>InternalName:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Set filename=..ExternalName(InternalName) If filename="" Quit $$$OK If ($zu(140,1,filename)<0) { Write:'$get(^ClearCase("nodisplay")) !,"File ",filename," not found, skipping import" Quit $$$OK } If ($zu(140,2,filename)=$get(^ClearCase(0,"FileTimeStamp",InternalName))) { Write:'$get(^ClearCase("nodisplay")) !,"File ",filename," is up to date, skipping import" Quit $$$OK } Set name=$Piece(InternalName,".",1,$Length(InternalName,".")-1) Set ext=$ZConvert($Piece(InternalName,".",$Length(InternalName,".")),"U") If ( (ext="CLS") || (ext="MAC") || (ext="PRJ") ) { Set sc=$$Import^%occSAX(filename,"-d-l") } Else { Set sc=$$Import^%apiRTN(filename,"all,replace,nocompile,lock=0",,,,"-d") } If ($$$ISOK(sc)) { Write:'$get(^ClearCase("nodisplay")) !,"Imported '",InternalName,"' from file '",filename,"'" Set ^ClearCase(0,"FileTimeStamp",InternalName)=$zu(140,2,filename) } Else { do DecomposeStatus^%apiOBJ(sc,.errors) Do:'$get(^ClearCase("nodisplay"))&($g(errors(1,"code"))'=6301) DecomposeStatus^%apiOBJ(sc,.errors,"d") } Quit sc ]]></Implementation> </Method>
 * 1) ; If no file then skip the import

<Method name="OnAfterSave">  Export the routine/class/csp file to the external format. This is called after the item has been saved to the database.</Description> <FormalSpec>InternalName:%String,Object:%RegisteredObject=$$$NULLOREF</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Set filename=..ExternalName(InternalName) If filename="" Quit $$$OK Set name=$Piece(InternalName,".",1,$Length(InternalName,".")-1) Set ext=$ZConvert($Piece(InternalName,".",$Length(InternalName,".")),"U") If ( $zu(140,1,filename)<0 ) { Do ..MakeDir(filename) } If ( (ext="CLS") ) { Set sc=$$Export^%occSAX(filename,"-d",InternalName) } If $$$ISOK(sc) { Write:'$get(^ClearCase("nodisplay")) !,"Exported '",InternalName,"' to file '",filename,"'" Set ^ClearCase(0,"FileTimeStamp",InternalName)=$zu(140,2,filename) } Else { do DecomposeStatus^%apiOBJ(sc,.errors) Do:'$get(^ClearCase("nodisplay"))&($g(errors(1,"code"))'=6301) DecomposeStatus^%apiOBJ(sc,.errors,"d") } Quit $$$OK ]]></Implementation> </Method>

<Method name="OnBeforeSave">  Called before the item is saved to the Cache database it is passed a reference to the current temporary storage of this item so that it can be modified before the save completes. If you quit with an error value then it will abort the save.</Description> <FormalSpec>InternalName:%String,Location:%String,Object:%RegisteredObject=$$$NULLOREF</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Quit $$$OK ]]></Implementation> </Method>

<Method name="OnBeforeCompile">  Called before the compile of the item is done.</Description> <FormalSpec>InternalName:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Quit $$$OK ]]></Implementation> </Method>

<Method name="OnAfterCompile">  Called after the compile of the item is done.</Description> <FormalSpec>InternalName:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Set filename=..ExternalName(InternalName) If filename="" Quit $$$OK Set name=$Piece(InternalName,".",1,$Length(InternalName,".")-1) Set ext=$ZConvert($Piece(InternalName,".",$Length(InternalName,".")),"U") If ( (ext'="CLS") ) { Quit $$$OK } //# // don't export ...admin.Build... If ( $zu(140,1,filename)<0 ) { Do ..MakeDir(filename) } If ( (ext="CLS") ) { Set sc=$$Export^%occSAX(filename,"-d",InternalName) }  If $$$ISOK(sc) { Write:'$get(^ClearCase("nodisplay")) !,"Exported '",InternalName,"' to file '",filename,"'" Set ^ClearCase(0,"FileTimeStamp",InternalName)=$zu(140,2,filename) } Else { do DecomposeStatus^%apiOBJ(sc,.errors) Do:'$get(^ClearCase("nodisplay"))&($g(errors(1,"code"))'=6301) DecomposeStatus^%apiOBJ(sc,.errors,"d") }  Quit $$$OK ]]></Implementation> </Method>

<Method name="CheckIn">  Check this routine/class/csp file into source control.</Description> <FormalSpec>InternalName:%String,Description:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Quit $$$ERROR(10000,"Работать с контролем версий можно только из VSSDB") ]]></Implementation> </Method>

<Method name="CheckOut">  Check this routine/class/csp file out of source control.</Description> <FormalSpec>InternalName:%String,Description:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Quit $$$ERROR(10000,"Работать с контролем версий можно только из VSSDB") ]]></Implementation> </Method>

<Method name="GetLatest">  Get the latest version of this file from source control.</Description> <FormalSpec>InternalName:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Quit $$$ERROR(10000,"Работать с контролем версий можно только из VSSDB") ]]></Implementation> </Method>

<Method name="GetStatus">  Return information about this entity.</Description> <FormalSpec><![CDATA[InternalName:%String,&IsInSourceControl:%Boolean,&Editable:%Boolean,&IsCheckedOut:%Boolean,&UserCheckedOut:%String]]></FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[ Set Editable=1,IsCheckedOut=0,UserCheckedOut="" Set filename=..ExternalName(InternalName) Set IsInSourceControl=(filename'="") If ( filename="" ) { Quit $$$OK } //# If ($zu(140,1,filename)<0) { Quit $$$OK } //# Open filename:"W":0 Set Editable=$T Close filename If ('Editable) { Write:'$get(^ClearCase("nodisplay")) !,InternalName," locked in SourceControl" }  Quit $$$OK ]]></Implementation> </Method>

<Method name="AddToSourceControl">  Add this routine/class/csp file to source control.</Description> <FormalSpec>InternalName:%String,Description:%String</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[  Quit $$$OK ]]></Implementation> </Method>

<Method name="RemoveFromSourceControl">  Remove this routine/class/csp file from source control.</Description> <FormalSpec>InternalName:%String,Description:%String=""</FormalSpec> <ReturnType>%Status</ReturnType> <Implementation><![CDATA[  Quit $$$OK ]]></Implementation> </Method>

<Method name="IsInSourceControl">  Returns true if this item is in source control and false otherwise.</Description> <FormalSpec>InternalName:%String</FormalSpec> <ReturnType>%Boolean</ReturnType> <Implementation><![CDATA[  Quit ..ExternalName(InternalName)'="" ]]></Implementation> </Method>

<Method name="ExternalName">  Convert the internal name, e.g. TEST.MAC to an external name that is used to export the routine/class/csp item. This is often a filename to write the file out to.</Description> <FormalSpec>InternalName:%String</FormalSpec> <ReturnType>%String</ReturnType> <Implementation><![CDATA[ Set nam=$p(InternalName,".",1,$l(InternalName,".")-1), ext=$p(InternalName,".",$l(InternalName,".")), ext=$zcvt(ext,"u") If ( nam="" ) { Quit "" } //# If ( ext="CLS" ) { Quit "c:\VSSDB\"_$znspace_"\Classes\"_$translate(nam,".","\")_".xml" }   Quit "" ]]></Implementation> </Method>

<Method name="MakeDir"> <FormalSpec>filename:%String</FormalSpec> <Private>1</Private> <Implementation><![CDATA[ Set filename=$piece(filename,"\",1,$length(filename,"\")-1) If ( $zu(12,filename,2)="" ) { For i=1:1:$length(filename,"\") Do $ZU(140,9,$piece(filename,"\",1,i)) } ]]></Implementation> </Method> </Class> <Checksum value="3580265751"/> </Export>

BuildVersion.mac

<?xml version="1.0" encoding="UTF-8"?> <Export generator="Cache" version="20" zv="Cache for Windows (Intel) 5.2.3 (Build 710)" ts="2007-06-28 18:26:20"> <Routine name="BuildVersion" type="MAC" languagemode="0" timestamp="60809,66369"><![CDATA[ BuildVersion(ver="") //загрузка и компиляция классов для ДИМАС // do ^BuildVersion new DirMac, DirClasses, DirCsr, DirCsp, time, build set DirMac="c:\vssdb\dimas\mac" //path to .MAC set DirClasses="c:\vssdb\dimas\classes" //path to classes set DirCsr="\csp\dimas" //path to csr set DirCsp="\csp\dimas" //path to CSP set time = $piece($horolog,",",2) set File=##class(%FileCharacterStream).%New set File.Filename=DirClasses_"\.svn\entries" while 'File.AtEnd { set line=File.ReadLine if $find(line,"revision")'=0 set build=$piece(line,"""",2) } write !,"delete classes:",! do $system.OBJ.DeletePackage("*") write !,"delete routine:",!

set rset = ##class(%ResultSet).%New set rset.ClassName = "%Library.Routine" set rset.QueryName = "RoutineList" for rext = "*.MAC","*.INT","*.OBJ" { do rset.Execute(rext,,1) while (rset.Next(.sc)) { if ($SYSTEM.Status.IsOK(sc)) { set rname = rset.Data("Name") if ($t(+0)'=$extract(rname,1,$length(rname)-4)) { write rset.Data("Name"),! do ##class(%Library.Routine).Delete(rname) }	   }	 } } write !,"Load MAC "_DirMac_":",! set sc = $system.OBJ.LoadDir(DirMac,"c","",1) write !,"compile routines:",! for rext = "*.INT","*.MAC" { do rset.Execute(rext) while (rset.Next(.sc)) { if ($SYSTEM.Status.IsOK(sc)) { set rname = rset.Data("Name") if ($t(+0)'=$extract(rname,1,$length(rname)-4)) { write rset.Data("Name"),! do $system.OBJ.CompileList(rname,"c-d") }	   }	 } } kill rset write !,"Load class "_DirClasses_":",! write $system.OBJ.LoadDir(DirClasses,"","",1),! write !,"Compile all:",! write $system.OBJ.CompileAll,! write !,"Load rules: ",! do $SYSTEM.CSP.DeleteAllRules do $SYSTEM.CSP.LoadRuleDir(DirCsr) write !,"Clear cached queries via $system.SQL.Purge(0)",! Do $system.SQL.Purge(0) ; Purge all Cached Queries kill ^mcq,^mqh // for Cache 5.2 set time=$piece($horolog,",",2) - time write !,"Time: ",time\60," min. ",time#60," sec.",! quit //# ]]></Routine> </Export>