Skip to content

Commit 43c4b71

Browse files
committed
git-source-control settings are now a source-controlled file
1 parent 5778f7f commit 43c4b71

File tree

6 files changed

+319
-7
lines changed

6 files changed

+319
-7
lines changed

cls/SourceControl/Git/PackageManagerContext.cls

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Method InternalNameSet(InternalName As %String = "") As %Status
1818
set InternalName = ##class(SourceControl.Git.Utils).NormalizeInternalName(InternalName)
1919
if (InternalName '= i%InternalName) {
2020
set i%InternalName = InternalName
21+
if (InternalName = ##class(SourceControl.Git.Settings.Document).#INTERNALNAME) {
22+
// git source control settings document is never in an IPM context
23+
quit $$$OK
24+
}
2125
if $$$comClassDefined("%IPM.ExtensionBase.Utils") {
2226
set ..Package = ##class(%IPM.ExtensionBase.Utils).FindHomeModule(InternalName,,.resourceReference)
2327
} elseif $$$comClassDefined("%ZPM.PackageManager.Developer.Extension.Utils") {
@@ -51,4 +55,3 @@ Method Dump()
5155
}
5256

5357
}
54-

cls/SourceControl/Git/Settings.cls

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,31 @@ Method %OnNew() As %Status
6666
quit $$$OK
6767
}
6868

69+
Method SaveWithSourceControl() As %Status
70+
{
71+
set sc = $$$OK
72+
#dim %SourceControl As %Studio.SourceControl.Interface
73+
if '$isobject($get(%SourceControl)) {
74+
do ##class(%Studio.SourceControl.Interface).SourceControlCreate($username)
75+
}
76+
// Source control settings naively by only calling OnAfterSave hooks
77+
// Future enhancement: add full source control support with settings UI
78+
set internalName = ##class(SourceControl.Git.Settings.Document).#INTERNALNAME
79+
set settingsDoc = ##class(SourceControl.Git.Settings.Document).%New(internalName)
80+
if (##class(%Studio.SourceControl.Interface).SourceControlClassGet() = ##class(SourceControl.Git.Extension).%ClassName(1)) {
81+
if '##class(SourceControl.Git.Utils).IsInSourceControl(internalName) {
82+
set sc = ##class(SourceControl.Git.Utils).AddToSourceControl(internalName)
83+
$$$QuitOnError(sc)
84+
}
85+
}
86+
$$$QuitOnError(..%Save())
87+
$$$QuitOnError(settingsDoc.Load()) // reload doc to update timestamps
88+
if ($IsObject(%SourceControl)) {
89+
$$$QuitOnError(%SourceControl.OnAfterSave(internalName))
90+
}
91+
quit sc
92+
}
93+
6994
Method %Save() As %Status
7095
{
7196
set sc = ..%ValidateObject()
@@ -122,6 +147,45 @@ Method %Save() As %Status
122147
quit $$$OK
123148
}
124149

