Skip to content

Commit 2d08c9a

Browse files
authored
Merge branch 'main' into ipm-multideveloper-setting
2 parents 56148ea + 78a8871 commit 2d08c9a

File tree

9 files changed

+314
-9
lines changed

9 files changed

+314
-9
lines changed

CHANGELOG.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ 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.4.0] - 2024-05-31
8+
9+
## [2.4.0] - Unreleased
910

1011
### Added
12+
- Pre-release support for IPM v0.9.0+
1113
- Items mapped from database other than namespace's default routine database are now ignored by default when exporting or adding files
1214
- New setting to configure whether mapped items should be should be treated as read-only
13-
- Now skips files belonging to other git enabled packages in `##class(SourceControl.Git.Change).RefreshUncommitted()`
15+
- Now skips files belonging to other git enabled packages in `##class(SourceControl.Git.Change).RefreshUncommitted()` (#347)
16+
- Added a new "Branch" parameter to `##class(SourceControl.Git.PullEventHandler)` (#351)
17+
- Command-line utility to do a baseline export of items in a namespace
18+
1419

1520
## [2.3.1] - 2024-04-30
1621

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ This might look like:
5858
5959
![Example of mapping configuration](docs/images/settings.PNG "Example of mapping configuration")
6060
61+
### Pull Event Handlers
62+
63+
The ##class(SourceControl.Git.PullEventHandler) is a base class that can be extended in order to develop functionality that should be run when the repository pulls from remote. The code placed inside the subclass' OnPull() method will be executed any time a pull occurs.
64+
65+
A recommended way to implement CI/CD would be to use one of the pre-defined subclasses of PullEventHandler that are placed inside the PullEventHandler package. Additionally, custom load logic can be placed in that package following the model of the existing subclasses.
66+
6167
### Security
6268
6369
#### Unsecured (http) connections
@@ -99,6 +105,8 @@ Assuming you have the local and remote repositories created,
99105
`git config core.sshCommand 'ssh -i ~/.ssh/<private key name>'`
100106
8. Test the refresh button for the remote branches on the WebUI, fetch from the source control menu in Studio or VS Code, and `git fetch` in Git Bash. All 3 should work without any issues.
101107
108+
109+
102110
## During Development
103111
104112
:warning: Whenever any code in this project is updated outside the server (e.g. after every `git pull`), you _have_ to run `zpm "load <absolute path to git-source-control>"`. Otherwise, the changes won't be reflected on the server. However, if you load git-source-control via the InterSystems package manager and run `git pull` via the extension itself with the default pull event handler configured, it'll just work.

cls/SourceControl/Git/API.cls

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,12 @@ ClassMethod Unlock()
5656
quit ##class(SourceControl.Git.Utils).Locked(0)
5757
}
5858

59+
/// Run in terminal to baseline a namespace by adding all items to source control.
60+
/// - pCommitMessage: if defined, all changes in namespace context will be committed.
61+
/// - pPushToRemote: if defined, will run a git push to the specified remote
62+
ClassMethod BaselineExport(pCommitMessage = "", pPushToRemote = "") As %Status
63+
{
64+
quit ##class(SourceControl.Git.Utils).BaselineExport(pCommitMessage, pPushToRemote)
5965
}
6066

67+
}

