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()