150+
Method ToDynamicObject() As %DynamicObject
151+
{
152+
// uses custom methods rather than %JSON.Adaptor because Mappings multidimensional
153+
// array is not supported
154+
set settingsJSON = {
155+
"pullEventClass": (..pullEventClass),
156+
"percentClassReplace": (..percentClassReplace),
157+
"Mappings": {}
158+
}
159+
do settingsJSON.%Set("decomposeProductions",..decomposeProductions,"boolean")
160+
set k1 = $order(..Mappings(""))
161+
while (k1 '= "") {
162+
do settingsJSON.Mappings.%Set(k1, {})
163+
set k2 = $order(..Mappings(k1,""))
164+
while (k2 '= "") {
165+
do settingsJSON.Mappings.%Get(k1).%Set(k2,..Mappings(k1,k2))
166+
set k2 = $order(..Mappings(k1, k2))
167+
}
168+
set k1 = $order(..Mappings(k1))
169+
}
170+
return settingsJSON
171+
}
172+
173+
Method ImportDynamicObject(pSettingsDyn As %DynamicObject)
174+
{
175+
set ..pullEventClass = pSettingsDyn.%Get("pullEventClass")
176+
set ..percentClassReplace = pSettingsDyn.%Get("percentClassReplace")
177+
set ..decomposeProductions = pSettingsDyn.%Get("decomposeProductions")
178+
kill ..Mappings
179+
set mappingsDyn = pSettingsDyn.%Get("Mappings", {})
180+
set i1 = mappingsDyn.%GetIterator()
181+
while i1.%GetNext(.k1, .v1) {
182+
set i2 = v1.%GetIterator()
183+
while i2.%GetNext(.k2, .v2) {
184+
set ..Mappings(k1, k2) = v2
185+
}
186+
}
187+
}
188+
125189
ClassMethod CreateNamespaceTempFolder() As %Status
126190
{
127191
set storage = ##class(SourceControl.Git.Utils).#Storage
@@ -158,7 +222,7 @@ ClassMethod Configure() As %Boolean [ CodeMode = objectgenerator ]
158222
do %code.WriteLine(" set inst."_property_" = value")
159223

160224
}
161-
do %code.WriteLine(" $$$ThrowOnError(inst.%Save())")
225+
do %code.WriteLine(" $$$ThrowOnError(inst.SaveWithSourceControl())")
162226
do %code.WriteLine(" write !,""Settings saved.""")
163227
do %code.WriteLine(" do inst.OnAfterConfigure()")
164228
do %code.WriteLine(" quit 1")
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/// Custom studio document type for git-source-control settings that are controlled by a file
2+
Class SourceControl.Git.Settings.Document Extends %Studio.AbstractDocument
3+
{
4+
5+
Projection RegisterExtension As %Projection.StudioDocument(DocumentExtension = "GSC", DocumentNew = 0, DocumentType = "json");
6+
7+
Parameter INTERNALNAME = "git-source-control.GSC";
8+
9+
Parameter EXTERNALNAME = "git-source-control.json";
10+
11+
/// Return 1 if the routine 'name' exists and 0 if it does not.
12+
ClassMethod Exists(name As %String) As %Boolean
13+
{
14+
return (name = ..#INTERNALNAME)
15+
}
16+
17+
/// Load the routine in Name into the stream Code
18+
Method Load() As %Status
19+
{
20+
set sc = $$$OK
21+
try {
22+
set stream = ..GetCurrentStream()
23+
$$$ThrowOnError(..Code.CopyFromAndSave(stream))
24+
$$$ThrowOnError(..Code.Rewind())
25+
do ..UpdateHash(stream)
26+
} catch err {
27+
set sc = err.AsStatus()
28+
}
29+
return sc
30+
}
31+
32+
Method GetCurrentStream() As %Stream.Object
33+
{
34+
set settings = ##class(SourceControl.Git.Settings).%New()
35+
set dynObj = settings.ToDynamicObject()
36+
set formatter = ##class(%JSON.Formatter).%New()
37+
$$$ThrowOnError(formatter.FormatToStream(dynObj, .stream))
38+
return stream
39+
}
40+
41+
/// Save the routine stored in Code
42+
Method Save() As %Status
43+
{
44+
set sc = $$$OK
45+
try {
46+
try {
47+
set settingsJSON = ##class(%DynamicObject).%FromJSON(..Code)
48+
} catch err {
49+
$$$ThrowStatus($$$ERROR($$$GeneralError, "Invalid JSON"))
50+
}
51+
set settings = ##class(SourceControl.Git.Settings).%New()
52+
do settings.ImportDynamicObject(settingsJSON)
53+
set sc = settings.%Save()
54+
quit:$$$ISERR(sc)
55+
} catch err {
56+
set sc = err.AsStatus()
57+
}
58+
return sc
59+
}
60+
61+
ClassMethod ListExecute(ByRef qHandle As %Binary, Directory As %String, Flat As %Boolean, System As %Boolean) As %Status
62+
{
63+
if $g(Directory)'="" {
64+
set qHandle=""
65+
quit $$$OK
66+
}
67+
set qHandle = $listbuild(1,"")
68+
quit $$$OK
69+
}
70+
71+
ClassMethod ListFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = ListExecute ]
72+
{
73+
set Row="", AtEnd=0
74+
set rownum = $lg(qHandle,1)
75+
if rownum'=1 {
76+
set AtEnd = 1
77+
} else {
78+
set Row = $listbuild(..#INTERNALNAME,$zts-5,0,"")
79+
set $list(qHandle,1) = 2
80+
}
81+
quit $$$OK
82+
}
83+
84+
ClassMethod ListClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = ListExecute ]
85+
{
86+
set qHandle = ""
87+
quit $$$OK
88+
}
89+
90+
Method UpdateHash(stream)
91+
{
92+
set stream = $Get(stream,..GetCurrentStream())
93+
set hash = $System.Encryption.SHA1HashStream(stream)
94+
if $get(@##class(SourceControl.Git.Utils).#Storage@("settings","Hash")) '= hash {
95+
set @##class(SourceControl.Git.Utils).#Storage@("settings","Hash") = hash
96+
set @##class(SourceControl.Git.Utils).#Storage@("settings","TS") = $zdatetime($h,3)
97+
}
98+
}
99+
100+
/// Return the timestamp of routine 'name' in %TimeStamp format. This is used to determine if the routine has
101+
/// been updated on the server and so needs reloading from Studio. So the format should be $zdatetime($horolog,3),
102+
/// or "" if the routine does not exist.
103+
ClassMethod TimeStamp(name As %String) As %TimeStamp
104+
{
105+
return $get(@##class(SourceControl.Git.Utils).#Storage@("settings","TS"), $zdatetime($h,3))
106+
}
107+
108+
}