cls/SourceControl/Git/PackageManagerContext.cls

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Class SourceControl.Git.PackageManagerContext Extends %ZPM.PackageManager.Core.Singleton
1+
Class SourceControl.Git.PackageManagerContext Extends SourceControl.Git.Util.Singleton
22
{
33

44
Property InternalName As %String;
@@ -7,22 +7,24 @@ Property IsInDefaultPackage As %Boolean [ InitialExpression = 0 ];
77

88
Property IsInGitEnabledPackage As %Boolean [ InitialExpression = 0 ];
99

10-
/// Really is a %ZPM.PackageManager.Developer.Module
10+
/// Really is a %ZPM.PackageManager.Developer.Module / %IPM.Storage.Module
1111
Property Package As %RegisteredObject [ InitialExpression = {$$$NULLOREF} ];
1212

13-
// Really is a %ZPM.PackageManager.Developer.ResourceReference
14-
13+
/// Really is a %ZPM.PackageManager.Developer.ResourceReference / %IPM.Storage.ResourceReference
1514
Property ResourceReference As %RegisteredObject [ InitialExpression = {$$$NULLOREF} ];
1615

1716
Method InternalNameSet(InternalName As %String = "") As %Status
1817
{
1918
set InternalName = ##class(SourceControl.Git.Utils).NormalizeInternalName(InternalName)
2019
if (InternalName '= i%InternalName) {
2120
set i%InternalName = InternalName
22-
if '$$$comClassDefined("%ZPM.PackageManager.Developer.Extension.Utils") {
21+
if $$$comClassDefined("%IPM.ExtensionBase.Utils") {
22+
set ..Package = ##class(%IPM.ExtensionBase.Utils).FindHomeModule(InternalName,,.resourceReference)
23+
} elseif $$$comClassDefined("%ZPM.PackageManager.Developer.Extension.Utils") {
24+
set ..Package = ##class(%ZPM.PackageManager.Developer.Extension.Utils).FindHomeModule(InternalName,,.resourceReference)
25+
} else {
2326
quit $$$OK
2427
}
25-
set ..Package = ##class(%ZPM.PackageManager.Developer.Extension.Utils).FindHomeModule(InternalName,,.resourceReference)
2628
set ..ResourceReference = resourceReference
2729
set ..IsInGitEnabledPackage = $isobject(..Package) && ##class(%Library.File).Exists(##class(%Library.File).NormalizeFilename(".git",..Package.Root))
2830
set ..IsInDefaultPackage = $isobject(..Package) && (##class(%Library.File).NormalizeDirectory(..Package.Root) = ##class(%Library.File).NormalizeDirectory(##class(SourceControl.Git.Utils).DefaultTempFolder()))

cls/SourceControl/Git/PullEventHandler.cls

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Property LocalRoot As %String(MAXLEN = "");
1313
/// Modified files (integer-subscripted array storing objects of class SourceControl.Git.Modification)
1414
Property ModifiedFiles [ MultiDimensional ];
1515

16+
/// The branch that is checked out before OnPull() is called
17+
Property Branch [ InitialExpression = {##class(SourceControl.Git.Utils).GetCurrentBranch()} ];
18+
1619
Method OnPull() As %Status [ Abstract ]
1720
{
1821
}
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/// General class to extend to add a "singleton" interface to any registered or persistent class.
2+
/// For persistent classes, requires that the class has a unique index defined on a read-only property with an InitialExpression.
3+
/// Copied from %ZPM.PackageManager.Core.Singleton to support upgrade to IPM; at some point, can fall back to IPM equivalent.
4+
Class SourceControl.Git.Util.Singleton Extends %RegisteredObject [ Abstract ]
5+
{
6+
7+
/// If set to 1, calls to %Get must return an instance of this class created in the current namespace; a new instance will be created if none exists.
8+
Parameter NAMESPACESCOPE As BOOLEAN = 0;
9+
10+
/// PPG in which to track references to the instance of this class
11+
Parameter PPG As STRING = "^||%ZPM.Singleton";
12+
13+
/// Internal property to track the namespace in which this instance was created.
14+
Property %namespace As %String [ InitialExpression = {$Namespace}, Private, Transient ];
15+
16+
/// This method finds the existing instance of an object of a current class (created in the namespace if ..#NAMESPACESCOPE is 1) if it exists in the current process.
17+
/// Exceptions are caught by calling code.
18+
ClassMethod GetInMemoryInstance() As SourceControl.Git.Util.Singleton [ CodeMode = objectgenerator, Private ]
19+
{
20+
Set tClass = %class.Name
21+
Set tPPG = $$$comMemberKeyGet(tClass,$$$cCLASSparameter,"PPG",$$$cPARAMdefault)
22+
Set tIncludeNS = $$$comMemberKeyGet(tClass,$$$cCLASSparameter,"NAMESPACESCOPE",$$$cPARAMdefault)
23+
Set tPPGRef = tPPG_"("_$$$QUOTE(tClass)_$Select(tIncludeNS:",$Namespace",1:"")_")"
24+
Do %code.WriteLine(" Set tReturnValue = $$$NULLOREF")
25+
Do %code.WriteLine(" If $Data("_tPPGRef_",tObjInt) {")
26+
Do %code.WriteLine(" Set tInstance = $$$objIntToOref(tObjInt)")
27+
Do %code.WriteLine(" If $IsObject(tInstance) && ($classname(tInstance) = "_$$$QUOTE(tClass)_") {")
28+
Do %code.WriteLine(" Set tReturnValue = tInstance")
29+
Do %code.WriteLine(" }")
30+
Do %code.WriteLine(" }")
31+
Do %code.WriteLine(" Quit tReturnValue")
32+
Quit $$$OK
33+
}
34+
35+
/// Return the single per-process/namespace instance of this class, or create a new one.
36+
/// For persistent classes, may open the existing single record by its unique index.
37+
ClassMethod %Get(Output pSC As %Status) As SourceControl.Git.Util.Singleton [ CodeMode = objectgenerator, Final ]
38+
{
39+
Set tSC = $$$OK
40+
Try {
41+
Set tThisClass = %class.Name
42+
Set tGenPersistent = 0
43+
44+
// No-op for abstract classes.
45+
If $$$comClassKeyGet(tThisClass,$$$cCLASSabstract) {
46+
Quit
47+
}
48+
49+
// Validation for persistent classes.
50+
If ($$$comClassKeyGet(tThisClass,$$$cCLASSclasstype) = $$$cCLASSCLASSTYPEPERSISTENT) {
51+
Set tGenPersistent = 1
52+
53+
// Find a candidate index.
54+
Set tInitialExpression = ""
55+
Set tIndex = ""
56+
For {
57+
Set tIndex = $$$comMemberNext(tThisClass,$$$cCLASSindex,tIndex)
58+
If (tIndex = "") {
59+
Quit
60+
}
61+
62+
// Is the index unique?
63+
If '$$$comMemberKeyGet(tThisClass,$$$cCLASSindex,tIndex,$$$cINDEXunique) {
64+
Continue
65+
}
66+
67+
// Is the index on one property?
68+
If ($$$comMemberKeyGet(tThisClass,$$$cCLASSindex,tIndex,$$$cINDEXproperty) '= 1) {
69+
Continue
70+
}
71+
72+
// Get that one property.
73+
Set tProperty = $$$comSubMemberKeyGet(tThisClass,$$$cCLASSindex,tIndex,$$$cINDEXproperty,1,$$$cINDEXPROPproperty)
74+
If (tProperty = "") {
75+
Continue
76+
}
77+
78+
// Is that property read-only?
79+
If '$$$comMemberKeyGet(tThisClass,$$$cCLASSproperty,tProperty,$$$cPROPreadonly) {
80+
Continue
81+
}
82+
83+
// Get the property's initial expression.
84+
Set tInitialExpression = $$$comMemberKeyGet(tThisClass,$$$cCLASSproperty,tProperty,$$$cPROPinitialexpression)
85+
If (tInitialExpression = "") {
86+
Continue
87+
}
88+
89+
// If we got this far, we have a match, and tIndex won't be empty.
90+
Quit
91+
}
92+
93+
If (tIndex = "") {
94+
// If we found no results...
95+
Set tMsg = "Class '%1' that extends %ZPM.PackageManager.Core.Singleton must define a unique index on a read-only property with an InitialExpression defined."
96+
Set tSC = $$$ERROR($$$GeneralError,$$$FormatText(tMsg,tThisClass))
97+
Quit
98+
}
99+
}
100+
101+
Do %code.WriteLine(" Set tInstance = $$$NULLOREF")
102+
Do %code.WriteLine(" Set pSC = $$$OK")
103+
Do %code.WriteLine(" Try {")
104+
Do %code.WriteLine(" Set tInstance = ..GetInMemoryInstance()")
105+
If tGenPersistent {
106+
// Support opening an existing object by its unique index on a read-only property with an initial expression
107+
Do %code.WriteLine(" If (tInstance = $$$NULLOREF) && .."_tIndex_"Exists("_tInitialExpression_") {")
108+
Do %code.WriteLine(" Set tInstance = .."_tIndex_"Open("_tInitialExpression_",,.pSC)")
109+
// If we found an existing instance, ensure that we have changes that another process may have made
110+
Do %code.WriteLine(" } ElseIf $IsObject(tInstance) && (tInstance.%Id() '= """") {")
111+
Do %code.WriteLine(" Set pSC = tInstance.%Reload()")
112+
Do %code.WriteLine(" } ElseIf (tInstance = $$$NULLOREF) {")
113+
} Else {
114+
Do %code.WriteLine(" If (tInstance = $$$NULLOREF) {")
115+
}
116+
Do %code.WriteLine(" Set tInstance = ..%New()")
117+
Do %code.WriteLine(" }")
118+
119+
Do %code.WriteLine(" } Catch e { ")
120+
Do %code.WriteLine(" Set tInstance = $$$NULLOREF")
121+
Do %code.WriteLine(" Set pSC = e.AsStatus()")
122+
Do %code.WriteLine(" }")
123+
Do %code.WriteLine(" Quit tInstance")
124+
} Catch e {
125+
Set tSC = e.AsStatus()
126+
}
127+
Quit tSC
128+
}
129+
130+
/// Tracks the OREF of this instance in a PPG for later reference.
131+
/// Subclasses of %ZPM.PackageManager.Core.Singleton that override this method *MUST* call ##super().
132+
Method %OnNew() As %Status [ Private, ServerOnly = 1 ]
133+
{
134+
Quit ..%RecordOref()
135+
}
136+
137+
/// Removes the OREF of this instance from PPG.
138+
/// Subclasses of %ZPM.PackageManager.Core.Singleton that override this method *MUST* call ##super().
139+
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
140+
{
141+
Quit ..%RemoveOref()
142+
}
143+
144+
Method %RecordOref() As %Status [ CodeMode = objectgenerator, Final, Internal, Private ]
145+
{
146+
Set tClass = %class.Name
147+
Set tPPG = $$$comMemberKeyGet(tClass,$$$cCLASSparameter,"PPG",$$$cPARAMdefault)
148+
Set tIncludeNS = $$$comMemberKeyGet(tClass,$$$cCLASSparameter,"NAMESPACESCOPE",$$$cPARAMdefault)
149+
Set tPPGRef = tPPG_"("_$$$QUOTE(tClass)_$Select(tIncludeNS:",..%namespace",1:"")_")"
150+
Do %code.WriteLine(" If $Data("_tPPGRef_") {")
151+
Do %code.WriteLine(" Quit $$$ERROR($$$GeneralError,""Instance of "_tClass_" already created for this process."")")
152+
Do %code.WriteLine(" }")
153+
Do %code.WriteLine(" Set "_tPPGRef_" = +$This")
154+
Do %code.WriteLine(" Quit $$$OK")
155+
Quit $$$OK
156+
}
157+
158+
Method %RemoveOref() As %Status [ CodeMode = objectgenerator, Final, Internal, Private ]
159+
{
160+
Set tClass = %class.Name
161+
Set tPPG = $$$comMemberKeyGet(tClass,$$$cCLASSparameter,"PPG",$$$cPARAMdefault)
162+
Set tIncludeNS = $$$comMemberKeyGet(tClass,$$$cCLASSparameter,"NAMESPACESCOPE",$$$cPARAMdefault)
163+
Set tPPGRef = tPPG_"("_$$$QUOTE(tClass)_$Select(tIncludeNS:",..%namespace",1:"")_")"
164+
Do %code.WriteLine(" Kill "_tPPGRef)
165+
Do %code.WriteLine(" Quit $$$OK")
166+
Quit $$$OK
167+
}
168+
169+
}
170+

cls/SourceControl/Git/Utils.cls

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,12 @@ ClassMethod Fetch(ByRef diffFiles) As %Status
344344
quit $$$OK
345345
}
346346

347+
ClassMethod GetCurrentBranch() As %String{
348+
do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("branch",,.errStream,.outStream,"--show-current")
349+
set branchName = outStream.ReadLine(outStream.Size)
350+
quit branchName
351+
}
352+
347353
ClassMethod Pull(remote As %String = "origin", preview As %Boolean = 0) As %Status
348354
{
349355
#define Force 1
@@ -513,6 +519,7 @@ ClassMethod AddToServerSideSourceControl(InternalName As %String) As %Status
513519

514520
ClassMethod AddToSourceControl(InternalName As %String) As %Status
515521
{
522+
do ##class(SourceControl.Git.PackageManagerContext).ForInternalName(InternalName)
516523
set settings = ##class(SourceControl.Git.Settings).%New()
517524
#dim i as %Integer
518525
#dim ec as %Status = $$$OK
@@ -2062,5 +2069,47 @@ ClassMethod ResetSourceControlClass()
20622069
do ##class(%Studio.SourceControl.Interface).SourceControlClassSet("")
20632070
}
20642071

2072+
ClassMethod BaselineExport(pCommitMessage = "", pPushToRemote = "") As %Status
2073+
{
2074+
set sc = $$$OK
2075+
try {
2076+
write !, "Exporting items..."
2077+
set rs = ##class(%Library.RoutineMgr).StudioOpenDialogFunc(
2078+
"*.mac,*.int,*.inc,*.cls,*.csp" // Spec
2079+
, , ,0 // SystemFiles
2080+
,1 // Flat
2081+
,0 // NotStudio
2082+
,0 // ShowGenerated
2083+
, , ,0 // Mapped
2084+
)
2085+
throw:rs.%SQLCODE<0 ##class(%Exception.SQL).CreateFromSQLCODE(rs.%SQLCODE,rs.%Message)
2086+
while rs.%Next(.sc) {
2087+
$$$ThrowOnError(sc)
2088+
set internalName = rs.Name
2089+
// exclude items in a non-default IPM package
2090+
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(internalName)
2091+
continue:($isobject(context.Package) && 'context.IsInDefaultPackage)
2092+
$$$ThrowOnError(..AddToSourceControl(internalName))
2093+
}
2094+
if pCommitMessage '= "" {
2095+
// switch to default context
2096+
do ##class(SourceControl.Git.PackageManagerContext).ForInternalName("")
2097+
do ..RunGitWithArgs(.errStream, .outStream, "add", "--all")
2098+
do ..PrintStreams(errStream, outStream)
2099+
set username = ..GitUserName()
2100+
set email = ..GitUserEmail()
2101+
set author = username_" <"_email_">"
2102+
do ..RunGitWithArgs(.errStream, .outStream, "commit", "--author", author, "-m", pCommitMessage)
2103+
do ..PrintStreams(errStream, outStream)
2104+
$$$ThrowOnError(##class(SourceControl.Git.Change).RefreshUncommitted(,,,1))
2105+
if (pPushToRemote '= "") {
2106+
$$$ThrowOnError(..Push(pPushToRemote))
2107+
}
2108+
}
2109+
} catch err {
2110+
set sc = err.AsStatus()
2111+
}
2112+
return sc
20652113
}
20662114

2115+
}

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.3.1</Version>
6+
<Version>2.4.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>

0 commit comments

Comments
 (0)