Skip to content

Commit

Permalink
Merge pull request #469 from intersystems/production-change-control
Browse files Browse the repository at this point in the history
Production decomposition
  • Loading branch information
isc-tleavitt authored Nov 18, 2024
2 parents 6880f7f + e8e7209 commit 4c5dfee
Show file tree
Hide file tree
Showing 18 changed files with 1,470 additions and 65 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.8.0] - Unreleased

### Added
- Production Decomposition mode allows controlling interoperability productions as individual files for each host (#469)

## [2.7.1] - 2024-11-13

### Fixed
Expand Down
7 changes: 7 additions & 0 deletions cls/SourceControl/Git/API.cls
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,11 @@ ClassMethod MapEverywhere()
Quit ##class(SourceControl.Git.Installer).MapEverywhere()
}

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

}
82 changes: 68 additions & 14 deletions cls/SourceControl/Git/Extension.cls
Original file line number Diff line number Diff line change
Expand Up @@ -327,22 +327,44 @@ Method OnBeforeTimestamp(InternalName As %String)
Method OnAfterSave(InternalName As %String, Object As %RegisteredObject = {$$$NULLOREF}) As %Status
{
set sc = $$$OK
quit:$get(%gscSkipSaveHooks) sc
try {
set InternalName = ##class(SourceControl.Git.Utils).NormalizeInternalName(.InternalName,.fromWebApp,.fullExternalName)
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(InternalName)
if ##class(SourceControl.Git.Utils).IsNamespaceInGit() && ..IsInSourceControl(InternalName) {
if fromWebApp {
if fullExternalName = ##class(SourceControl.Git.Utils).FullExternalName(InternalName) {
// Reimport item into database
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportItem(InternalName,,1,1))
if ##class(SourceControl.Git.Utils).IsNamespaceInGit() {
// If this is a production class and production decomposition is enabled, call recursively on all modified production items.
if ##class(SourceControl.Git.Utils).ItemIsProductionToDecompose(InternalName) {
do ##class(SourceControl.Git.Production).GetModifiedItemsAfterSave(InternalName, .productionItems)
set key = $order(productionItems(""))
while (key '= "") {
if productionItems(key) = "D" {
set itemFilename = ..FullExternalName(key)
if ##class(SourceControl.Git.Utils).IsInSourceControl(key) && ##class(%File).Exists(itemFilename) {
$$$ThrowOnError(##class(SourceControl.Git.Change).AddDeletedToUncommitted(itemFilename, key))
$$$ThrowOnError(##class(SourceControl.Git.Utils).DeleteExternalFile(key))
}
} elseif '..IsInSourceControl(key) {
$$$ThrowOnError(##class(SourceControl.Git.Utils).AddToSourceControl(key))
} else {
$$$ThrowOnError(..OnAfterSave(key))
}
set key = $order(productionItems(key))
}
} else {
set filename = ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
$$$ThrowOnError(##class(SourceControl.Git.Utils).RemoveRoutineTSH(InternalName))
set forceExport = (InternalName'= "") && ($data(..Modified(InternalName)))
$$$ThrowOnError(##class(SourceControl.Git.Utils).ExportItem(InternalName,,forceExport))
if '##class(SourceControl.Git.Change).IsUncommitted(filename) {
$$$ThrowOnError(##class(SourceControl.Git.Change).SetUncommitted(filename, "edit", InternalName, $username, "", 1, "", "", 0))
}
if ..IsInSourceControl(InternalName) {
if fromWebApp {
if fullExternalName = ##class(SourceControl.Git.Utils).FullExternalName(InternalName) {
// Reimport item into database
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportItem(InternalName,,1,1))
}
} else {
set filename = ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
$$$ThrowOnError(##class(SourceControl.Git.Utils).RemoveRoutineTSH(InternalName))
set forceExport = (InternalName'= "") && ($data(..Modified(InternalName)))
$$$ThrowOnError(##class(SourceControl.Git.Utils).ExportItem(InternalName,,forceExport))
if '##class(SourceControl.Git.Change).IsUncommitted(filename) {
$$$ThrowOnError(##class(SourceControl.Git.Change).SetUncommitted(filename, "edit", InternalName, $username, "", 1, "", "", 0))
}
}
}
}
Expand Down Expand Up @@ -400,9 +422,41 @@ Method ExternalName(InternalName As %String) As %String
quit ##class(SourceControl.Git.Utils).ExternalName(InternalName)
}

ClassMethod FullExternalName(InternalName As %String) As %String
{
quit ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
}

Method IsReadOnly(InternalName As %String) As %Boolean
{
quit ##class(SourceControl.Git.Utils).Locked()
set settings = ##class(SourceControl.Git.Settings).%New()
quit (##class(SourceControl.Git.Utils).Locked()
&& '$get(^IRIS.Temp("sscProd",$job,"bypassLock")))
|| (##class(SourceControl.Git.Utils).ItemIsProductionToDecompose($get(InternalName))
&& 'settings.decomposeProdAllowIDE
&& '##class(SourceControl.Git.Production).IsEnsPortal())
}

/// Called before the item is saved to the database it is passed
/// a reference to the current temporary storage of this item so that it
/// can be modified before the save completes. If you quit with an error
/// value then it will abort the save.
Method OnBeforeSave(InternalName As %String, Location As %String = "", Object As %RegisteredObject = {$$$NULLOREF}) As %Status
{
set st = $$$OK
if ##class(SourceControl.Git.Utils).ItemIsProductionToDecompose(InternalName) {
do ##class(SourceControl.Git.Production).GetModifiedItemsBeforeSave(InternalName,,.productionItems)
set key = $order(productionItems(""))
while (key '= "") {
// if any modified items in this production class are checked out by a different user, fail the check.
set st = ..GetStatus(key, .IsInSourceControl, .Editable, .IsCheckedOut, .UserCheckedOut)
quit:$$$ISERR(st)
if 'Editable set st = $$$ERROR($$$GeneralError,"Item is checked out by another user: "_UserCheckedOut)
quit:$$$ISERR(st)
set key = $order(productionItems(key))
}
}
return st
}

/// Check the status of the given item
Expand All @@ -411,7 +465,7 @@ Method IsReadOnly(InternalName As %String) As %Boolean
Method GetStatus(ByRef InternalName As %String, ByRef IsInSourceControl As %Boolean, ByRef Editable As %Boolean, ByRef IsCheckedOut As %Boolean, ByRef UserCheckedOut As %String) As %Status
{
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(.InternalName)
set Editable='..IsReadOnly(),IsCheckedOut=1,UserCheckedOut=""
set Editable='..IsReadOnly($get(InternalName)),IsCheckedOut=1,UserCheckedOut=""
set filename=##class(SourceControl.Git.Utils).FullExternalName(.InternalName)
set IsInSourceControl=(filename'=""&&($$$FileExists(filename)))
if filename="" quit $$$OK
Expand Down
19 changes: 17 additions & 2 deletions cls/SourceControl/Git/File.cls
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,23 @@ ClassMethod ExternalNameToInternalName(ExternalName As %String) As %String
}
new %SourceControl //don't trigger source hooks with this test load to get the Name
set sc=$system.OBJ.Load(ExternalName,"-d",,.outName,1)
if (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(##class(SourceControl.Git.Utils).Type(outName),"U") '= "CSP") {
set itemIsPTD = 0
if $data(outName) = 11 {
set key = $order(outName(""))
while (key '= "") {
if ($zconvert($piece(outName,".",*),"U") = "PTD") {
set itemIsPTD = 1
quit
}
set key = $order(outName(key))
}
}
if itemIsPTD && ##class(%Library.EnsembleMgr).IsEnsembleNamespace() {
do ##class(SourceControl.Git.Production).ParseExternalName($replace(ExternalName,"\","/"),.internalName)
} elseif (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(##class(SourceControl.Git.Utils).Type(outName),"U") '= "CSP") {
set internalName = outName
}
if (internalName '= "") {
set inst.InternalName = internalName
$$$ThrowOnError(inst.%Save())
}
Expand Down Expand Up @@ -68,4 +83,4 @@ Storage Default
<Type>%Storage.Persistent</Type>
}

}
}
6 changes: 5 additions & 1 deletion cls/SourceControl/Git/PackageManagerContext.cls
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ Method InternalNameSet(InternalName As %String = "") As %Status
set InternalName = ##class(SourceControl.Git.Utils).NormalizeInternalName(InternalName)
if (InternalName '= i%InternalName) {
set i%InternalName = InternalName
if (InternalName = ##class(SourceControl.Git.Settings.Document).#INTERNALNAME) {
// git source control settings document is never in an IPM context
quit $$$OK
}
if $$$comClassDefined("%IPM.ExtensionBase.Utils") {
set ..Package = ##class(%IPM.ExtensionBase.Utils).FindHomeModule(InternalName,,.resourceReference)
} elseif $$$comClassDefined("%ZPM.PackageManager.Developer.Extension.Utils") {
Expand Down Expand Up @@ -50,4 +54,4 @@ Method Dump()
write !?4,"Git-enabled? ",$select(..IsInGitEnabledPackage:"Yes",1:"No"),!
}

}
}
Loading

0 comments on commit 4c5dfee

Please sign in to comment.