cls/SourceControl/Git/Utils.cls

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,6 +2059,10 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
20592059
set relativePath = context.ResourceReference.Processor.OnItemRelativePath(InternalName)
20602060
quit relativePath
20612061
}
2062+
if ($zconvert(InternalName,"l") = $zconvert(##class(SourceControl.Git.Settings.Document).#INTERNALNAME,"l")) {
2063+
// git-source-control settings file will always live at repository root
2064+
quit ##class(SourceControl.Git.Settings.Document).#EXTERNALNAME
2065+
}
20622066

20632067
// For an abstract document, use the GetOther() method to try to determine its "real" class
20642068
if ..UserTypeCached(InternalName,.docclass,.doctype) {
@@ -2219,6 +2223,9 @@ ClassMethod NameToInternalName(Name, IgnorePercent = 1, IgnoreNonexistent = 1, V
22192223
} elseif ($zconvert(Name,"U")'[$zconvert($$$SourceRoot,"U")) {
22202224
set Name = ..TempFolder()_Name
22212225
}
2226+
if ($zconvert(Name,"l") = $zconvert(..TempFolder()_##class(SourceControl.Git.Settings.Document).#EXTERNALNAME,"l")) {
2227+
quit ##class(SourceControl.Git.Settings.Document).#INTERNALNAME
2228+
}
22222229
if (##class(%File).Exists(Name)) {
22232230
set InternalName = ##class(SourceControl.Git.File).ExternalNameToInternalName(Name)
22242231
if (InternalName '= "") && (context.IsInGitEnabledPackage) {

csp/gitprojectsettings.csp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ body {
7575
set webuiURL = ##class(SourceControl.Git.WebUIDriver).GetURLPrefix(%request, webuiURL)
7676

7777
set settings = ##class(SourceControl.Git.Settings).%New()
78-
if $Data(%request.Data("gitsettings",1)) {
78+
if (%request.Method="POST") && $Data(%request.Data("gitsettings",1)) {
7979
for param="gitUserName","gitUserEmail" {
8080
set $Property(settings,param) = $Get(%request.Data(param,1))
8181
}
@@ -120,7 +120,21 @@ body {
120120
set i = i+1
121121
}
122122
}
123-
do settings.%Save()
123+
set err = ""
124+
try {
125+
set buffer = ##class(SourceControl.Git.Util.Buffer).%New()
126+
do buffer.BeginCaptureOutput()
127+
$$$ThrowOnError(settings.SaveWithSourceControl())
128+
do buffer.EndCaptureOutput(.out)
129+
&html<<div class="alert alert-primary">
130+
<div>#(..EscapeHTML(out))#</div>
131+
<div>Settings saved.</div>
132+
</div>>
133+
} catch err {
134+
kill buffer
135+
do err.Log()
136+
&html<<div class="alert alert-danger">An error occurred and has been logged to the application error log.</div>>
137+
}
124138
}
125139
</server>
126140
<div class = 'container'>
@@ -546,9 +560,6 @@ body {
546560
</div>
547561

548562
</form>
549-
<csp:if condition='$D(%request.Data("gitsettings",1)) && (##class(SourceControl.Git.Utils).NeedSettings() = 0)'>
550-
<em>Settings saved. Click cross in the upper-right corner to close the settings window.</em>
551-
</csp:if>
552563
</div>
553564
<script src="js/polyfills.js"></script>
554565
<script src="js/jquery.min.js"></script>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
Class UnitTest.SourceControl.Git.Settings Extends %UnitTest.TestCase
2+
{
3+
4+
Property SourceControlGlobal [ MultiDimensional ];
5+
6+
Property InitialExtension As %String [ InitialExpression = {##class(%Studio.SourceControl.Interface).SourceControlClassGet()} ];
7+
8+
Method SampleSettingsJSON()
9+
{
10+
return {
11+
"pullEventClass": "pull event class",
12+
"percentClassReplace": "x",
13+
"decomposeProductions": true,
14+
"Mappings": {
15+
"TUV": {
16+
"*": "tuv/",
17+
"UnitTest": "tuv2/"
18+
},
19+
"XYZ": {
20+
"*": "xyz/"
21+
}
22+
}
23+
}
24+
}
25+
26+
Method TestJSONImportExport()
27+
{
28+
set settingsDynObj = ..SampleSettingsJSON()
29+
set settings = ##class(SourceControl.Git.Settings).%New()
30+
set settings.decomposeProductions = ""
31+
set settings.percentClassReplace = ""
32+
set settings.pullEventClass = ""
33+
do settings.ImportDynamicObject(settingsDynObj)
34+
do $$$AssertEquals(settings.decomposeProductions, 1)
35+
do $$$AssertEquals(settings.percentClassReplace, "x")
36+
do $$$AssertEquals(settings.pullEventClass, "pull event class")
37+
do $$$AssertEquals($get(settings.Mappings("TUV","*")),"tuv/")
38+
do $$$AssertEquals($get(settings.Mappings("TUV","UnitTest")),"tuv2/")
39+
do $$$AssertEquals($get(settings.Mappings("XYZ","*")),"xyz/")
40+
41+
$$$ThrowOnError(settings.%Save())
42+
set document = ##class(%RoutineMgr).%OpenId(##class(SourceControl.Git.Settings.Document).#INTERNALNAME)
43+
set settingsDynObj = ##class(%DynamicObject).%FromJSON(document.Code)
44+
do $$$AssertEquals(settingsDynObj.decomposeProductions, 1)
45+
do $$$AssertEquals(settingsDynObj.percentClassReplace, "x")
46+
do $$$AssertEquals(settingsDynObj.pullEventClass, "pull event class")
47+
do $$$AssertEquals(settingsDynObj.Mappings."TUV"."*","tuv/")
48+
do $$$AssertEquals(settingsDynObj.Mappings."TUV"."UnitTest","tuv2/")
49+
do $$$AssertEquals(settingsDynObj.Mappings."XYZ"."*","xyz/")
50+
}
51+
52+
Method TestSaveAndImportSettings()
53+
{
54+
// save settings
55+
set settings = ##class(SourceControl.Git.Settings).%New()
56+
set settings.Mappings("CLS","Foo") = "foo/"
57+
set settings.pullEventClass = "SourceControl.Git.PullEventHandler.Default"
58+
set settings.percentClassReplace = "_"
59+
set settings.decomposeProductions = 1
60+
$$$ThrowOnError(settings.SaveWithSourceControl())
61+
do $$$AssertStatusOK(##class(SourceControl.Git.Utils).AddToSourceControl("git-source-control.GSC"))
62+
// settings file should be in source control
63+
do $$$AssertTrue(##class(SourceControl.Git.Utils).IsInSourceControl("git-source-control.GSC"))
64+
do $$$AssertEquals($replace(##class(SourceControl.Git.Utils).ExternalName("git-source-control.GSC"),"\","/"),"git-source-control.json")
65+
// commit settings
66+
do $$$AssertStatusOK(##class(SourceControl.Git.Utils).Commit("git-source-control.GSC"))
67+
// settings should be in the global
68+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","mappings","CLS","Foo"),"foo/")
69+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","pullEventClass"),"SourceControl.Git.PullEventHandler.Default")
70+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","percentClassReplace"),"_")
71+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","decomposeProductions"),"1")
72+
// change and save settings
73+
set settings.Mappings("CLS","Foo") = "foo2/"
74+
set settings.pullEventClass = "SourceControl.Git.PullEventHandler.IncrementalLoad"
75+
set settings.percentClassReplace = "x"
76+
set settings.decomposeProductions = 0
77+
$$$ThrowOnError(settings.SaveWithSourceControl())
78+
// new setting should be in the global
79+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","mappings","CLS","Foo"),"foo2/")
80+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","pullEventClass"),"SourceControl.Git.PullEventHandler.IncrementalLoad")
81+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","percentClassReplace"),"x")
82+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","decomposeProductions"),"0")
83+
// revert change to settings
84+
do $$$AssertStatusOK(##class(SourceControl.Git.Utils).Revert("git-source-control.GSC"))
85+
// old setting should be in the global
86+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","mappings","CLS","Foo"),"foo/")
87+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","pullEventClass"),"SourceControl.Git.PullEventHandler.Default")
88+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","percentClassReplace"),"_")
89+
do $$$AssertEquals(^SYS("SourceControl","Git","settings","decomposeProductions"),"1")
90+
}
91+
92+
Method OnBeforeAllTests() As %Status
93+
{
94+
merge ..SourceControlGlobal = ^SYS("SourceControl")
95+
return $$$OK
96+
}
97+
98+
Method OnBeforeOneTest() As %Status
99+
{
100+
kill ^SYS("SourceControl")
101+
do ##class(%Studio.SourceControl.Interface).SourceControlClassSet("SourceControl.Git.Extension")
102+
set settings = ##class(SourceControl.Git.Settings).%New()
103+
set settings.namespaceTemp = ##class(%Library.File).TempFilename()_"dir"
104+
$$$ThrowOnError(settings.%Save())
105+
set workMgr = $System.WorkMgr.%New("")
106+
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Init"))
107+
$$$ThrowOnError(workMgr.WaitForComplete())
108+
quit $$$OK
109+
}
110+
111+
Method %OnClose() As %Status
112+
{
113+
do ##class(%Studio.SourceControl.Interface).SourceControlClassSet(..InitialExtension)
114+
kill ^SYS("SourceControl")
115+
merge ^SYS("SourceControl") = ..SourceControlGlobal
116+
quit $$$OK
117+
}
118+
119+
}

0 commit comments

Comments
 (0)