Skip to content

Commit 4c5dfee

Browse files
authored
Merge pull request #469 from intersystems/production-change-control
Production decomposition
2 parents 6880f7f + e8e7209 commit 4c5dfee

File tree

18 files changed

+1470
-65
lines changed

18 files changed

+1470
-65
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ 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.0] - Unreleased
9+
10+
### Added
11+
- Production Decomposition mode allows controlling interoperability productions as individual files for each host (#469)
12+
813
## [2.7.1] - 2024-11-13
914

1015
### Fixed

cls/SourceControl/Git/API.cls

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,11 @@ ClassMethod MapEverywhere()
7575
Quit ##class(SourceControl.Git.Installer).MapEverywhere()
7676
}
7777

78+
/// Run to baseline all interoperability productions in the namespace to source control.
79+
/// This should be done after changing the value of the "decompose productions" setting.
80+
ClassMethod BaselineProductions()
81+
{
82+
do ##class(SourceControl.Git.Util.Production).BaselineProductions()
83+
}
84+
7885
}

cls/SourceControl/Git/Extension.cls

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -327,22 +327,44 @@ Method OnBeforeTimestamp(InternalName As %String)
327327
Method OnAfterSave(InternalName As %String, Object As %RegisteredObject = {$$$NULLOREF}) As %Status
328328
{
329329
set sc = $$$OK
330+
quit:$get(%gscSkipSaveHooks) sc
330331
try {
331332
set InternalName = ##class(SourceControl.Git.Utils).NormalizeInternalName(.InternalName,.fromWebApp,.fullExternalName)
332333
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(InternalName)
333-
if ##class(SourceControl.Git.Utils).IsNamespaceInGit() && ..IsInSourceControl(InternalName) {
334-
if fromWebApp {
335-
if fullExternalName = ##class(SourceControl.Git.Utils).FullExternalName(InternalName) {
336-
// Reimport item into database
337-
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportItem(InternalName,,1,1))
334+
if ##class(SourceControl.Git.Utils).IsNamespaceInGit() {
335+
// If this is a production class and production decomposition is enabled, call recursively on all modified production items.
336+
if ##class(SourceControl.Git.Utils).ItemIsProductionToDecompose(InternalName) {
337+
do ##class(SourceControl.Git.Production).GetModifiedItemsAfterSave(InternalName, .productionItems)
338+
set key = $order(productionItems(""))
339+
while (key '= "") {
340+
if productionItems(key) = "D" {
341+
set itemFilename = ..FullExternalName(key)
342+
if ##class(SourceControl.Git.Utils).IsInSourceControl(key) && ##class(%File).Exists(itemFilename) {
343+
$$$ThrowOnError(##class(SourceControl.Git.Change).AddDeletedToUncommitted(itemFilename, key))
344+
$$$ThrowOnError(##class(SourceControl.Git.Utils).DeleteExternalFile(key))
345+
}
346+
} elseif '..IsInSourceControl(key) {
347+
$$$ThrowOnError(##class(SourceControl.Git.Utils).AddToSourceControl(key))
348+
} else {
349+
$$$ThrowOnError(..OnAfterSave(key))
350+
}
351+
set key = $order(productionItems(key))
338352
}
339-
} else {
340-
set filename = ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
341-
$$$ThrowOnError(##class(SourceControl.Git.Utils).RemoveRoutineTSH(InternalName))
342-
set forceExport = (InternalName'= "") && ($data(..Modified(InternalName)))
343-
$$$ThrowOnError(##class(SourceControl.Git.Utils).ExportItem(InternalName,,forceExport))
344-
if '##class(SourceControl.Git.Change).IsUncommitted(filename) {
345-
$$$ThrowOnError(##class(SourceControl.Git.Change).SetUncommitted(filename, "edit", InternalName, $username, "", 1, "", "", 0))
353+
}
354+
if ..IsInSourceControl(InternalName) {
355+
if fromWebApp {
356+
if fullExternalName = ##class(SourceControl.Git.Utils).FullExternalName(InternalName) {
357+
// Reimport item into database
358+
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportItem(InternalName,,1,1))
359+
}
360+
} else {
361+
set filename = ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
362+
$$$ThrowOnError(##class(SourceControl.Git.Utils).RemoveRoutineTSH(InternalName))
363+
set forceExport = (InternalName'= "") && ($data(..Modified(InternalName)))
364+
$$$ThrowOnError(##class(SourceControl.Git.Utils).ExportItem(InternalName,,forceExport))
365+
if '##class(SourceControl.Git.Change).IsUncommitted(filename) {
366+
$$$ThrowOnError(##class(SourceControl.Git.Change).SetUncommitted(filename, "edit", InternalName, $username, "", 1, "", "", 0))
367+
}
346368
}
347369
}
348370
}
@@ -400,9 +422,41 @@ Method ExternalName(InternalName As %String) As %String
400422
quit ##class(SourceControl.Git.Utils).ExternalName(InternalName)
401423
}
402424

425+
ClassMethod FullExternalName(InternalName As %String) As %String
426+
{
427+
quit ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
428+
}
429+
403430
Method IsReadOnly(InternalName As %String) As %Boolean
404431
{
405-
quit ##class(SourceControl.Git.Utils).Locked()
432+
set settings = ##class(SourceControl.Git.Settings).%New()
433+
quit (##class(SourceControl.Git.Utils).Locked()
434+
&& '$get(^IRIS.Temp("sscProd",$job,"bypassLock")))
435+
|| (##class(SourceControl.Git.Utils).ItemIsProductionToDecompose($get(InternalName))
436+
&& 'settings.decomposeProdAllowIDE
437+
&& '##class(SourceControl.Git.Production).IsEnsPortal())
438+
}
439+
440+
/// Called before the item is saved to the database it is passed
441+
/// a reference to the current temporary storage of this item so that it
442+
/// can be modified before the save completes. If you quit with an error
443+
/// value then it will abort the save.
444+
Method OnBeforeSave(InternalName As %String, Location As %String = "", Object As %RegisteredObject = {$$$NULLOREF}) As %Status
445+
{
446+
set st = $$$OK
447+
if ##class(SourceControl.Git.Utils).ItemIsProductionToDecompose(InternalName) {
448+
do ##class(SourceControl.Git.Production).GetModifiedItemsBeforeSave(InternalName,,.productionItems)
449+
set key = $order(productionItems(""))
450+
while (key '= "") {
451+
// if any modified items in this production class are checked out by a different user, fail the check.
452+
set st = ..GetStatus(key, .IsInSourceControl, .Editable, .IsCheckedOut, .UserCheckedOut)
453+
quit:$$$ISERR(st)
454+
if 'Editable set st = $$$ERROR($$$GeneralError,"Item is checked out by another user: "_UserCheckedOut)
455+
quit:$$$ISERR(st)
456+
set key = $order(productionItems(key))
457+
}
458+
}
459+
return st
406460
}
407461

408462
/// Check the status of the given item
@@ -411,7 +465,7 @@ Method IsReadOnly(InternalName As %String) As %Boolean
411465
Method GetStatus(ByRef InternalName As %String, ByRef IsInSourceControl As %Boolean, ByRef Editable As %Boolean, ByRef IsCheckedOut As %Boolean, ByRef UserCheckedOut As %String) As %Status
412466
{
413467
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(.InternalName)
414-
set Editable='..IsReadOnly(),IsCheckedOut=1,UserCheckedOut=""
468+
set Editable='..IsReadOnly($get(InternalName)),IsCheckedOut=1,UserCheckedOut=""
415469
set filename=##class(SourceControl.Git.Utils).FullExternalName(.InternalName)
416470
set IsInSourceControl=(filename'=""&&($$$FileExists(filename)))
417471
if filename="" quit $$$OK

cls/SourceControl/Git/File.cls

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,23 @@ ClassMethod ExternalNameToInternalName(ExternalName As %String) As %String
3535
}
3636
new %SourceControl //don't trigger source hooks with this test load to get the Name
3737
set sc=$system.OBJ.Load(ExternalName,"-d",,.outName,1)
38-
if (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(##class(SourceControl.Git.Utils).Type(outName),"U") '= "CSP") {
38+
set itemIsPTD = 0
39+
if $data(outName) = 11 {
40+
set key = $order(outName(""))
41+
while (key '= "") {
42+
if ($zconvert($piece(outName,".",*),"U") = "PTD") {
43+
set itemIsPTD = 1
44+
quit
45+
}
46+
set key = $order(outName(key))
47+
}
48+
}
49+
if itemIsPTD && ##class(%Library.EnsembleMgr).IsEnsembleNamespace() {
50+
do ##class(SourceControl.Git.Production).ParseExternalName($replace(ExternalName,"\","/"),.internalName)
51+
} elseif (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(##class(SourceControl.Git.Utils).Type(outName),"U") '= "CSP") {
3952
set internalName = outName
53+
}
54+
if (internalName '= "") {
4055
set inst.InternalName = internalName
4156
$$$ThrowOnError(inst.%Save())
4257
}
@@ -68,4 +83,4 @@ Storage Default
6883
<Type>%Storage.Persistent</Type>
6984
}
7085

71-
}
86+
}

cls/SourceControl/Git/PackageManagerContext.cls

Lines changed: 5 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") {
@@ -50,4 +54,4 @@ Method Dump()
5054
write !?4,"Git-enabled? ",$select(..IsInGitEnabledPackage:"Yes",1:"No"),!
5155
}
5256

53-
}
57+
}

0 commit comments

Comments
 (0)