From 5ba40b18a2cc857b085da3a7ef9f7aa5a470f0ef Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Tue, 16 Dec 2025 13:52:14 -0500 Subject: [PATCH 1/2] enh: option to make generated files read-only --- CHANGELOG.md | 1 + cls/SourceControl/Git/Extension.cls | 3 ++ cls/SourceControl/Git/Settings.cls | 9 +++-- cls/SourceControl/Git/Utils.cls | 17 ++++++++- csp/gitprojectsettings.csp | 20 ++++++++++- test/UnitTest/SourceControl/Git/Extension.cls | 35 +++++++++++++++++++ test/UnitTest/SourceControl/Git/Settings.cls | 9 +++++ 7 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 test/UnitTest/SourceControl/Git/Extension.cls diff --git a/CHANGELOG.md b/CHANGELOG.md index 26d0a376..920c4d28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Import All has been added to public-facing API (#891) - Web UI workspace view now has an option to abort merge in progress (#895) +- New setting lets you treat generated classes as read-only in Studio/VS Code (#712) ## Fixed - Web UI workspace view labels changes as Merge Conflict if there are unmerged changes (#890) diff --git a/cls/SourceControl/Git/Extension.cls b/cls/SourceControl/Git/Extension.cls index 2922e403..6b7d7af3 100644 --- a/cls/SourceControl/Git/Extension.cls +++ b/cls/SourceControl/Git/Extension.cls @@ -474,6 +474,8 @@ Method IsReadOnly(InternalName As %String) As %Boolean set settings = ##class(SourceControl.Git.Settings).%New() quit (##class(SourceControl.Git.Utils).Locked() && '$get(^IRIS.Temp("sscProd",$job,"bypassLock"))) + || (settings.generatedFilesReadOnly + && ##class(SourceControl.Git.Utils).ItemIsGenerated(InternalName)) || (##class(SourceControl.Git.Utils).ItemIsProductionToDecompose($get(InternalName)) && 'settings.decomposeProdAllowIDE && '##class(SourceControl.Git.Production).IsEnsPortal()) @@ -588,3 +590,4 @@ Method CheckBusinessProcessesAndRules(InternalName As %String) As %Status } } + diff --git a/cls/SourceControl/Git/Settings.cls b/cls/SourceControl/Git/Settings.cls index a25579ac..cd2b7d9a 100644 --- a/cls/SourceControl/Git/Settings.cls +++ b/cls/SourceControl/Git/Settings.cls @@ -51,6 +51,9 @@ Property gitUserEmail As %String(MAXLEN = 255) [ InitialExpression = {##class(So /// Whether mapped items should be read-only, preventing them from being added to source control Property mappedItemsReadOnly As %Boolean [ InitialExpression = {##class(SourceControl.Git.Utils).MappedItemsReadOnly()} ]; +/// Whether generated files should be read-only within Studio/VS Code +Property generatedFilesReadOnly As %Boolean [ InitialExpression = {##class(SourceControl.Git.Utils).GeneratedFilesReadOnly()} ]; + /// Whether basic mode should be enabled for user ${username}, greatly simplifying the functionality of the package, requiring no knowledge of git Property basicMode As %String [ InitialExpression = {##class(SourceControl.Git.Utils).BasicMode()} ]; @@ -179,6 +182,7 @@ Method %Save() As %Status set @storage@("settings","percentClassReplace") = ..percentClassReplace set @storage@("settings","settingsUIReadOnly") = ..settingsUIReadOnly set @storage@("settings", "mappedItemsReadOnly") = ..mappedItemsReadOnly + set @storage@("settings", "generatedFilesReadOnly") = ..generatedFilesReadOnly set @storage@("settings", "defaultMergeBranch") = ..defaultMergeBranch set @storage@("settings", "compileOnImport") = ..compileOnImport set @storage@("settings", "warnInstanceWideUncommitted") = ..warnInstanceWideUncommitted @@ -224,6 +228,7 @@ Method ToDynamicObject() As %DynamicObject [ Internal ] set settingsJSON = { "pullEventClass": (..pullEventClass), "percentClassReplace": (..percentClassReplace), + "generatedFilesReadOnly": (..generatedFilesReadOnly), "Mappings": {} } do settingsJSON.%Set("decomposeProductions",..decomposeProductions,"boolean") @@ -248,6 +253,7 @@ Method ImportDynamicObject(pSettingsDyn As %DynamicObject) [ Internal ] set ..pullEventClass = pSettingsDyn.%Get("pullEventClass") set ..percentClassReplace = pSettingsDyn.%Get("percentClassReplace") set ..decomposeProductions = pSettingsDyn.%Get("decomposeProductions") + set ..generatedFilesReadOnly = pSettingsDyn.%Get("generatedFilesReadOnly") kill ..Mappings set mappingsDyn = pSettingsDyn.%Get("Mappings", {}) set i1 = mappingsDyn.%GetIterator() @@ -546,7 +552,7 @@ Method RetrieveDefaults() As %Boolean [ Internal ] Method SaveDefaults() As %Boolean { set defaults = {} - set items = $lb("gitBinPath", "pullEventClass", "percentClassReplace", "environmentName", "systemBasicMode", "defaultMergeBranch", "mappedItemsReadOnly", "compileOnImport") + set items = $lb("gitBinPath", "pullEventClass", "percentClassReplace", "environmentName", "systemBasicMode", "defaultMergeBranch", "mappedItemsReadOnly", "generatedFilesReadOnly", "compileOnImport") for i=1:1:$LISTLENGTH(items) { set property = $listget(items,i) do defaults.%Set(property, $property($this, property)) @@ -555,4 +561,3 @@ Method SaveDefaults() As %Boolean } } - diff --git a/cls/SourceControl/Git/Utils.cls b/cls/SourceControl/Git/Utils.cls index 76c2f3f0..91d66b2d 100644 --- a/cls/SourceControl/Git/Utils.cls +++ b/cls/SourceControl/Git/Utils.cls @@ -155,6 +155,11 @@ ClassMethod MappedItemsReadOnly() As %Boolean quit $get(@..#Storage@("settings", "mappedItemsReadOnly"), 1) } +ClassMethod GeneratedFilesReadOnly() As %Boolean +{ + quit $get(@..#Storage@("settings", "generatedFilesReadOnly"), 0) +} + ClassMethod GitUserName() As %String { quit $get(@..#Storage@("settings","user",$username,"gitUserName"),$username) @@ -1103,6 +1108,17 @@ ClassMethod Type(InternalName As %String) As %String quit type } +ClassMethod ItemIsGenerated(InternalName As %String) As %Boolean +{ + set normalizedName = InternalName + set normalizedName = ..NormalizeInternalName(.normalizedName) + quit:normalizedName="" 0 + quit:..Type(.normalizedName)'="cls" 0 + set className = ..NameWithoutExtension(normalizedName) + quit:className="" 0 + quit ($$$defClassKeyGet(className,$$$cCLASSgeneratedby)'="") +} + ClassMethod NameWithoutExtension(InternalName As %String) As %String [ CodeMode = expression ] { $Piece(InternalName, ".", 1, $Length(InternalName,".")-1) @@ -3341,4 +3357,3 @@ ClassMethod IsSchemaStandard(pName As %String = "") As %Boolean [ Internal ] } } - diff --git a/csp/gitprojectsettings.csp b/csp/gitprojectsettings.csp index e070adc8..40296ada 100644 --- a/csp/gitprojectsettings.csp +++ b/csp/gitprojectsettings.csp @@ -130,6 +130,12 @@ body { set settings.mappedItemsReadOnly = 0 } + if ($Get(%request.Data("generatedFilesReadOnly", 1)) = 1) { + set settings.generatedFilesReadOnly = 1 + } else { + set settings.generatedFilesReadOnly = 0 + } + set settings.compileOnImport = ($Get(%request.Data("compileOnImport", 1)) = 1) set settings.decomposeProductions = ($Get(%request.Data("decomposeProductions", 1)) = 1) @@ -502,6 +508,18 @@ body { + +
+ +
+
+ + +
+
+ +
@@ -986,4 +1004,4 @@ $('.mapping-input-group').children('.voca').each(function(){ // ignore; may occur on platform versions without the above properties } quit 1 - \ No newline at end of file + diff --git a/test/UnitTest/SourceControl/Git/Extension.cls b/test/UnitTest/SourceControl/Git/Extension.cls new file mode 100644 index 00000000..048cd27a --- /dev/null +++ b/test/UnitTest/SourceControl/Git/Extension.cls @@ -0,0 +1,35 @@ +Class UnitTest.SourceControl.Git.Extension Extends %UnitTest.TestCase +{ + +Method TestGeneratedFilesReadOnlyOption() +{ + set sc = $$$OK + set classCreated = 0 + try { + do $System.OBJ.Delete("UnitTest.SourceControl.Git.GeneratedReadOnly") + set classDef = ##class(%Dictionary.ClassDefinition).%New() + set classDef.Name = "UnitTest.SourceControl.Git.GeneratedReadOnly" + set classDef.GeneratedBy = "UnitTest.SourceControl.Git.GeneratedBy" + $$$ThrowOnError(classDef.%Save()) + $$$ThrowOnError($System.OBJ.Compile("UnitTest.SourceControl.Git.GeneratedReadOnly", "ck")) + set classCreated = 1 + + set settings = ##class(SourceControl.Git.Settings).%New() + set settings.generatedFilesReadOnly = 0 + $$$ThrowOnError(settings.%Save()) + set extension = ##class(SourceControl.Git.Extension).%New("") + do $$$AssertNotTrue(extension.IsReadOnly("UnitTest.SourceControl.Git.GeneratedReadOnly.cls")) + + set settings.generatedFilesReadOnly = 1 + $$$ThrowOnError(settings.%Save()) + do $$$AssertTrue(extension.IsReadOnly("UnitTest.SourceControl.Git.GeneratedReadOnly.cls")) + } catch err { + set sc = err.AsStatus() + } + if classCreated { + do $System.OBJ.Delete("UnitTest.SourceControl.Git.GeneratedReadOnly") + } + $$$ThrowOnError(sc) +} + +} \ No newline at end of file diff --git a/test/UnitTest/SourceControl/Git/Settings.cls b/test/UnitTest/SourceControl/Git/Settings.cls index d2ebb34a..d9e47b4f 100644 --- a/test/UnitTest/SourceControl/Git/Settings.cls +++ b/test/UnitTest/SourceControl/Git/Settings.cls @@ -11,6 +11,7 @@ Method SampleSettingsJSON() "pullEventClass": "pull event class", "percentClassReplace": "x", "decomposeProductions": true, + "generatedFilesReadOnly": true, "Mappings": { "TUV": { "*": { @@ -37,10 +38,12 @@ Method TestJSONImportExport() set settings.decomposeProductions = "" set settings.percentClassReplace = "" set settings.pullEventClass = "" + set settings.generatedFilesReadOnly = 0 do settings.ImportDynamicObject(settingsDynObj) do $$$AssertEquals(settings.decomposeProductions, 1) do $$$AssertEquals(settings.percentClassReplace, "x") do $$$AssertEquals(settings.pullEventClass, "pull event class") + do $$$AssertEquals(settings.generatedFilesReadOnly, 1) do $$$AssertEquals($get(settings.Mappings("TUV","*")),"tuv/") do $$$AssertEquals($get(settings.Mappings("TUV","UnitTest")),"tuv2/") do $$$AssertTrue($get(settings.Mappings("TUV","UnitTest","NoFolders"))) @@ -52,6 +55,7 @@ Method TestJSONImportExport() do $$$AssertEquals(settingsDynObj.decomposeProductions, 1) do $$$AssertEquals(settingsDynObj.percentClassReplace, "x") do $$$AssertEquals(settingsDynObj.pullEventClass, "pull event class") + do $$$AssertEquals(settingsDynObj.generatedFilesReadOnly, 1) do $$$AssertEquals(settingsDynObj.Mappings."TUV"."*".directory,"tuv/") do $$$AssertEquals(settingsDynObj.Mappings."TUV"."UnitTest".directory,"tuv2/") do $$$AssertTrue(settingsDynObj.Mappings."TUV"."UnitTest".noFolders) @@ -66,6 +70,7 @@ Method TestSaveAndImportSettings() set settings.pullEventClass = "SourceControl.Git.PullEventHandler.Default" set settings.percentClassReplace = "_" set settings.decomposeProductions = 1 + set settings.generatedFilesReadOnly = 1 $$$ThrowOnError(settings.SaveWithSourceControl()) do $$$AssertStatusOK(##class(SourceControl.Git.Utils).AddToSourceControl("embedded-git-config.GSC")) // settings file should be in source control @@ -78,17 +83,20 @@ Method TestSaveAndImportSettings() do $$$AssertEquals(^SYS("SourceControl","Git","settings","pullEventClass"),"SourceControl.Git.PullEventHandler.Default") do $$$AssertEquals(^SYS("SourceControl","Git","settings","percentClassReplace"),"_") do $$$AssertEquals(^SYS("SourceControl","Git","settings","decomposeProductions"),"1") + do $$$AssertEquals(^SYS("SourceControl","Git","settings","generatedFilesReadOnly"),"1") // change and save settings set settings.Mappings("CLS","Foo") = "foo2/" set settings.pullEventClass = "SourceControl.Git.PullEventHandler.IncrementalLoad" set settings.percentClassReplace = "x" set settings.decomposeProductions = 0 + set settings.generatedFilesReadOnly = 0 $$$ThrowOnError(settings.SaveWithSourceControl()) // new setting should be in the global do $$$AssertEquals(^SYS("SourceControl","Git","settings","mappings","CLS","Foo"),"foo2/") do $$$AssertEquals(^SYS("SourceControl","Git","settings","pullEventClass"),"SourceControl.Git.PullEventHandler.IncrementalLoad") do $$$AssertEquals(^SYS("SourceControl","Git","settings","percentClassReplace"),"x") do $$$AssertEquals(^SYS("SourceControl","Git","settings","decomposeProductions"),"0") + do $$$AssertEquals(^SYS("SourceControl","Git","settings","generatedFilesReadOnly"),"0") // revert change to settings do $$$AssertStatusOK(##class(SourceControl.Git.Utils).Revert("embedded-git-config.GSC")) // old setting should be in the global @@ -96,6 +104,7 @@ Method TestSaveAndImportSettings() do $$$AssertEquals(^SYS("SourceControl","Git","settings","pullEventClass"),"SourceControl.Git.PullEventHandler.Default") do $$$AssertEquals(^SYS("SourceControl","Git","settings","percentClassReplace"),"_") do $$$AssertEquals(^SYS("SourceControl","Git","settings","decomposeProductions"),"1") + do $$$AssertEquals(^SYS("SourceControl","Git","settings","generatedFilesReadOnly"),"1") } Method OnBeforeAllTests() As %Status From 8752edd9c9c03cb2e451a44332ca299530684010 Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Tue, 16 Dec 2025 14:28:35 -0500 Subject: [PATCH 2/2] enh: check for generated files handles routines etc not just classes --- cls/SourceControl/Git/Utils.cls | 8 +------- test/UnitTest/SourceControl/Git/Extension.cls | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/cls/SourceControl/Git/Utils.cls b/cls/SourceControl/Git/Utils.cls index 91d66b2d..a78d922d 100644 --- a/cls/SourceControl/Git/Utils.cls +++ b/cls/SourceControl/Git/Utils.cls @@ -1110,13 +1110,7 @@ ClassMethod Type(InternalName As %String) As %String ClassMethod ItemIsGenerated(InternalName As %String) As %Boolean { - set normalizedName = InternalName - set normalizedName = ..NormalizeInternalName(.normalizedName) - quit:normalizedName="" 0 - quit:..Type(.normalizedName)'="cls" 0 - set className = ..NameWithoutExtension(normalizedName) - quit:className="" 0 - quit ($$$defClassKeyGet(className,$$$cCLASSgeneratedby)'="") + return ##class(%RoutineMgr).IsGenerated(..NormalizeInternalName(InternalName)) } ClassMethod NameWithoutExtension(InternalName As %String) As %String [ CodeMode = expression ] diff --git a/test/UnitTest/SourceControl/Git/Extension.cls b/test/UnitTest/SourceControl/Git/Extension.cls index 048cd27a..971f9528 100644 --- a/test/UnitTest/SourceControl/Git/Extension.cls +++ b/test/UnitTest/SourceControl/Git/Extension.cls @@ -1,4 +1,4 @@ -Class UnitTest.SourceControl.Git.Extension Extends %UnitTest.TestCase +Class UnitTest.SourceControl.Git.Extension Extends UnitTest.SourceControl.Git.AbstractTest { Method TestGeneratedFilesReadOnlyOption()