Skip to content

Commit a9e9b56

Browse files
authored
Merge branch 'main' into 654-push-option-doesnt-appear-on-local-branch-after-commit
2 parents 416fb96 + 2e6a1a2 commit a9e9b56

File tree

9 files changed

+84
-61
lines changed

9 files changed

+84
-61
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [2.8.1] - Unreleased
8+
## [2.9.0] - Unreleased
9+
10+
### Added
11+
- Menu option to export production to support migrating to production decomposition (#665)
912

1013
### Fixed
14+
- Fixed errors on production page when item settings need to be XML escaped (#667)
1115
- Fixed push button not appearing after commit (#654)
1216

1317
## [2.8.0] - 2024-12-06

cls/SourceControl/Git/Extension.cls

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ XData Menu
3030
<MenuItem Name="SwitchBranch" Save="101" />
3131
<MenuItem Separator="true"/>
3232
<MenuItem Name="ExportSystemDefaults" />
33+
<MenuItem Name="ExportProduction" />
3334
<MenuItem Name="Export" Save="101" />
3435
<MenuItem Name="ExportForce" Save="101" />
3536
<MenuItem Name="Import" />
@@ -148,6 +149,7 @@ Method LocalizeName(name As %String) As %String
148149
"Fetch":$$$Text("@Fetch@Fetch from remote"),
149150
"Pull":$$$Text("@Pull@Pull changes from remote branch"),
150151
"Status": $$$Text("@Status@Status"),
152+
"ExportProduction": $$$Text("@ExportProduction@Export Production"),
151153
:name)
152154
}
153155

@@ -163,7 +165,7 @@ Method OnSourceMenuItem(name As %String, ByRef Enabled As %String, ByRef Display
163165
}
164166
if ##class(SourceControl.Git.Utils).IsNamespaceInGit() {
165167

166-
if $listfind($listbuild("AddToSC", "RemoveFromSC", "Revert", "Commit"), name) {
168+
if $listfind($listbuild("AddToSC", "RemoveFromSC", "Revert", "Commit", "ExportProduction"), name) {
167169
quit ..OnSourceMenuContextItem(InternalName,name,.Enabled,.DisplayName)
168170
}
169171

@@ -243,6 +245,9 @@ Method OnSourceMenuContextItem(itemName As %String, menuItemName As %String, ByR
243245
if '(##class(SourceControl.Git.Change).IsUncommitted(##class(SourceControl.Git.Utils).FullExternalName(itemName))) || ($username '= userCheckedOut) {
244246
set Enabled = 0
245247
}
248+
} elseif menuItemName = "ExportProduction" {
249+
set itemNameNoExt = $piece(itemName,".",1,*-1)
250+
set Enabled = (##class(SourceControl.Git.Production).IsProductionClass(itemNameNoExt,"FullExternalName"))
246251
} elseif ##class(SourceControl.Git.Utils).IsInSourceControl(itemName) {
247252
set Enabled = $case(menuItemName, "AddToSC":-1,:1)
248253
} else {

cls/SourceControl/Git/Production.cls

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ ClassMethod DeleteProductionDefinitionShards(productionClass As %String, deleteM
6262
while rs.%Next(.sc) {
6363
quit:$$$ISERR(sc)
6464
set ptdFilename = rs.Data("Name")
65-
set sc = ##class(%Studio.SourceControl.Production).ParseExternalName(ptdFilename, .ptdInternalName)
65+
set sc = ..ParseExternalName(ptdFilename, .ptdInternalName)
6666
quit:$$$ISERR(sc)
6767
// TODO: Consider reverting delete if any ptd is not editable by current user
6868
set sc = $method(%SourceControl, deleteMethod, ptdInternalName)
@@ -371,26 +371,32 @@ ClassMethod IsProductionClass(className As %String, nameMethod As %String) As %B
371371
ClassMethod ParseExternalName(externalName, Output internalName = "", Output productionName = "") As %Status
372372
{
373373
set sc = $$$OK
374-
set extNameNormalized = $replace(externalName, "\", "/")
375-
set file = $piece(extNameNormalized, "/", *)
376-
if $extract(file,1,9) = "ProdStgs-" {
377-
set productionName = $replace($extract(file,10,*-4), "_", ".")
378-
set internalName = ..CreateInternalName(productionName,,,1)
379-
} else {
380-
if ##class(%File).Exists(externalName) {
381-
// Special case for Config Item Settings PTD, requires checking PTD CDATA for Item and Class name
382-
set deployDoc = ##class(EnsLib.EDI.XML.Document).%New(externalName)
383-
set exportNotesPTDText = $ZCVT(deployDoc.GetValueAt("/Export/Document[1]/1"),"I","XML")
384-
set exportNotesPTD = ##class(EnsLib.EDI.XML.Document).%New(exportNotesPTDText)
385-
set productionName = exportNotesPTD.GetValueAt("/Deployment/Creation/SourceProduction")
386-
set settingsPTDText = $zconvert(deployDoc.GetValueAt("/Export/Document[2]/1"),"I","XML")
387-
set settingsPTD = ##class(EnsLib.EDI.XML.Document).%New(settingsPTDText)
388-
set itemClass = settingsPTD.GetValueAt("/Item/@ClassName")
389-
set itemName = settingsPTD.GetValueAt("/Item/@Name")
390-
set internalName = ..CreateInternalName(productionName, itemName, itemClass, 0)
391-
} else {
392-
set sc = $$$ERROR($$$GeneralError, "Item settings PTD file " _ externalName _ " does not exist. Cannot parse external name.")
393-
}
374+
try {
375+
set extNameNormalized = $replace(externalName, "\", "/")
376+
set file = $piece(extNameNormalized, "/", *)
377+
if $extract(file,1,9) = "ProdStgs-" {
378+
set productionName = $replace($extract(file,10,*-4), "_", ".")
379+
set internalName = ..CreateInternalName(productionName,,,1)
380+
} else {
381+
if ##class(%File).Exists(externalName) {
382+
// Special case for Config Item Settings PTD, requires checking PTD CDATA for Item and Class name
383+
set deployDoc = ##class(EnsLib.EDI.XML.Document).%New(externalName)
384+
set exportNotesPTDText = $ZCVT(deployDoc.GetValueAt("/Export/Document[1]/1"),"I","XML")
385+
set exportNotesPTD = ##class(EnsLib.EDI.XML.Document).%New(exportNotesPTDText)
386+
set productionName = exportNotesPTD.GetValueAt("/Deployment/Creation/SourceProduction")
387+
set settingsPTDText = $zconvert(deployDoc.GetValueAt("/Export/Document[2]/1"),"I","XML")
388+
// unquote embedded CDATA close markers - see Ens.Util.ProjectTextDocument StreamToGbl method
389+
set settingsPTDText = $replace(settingsPTDText,"]*]>", "]]>")
390+
set settingsPTD = ##class(EnsLib.EDI.XML.Document).%New(settingsPTDText)
391+
set itemClass = settingsPTD.GetValueAt("/Item/@ClassName")
392+
set itemName = settingsPTD.GetValueAt("/Item/@Name")
393+
set internalName = ..CreateInternalName(productionName, itemName, itemClass, 0)
394+
} else {
395+
set sc = $$$ERROR($$$GeneralError, "Item settings PTD file " _ externalName _ " does not exist. Cannot parse external name.")
396+
}
397+
}
398+
} catch err {
399+
set sc = err.AsStatus()
394400
}
395401
return sc
396402
}

cls/SourceControl/Git/Util/Production.cls

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,40 @@ Include SourceControl.Git
44
Class SourceControl.Git.Util.Production
55
{
66

7+
ClassMethod BaselineProduction(productionName, settings As SourceControl.Git.Settings = {##class(SourceControl.Git.Settings).%New()})
8+
{
9+
set productionInternalName = productionName _ ".cls"
10+
if '##class(SourceControl.Git.Utils).FileIsMapped(productionInternalName) {
11+
if settings.decomposeProductions {
12+
write !, "Exporting production in decomposed format: " _ productionInternalName
13+
if ##class(SourceControl.Git.Utils).IsInSourceControl(productionInternalName) {
14+
set st = ##class(SourceControl.Git.Utils).RemoveFromSourceControl(productionInternalName)
15+
$$$ThrowOnError(st)
16+
}
17+
set st = ##class(SourceControl.Git.Production).ExportProductionDefinitionShards(productionName,"FullExternalName",.itemInternalNames)
18+
$$$ThrowOnError(st)
19+
set key = $order(itemInternalNames(""))
20+
while (key '= "") {
21+
set st = ##class(SourceControl.Git.Utils).AddToSourceControl(key)
22+
$$$ThrowOnError(st)
23+
set key = $order(itemInternalNames(key))
24+
}
25+
} else {
26+
write !, "Exporting production in class format: " _ productionInternalName
27+
set st = ##class(SourceControl.Git.Utils).AddToSourceControl(productionInternalName)
28+
$$$ThrowOnError(st)
29+
set key = $order(@##class(SourceControl.Git.Utils).#Storage@("items", ""))
30+
while (key '= "") {
31+
if $match(key,"^"_productionName_"\|\|.*\.(?i)ptd$") {
32+
set st = ##class(SourceControl.Git.Utils).RemoveFromSourceControl(key)
33+
$$$ThrowOnError(st)
34+
}
35+
set key = $order(@##class(SourceControl.Git.Utils).#Storage@("items", key))
36+
}
37+
}
38+
}
39+
}
40+
741
/// Baselines all productions in this namespace from single-file to decomposed or vice versa.
842
ClassMethod BaselineProductions()
943
{
@@ -13,37 +47,7 @@ ClassMethod BaselineProductions()
1347
throw:rs.%SQLCODE<0 ##class(%Exception.SQL).CreateFromSQLCODE(rs.%SQLCODE,rs.%Message)
1448
while rs.%Next(.sc) {
1549
$$$ThrowOnError(sc)
16-
set productionName = rs.Name
17-
set productionInternalName = productionName _ ".cls"
18-
if '##class(SourceControl.Git.Utils).FileIsMapped(productionInternalName) {
19-
if settings.decomposeProductions {
20-
write !, "Decomposing production: " _ productionInternalName
21-
if ##class(SourceControl.Git.Utils).IsInSourceControl(productionInternalName) {
22-
set st = ##class(SourceControl.Git.Utils).RemoveFromSourceControl(productionInternalName)
23-
$$$ThrowOnError(st)
24-
}
25-
set st = ##class(SourceControl.Git.Production).ExportProductionDefinitionShards(productionName,"FullExternalName",.itemInternalNames)
26-
$$$ThrowOnError(st)
27-
set key = $order(itemInternalNames(""))
28-
while (key '= "") {
29-
set st = ##class(SourceControl.Git.Utils).AddToSourceControl(key)
30-
$$$ThrowOnError(st)
31-
set key = $order(itemInternalNames(key))
32-
}
33-
} else {
34-
write !, "Recomposing production: " _ productionInternalName
35-
set st = ##class(SourceControl.Git.Utils).AddToSourceControl(productionInternalName)
36-
$$$ThrowOnError(st)
37-
set key = $order(@##class(SourceControl.Git.Utils).#Storage@("items", ""))
38-
while (key '= "") {
39-
if $match(key,"^"_productionName_"\|\|.*\.(?i)ptd$") {
40-
set st = ##class(SourceControl.Git.Utils).RemoveFromSourceControl(key)
41-
$$$ThrowOnError(st)
42-
}
43-
set key = $order(@##class(SourceControl.Git.Utils).#Storage@("items", key))
44-
}
45-
}
46-
}
50+
do ..BaselineProduction(rs.Name, settings)
4751
}
4852
}
4953

cls/SourceControl/Git/Utils.cls

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ ClassMethod UserAction(InternalName As %String, MenuName As %String, ByRef Targe
323323
do ..RunGitCommand("status", .errStream, .outStream)
324324
write !, !, "Git Status: "
325325
do ..PrintStreams(outStream, errStream)
326+
} elseif (menuItemName = "ExportProduction") {
327+
do ##class(SourceControl.Git.Util.Production).BaselineProduction($piece(InternalName,".",1,*-1))
326328
}
327329
quit ec
328330
}

docs/production-decomposition.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Production Decomposition is a feature of Embedded Git that allows multiple devel
44
## Enabling production decomposition
55
The feature may be enabled by checking the "Decompose Productions" box in the Git Settings page. For deployment of changes to other environments through git to work properly, the value of this setting must match on all namespaces connected to this repository. To assist, settings are automatically exported into a `embedded-git-config.json` file at the root of the repository that may be committed and imported into other environments.
66

7-
If there are existing productions in the namespace, they should be migrated to the new decomposed format by running `do ##class(SourceControl.Git.API).BaselineProductions()`. You may then use the Git Web UI to view, commit, and push the corresponding changes. This method should be run in a single namespace and then deployed to other namespaces through normal Embedded Git deployment mechanisms.
7+
If there are existing productions in the namespace, they should be migrated to the new decomposed format. Do this by opening the production and selecting the "Export Production" option in the source control menu. You may then use the Git Web UI to view, commit, and push the corresponding changes. This step should be done in a single namespace and then deployed to other namespaces through normal Embedded Git deployment mechanisms.
88

99
## Editing productions in the IDE
1010
There are a couple of limitations related to editing a production class directly in an integrated development environment (Studio or VS Code).

docs/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ The following is a testing plan that should be followed prior to release of a ne
3131
- Test migration of a production to decomposed format:
3232
- On the initial namespace, disable production decomposition. Create a new production and add a number of items. Sync and confirm it has been pushed to the remote repository.
3333
- On the second namespace, sync and confirm the new production has been created.
34-
- On the initial namespace, turn on production decomposition. From terminal, run `do ##class(SourceControl.Git.API).BaselineProductions()`. Confirm the Web UI includes changes for delete of the old production class and adds for all production items. Commit all items and push the branch.
34+
- On the initial namespace, turn on production decomposition. Open the production page and use the "Export Production" option in the source control menu. Confirm the Web UI includes changes for delete of the old production class and adds for all production items. Commit all items and push the branch.
3535
- On the second namespace, turn on production decomposition. Sync. The production should be reloaded with no changes.

module.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<Document name="git-source-control.ZPM">
44
<Module>
55
<Name>git-source-control</Name>
6-
<Version>2.8.0</Version>
6+
<Version>2.9.0</Version>
77
<Description>Server-side source control extension for use of Git on InterSystems platforms</Description>
88
<Keywords>git source control studio vscode</Keywords>
99
<Packaging>module</Packaging>

test/UnitTest/SourceControl/Git/ProductionDecomposition.cls

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ Method TestEditProduction()
8282
$$$ThrowOnError(%SourceControl.OnBeforeSave(..#ProductionName_".cls"))
8383
do ..ReplaceProductionDefinition("ProductionDefinition3")
8484
$$$ThrowOnError(%SourceControl.OnAfterSave(..#ProductionName_".cls"))
85-
do $$$AssertTrue(##class(SourceControl.Git.Utils).IsInSourceControl("UnitTest.SampleProduction||Settings-b|Ens.Activity.Operation.Local.PTD"))
85+
do $$$AssertTrue(##class(SourceControl.Git.Utils).IsInSourceControl("UnitTest.SampleProduction||Settings-b|EnsLib.SOAP.GenericOperation.PTD"))
8686
do ##class(SourceControl.Git.Utils).RunGitCommand("add",,,".")
8787
do ##class(SourceControl.Git.Utils).Commit("UnitTest.SampleProduction||Settings-a|Ens.Activity.Operation.Local.PTD")
88-
do ##class(SourceControl.Git.Utils).Commit("UnitTest.SampleProduction||Settings-b|Ens.Activity.Operation.Local.PTD")
88+
do ##class(SourceControl.Git.Utils).Commit("UnitTest.SampleProduction||Settings-b|EnsLib.SOAP.GenericOperation.PTD")
8989
$$$ThrowOnError(production.%Reload())
9090
do $$$AssertEquals(production.Items.Count(), 2)
9191
do $$$AssertEquals(production.Items.GetAt(1).Settings.GetAt(1).Name, "RecordStatsInterval")
@@ -135,7 +135,8 @@ XData ProductionDefinition2
135135
<Item Name="a" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
136136
<Setting Target="Host" Name="RecordStatsInterval">61</Setting>
137137
</Item>
138-
<Item Name="b" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
138+
<Item Name="b" Category="" ClassName="EnsLib.SOAP.GenericOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
139+
<Setting Target="Adapter" Name="HTTPServer"><![CDATA[<something that must be escaped>]]></Setting>
139140
</Item>
140141
</Production>
141142
}
@@ -147,7 +148,8 @@ XData ProductionDefinition3
147148
<Item Name="a" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
148149
<Setting Target="Host" Name="RecordStatsInterval">71</Setting>
149150
</Item>
150-
<Item Name="b" Category="" ClassName="Ens.Activity.Operation.Local" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
151+
<Item Name="b" Category="" ClassName="EnsLib.SOAP.GenericOperation" PoolSize="1" Enabled="false" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
152+
<Setting Target="Adapter" Name="HTTPServer"><![CDATA[<something that must be escaped>]]></Setting>
151153
</Item>
152154
</Production>
153155
}

0 commit comments

Comments
 (0)