Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.16.1] - Unreleased

### Fixed
- Changes to % routines mapped to the current namespace may now be added to source control and committed (#944)

## [2.16.0] - 2026-03-06

### Added
Expand Down
4 changes: 2 additions & 2 deletions cls/SourceControl/Git/Utils.cls
Original file line number Diff line number Diff line change
Expand Up @@ -2414,7 +2414,7 @@ ClassMethod GitStatus(ByRef files, IncludeAllFiles = 0)
while $listnext(list, pointer, item) {
set operation = $zstrip($extract(item, 1, 2), "<>W")
set externalName = $extract(item, 4, *)
set internalName = ..NameToInternalName(externalName,,0)
set internalName = ..NameToInternalName(externalName,0,0)
if (internalName '= "") {
set files(internalName) = $listbuild(operation, externalName)
if (operation '= "D") {
Expand Down Expand Up @@ -2464,7 +2464,7 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
set usertype = 0
}

if 'usertype && $$CheckProtect^%qccServer(InternalName) {
if 'usertype && ##class(%Routine).CheckProtect(InternalName) {
quit ""
}

Expand Down
45 changes: 45 additions & 0 deletions ipm
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash

# ipm: convenience wrapper to run a ZPM command in an IRIS session
# Usage examples:
# ipm git-source-control test -only
# ipm -U USER git-source-control test -only -verbose

CONTAINER=${IRIS_CONTAINER:-git-source-control-iris-1}
ARGS=()
CMD=()

# Allow namespace via env
if [ -n "$IRIS_NAMESPACE" ]; then
ARGS+=( -U "$IRIS_NAMESPACE" )
elif [ -n "$IRISNAMESPACE" ]; then
ARGS+=( -U "$IRISNAMESPACE" )
fi

# Optional leading -U <ns>
if [ "$1" = "-U" ] && [ -n "$2" ]; then
ARGS+=( -U "$2" )
shift 2
fi

# Remaining args compose the ZPM command string
while [[ $# -gt 0 ]]; do
CMD+=( "$1" )
shift
done

if [ ${#CMD[@]} -eq 0 ]; then
echo "Usage: ipm [ -U <NAMESPACE> ] <zpm command and args...>" >&2
exit 1
fi

# Join CMD array into a single string
CMDSTR="${CMD[*]}"
# Escape quotes for ObjectScript string literal
CMDSTR_ESC=${CMDSTR//\"/\"\"}

# Build an OS snippet and feed to iris session via docker exec
(
echo "zpm \"${CMDSTR_ESC}\":1:1"
echo "halt"
) | docker exec -i "$CONTAINER" iris session iris "${ARGS[@]}"
41 changes: 41 additions & 0 deletions iriscli
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash

# iriscli: convenience wrapper to run ObjectScript commands or script files in an IRIS session
# Usage examples:
# iriscli -U USER (interactive-like session)
# iriscli -U USER script.txt (run a script file)
# iriscli script.txt arg1 arg2 (run script with parameters)

CONTAINER=${IRIS_CONTAINER:-git-source-control-iris-1}
ARGS=()
PARAMS=()
file=

if [ -n "$IRIS_NAMESPACE" ]; then
ARGS+=( -U $IRIS_NAMESPACE )
elif [ -n "$IRISNAMESPACE" ]; then
ARGS+=( -U $IRISNAMESPACE )
fi

while [[ $# -gt 0 ]]; do
if [ -x $1 ]; then
file=$1
elif [ -z "$file" ]; then
ARGS+=("$1")
else
PARAMS+=("$1")
fi
shift
done

if [ -n "$file" ]; then
(
for param in ${PARAMS[@]}; do
echo "Set params(\$i(params)) = \"${param//\"/\"\"}\""
done
egrep -v '^(;|#|//)|^$' $file;
echo halt
) | docker exec -i "$CONTAINER" iris session iris "${ARGS[@]}"
else
docker exec -i "$CONTAINER" iris session iris "${ARGS[@]}"
fi
2 changes: 1 addition & 1 deletion module.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Document name="git-source-control.ZPM">
<Module>
<Name>git-source-control</Name>
<Version>2.16.0</Version>
<Version>2.16.1</Version>
<Description>Server-side source control extension for use of Git on InterSystems platforms</Description>
<Keywords>git source control studio vscode</Keywords>
<Packaging>module</Packaging>
Expand Down
172 changes: 172 additions & 0 deletions test/UnitTest/SourceControl/Git/Utils/AddToSourceControl.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
Import SourceControl.Git

Class UnitTest.SourceControl.Git.Utils.AddToSourceControl Extends UnitTest.SourceControl.Git.AbstractTest
{

Property RoutineMappingCreated As %Boolean [ InitialExpression = 0 ];

Property PackageMappingCreated As %Boolean [ InitialExpression = 0 ];

Method %OnNew(initvalue) As %Status
{
Set sc = ##super(initvalue)
If $$$ISERR(sc) Quit sc
Set settings = ##class(SourceControl.Git.Settings).%New()
Set settings.Mappings("MAC","*") = "rtn/"
Do settings.%Save()
Quit $$$OK
}

Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
{
// Clean up routines
if ##class(%Routine).Exists("%gitunittestroutine.mac") {
do ##class(%Routine).Delete("%gitunittestroutine.mac")
}
if ##class(%Routine).Exists("gitunittestroutine.mac") {
do ##class(%Routine).Delete("gitunittestroutine.mac")
}
// Clean up classes
if $$$defClassDefined("%gitunittest.TestClass") {
do $system.OBJ.Delete("%gitunittest.TestClass.CLS", "-d")
}
if $$$defClassDefined("gitunittest.TestClass") {
do $system.OBJ.Delete("gitunittest.TestClass.CLS", "-d")
}
// Clean up mappings
if ..RoutineMappingCreated {
do ..DeleteRoutineMapping("%gitunittest*")
}
if ..PackageMappingCreated {
do ..DeletePackageMapping("%gitunittest")
}
Quit ##super()
}

/// Returns the name of the default routine database for this namespace.
Method GetDefaultDBName() As %String
{
set defaultDB = ##class(%SYS.Namespace).GetRoutineDest($namespace, "gitunittestroutine.mac")
quit ##class(%File).GetDirectoryPiece(defaultDB, ##class(%File).GetDirectoryLength(defaultDB))
}

/// Creates a routine mapping in %SYS so that routines matching the given pattern
/// resolve to the current namespace's default routine database.
Method CreateRoutineMapping(pattern As %String) As %Status
{
set ns = $namespace
set defaultDBName = ..GetDefaultDBName()

new $namespace
set $namespace = "%SYS"
set props("Database") = defaultDBName
set sc = ##class(Config.MapRoutines).Create(ns, pattern, .props)
quit sc
}

/// Creates a package mapping in %SYS so that classes in the given package
/// resolve to the current namespace's default routine database.
Method CreatePackageMapping(package As %String) As %Status
{
set ns = $namespace
set defaultDBName = ..GetDefaultDBName()

new $namespace
set $namespace = "%SYS"
set props("Database") = defaultDBName
set sc = ##class(Config.MapPackages).Create(ns, package, .props)
quit sc
}

/// Deletes a routine mapping from %SYS for the given pattern.
ClassMethod DeleteRoutineMapping(pattern As %String) As %Status
{
set ns = $namespace
new $namespace
set $namespace = "%SYS"
quit ##class(Config.MapRoutines).Delete(ns, pattern)
}

/// Deletes a package mapping from %SYS for the given package.
ClassMethod DeletePackageMapping(package As %String) As %Status
{
set ns = $namespace
new $namespace
set $namespace = "%SYS"
quit ##class(Config.MapPackages).Delete(ns, package)
}

Method TestNonPercentRoutine()
{
set internalName = "gitunittestroutine.mac"

set r = ##class(%Routine).%New(internalName)
do r.WriteLine(" write ""hello"",!")
$$$ThrowOnError(r.Save())

// Verify FullExternalName returns a non-empty path
set fullName = ##class(Utils).FullExternalName(internalName)
do $$$AssertNotEquals(fullName, "")

// Add to source control and verify it exported to the file system
do $$$AssertStatusOK(##class(Utils).AddToSourceControl(internalName))
do $$$AssertTrue(##class(%File).Exists(fullName), "non-% routine file should exist on disk after AddToSourceControl")
}

Method TestPercentRoutine()
{
set internalName = "%gitunittestroutine.mac"

$$$ThrowOnError(..CreateRoutineMapping("%gitunittest*"))
set ..RoutineMappingCreated = 1

// Create a routine named %gitunittestroutine.mac
set r = ##class(%Routine).%New(internalName)
do r.WriteLine(" write ""hello"",!")
$$$ThrowOnError(r.Save())


// Verify FullExternalName returns a non-empty path
set fullName = ##class(Utils).FullExternalName(internalName)
do $$$AssertNotEquals(fullName, "")

// Add to source control and verify it exported to the file system
do $$$AssertStatusOK(##class(Utils).AddToSourceControl(internalName))
do $$$AssertTrue(##class(%File).Exists(fullName), "% routine file should exist on disk after AddToSourceControl")
}

Method TestNonPercentClass()
{
set internalName = "gitunittest.TestClass.CLS"

set classDef = ##class(%Dictionary.ClassDefinition).%New()
set classDef.Name = "gitunittest.TestClass"
$$$ThrowOnError(classDef.%Save())

set fullName = ##class(Utils).FullExternalName(internalName)
do $$$AssertNotEquals(fullName, "", "FullExternalName should return a path for non-% class")

do $$$AssertStatusOK(##class(Utils).AddToSourceControl(internalName))
do $$$AssertTrue(##class(%File).Exists(fullName), "non-% class file should exist on disk after AddToSourceControl")
}

Method TestPercentClass()
{
set internalName = "%gitunittest.TestClass.CLS"

$$$ThrowOnError(..CreatePackageMapping("%gitunittest"))
set ..PackageMappingCreated = 1

set classDef = ##class(%Dictionary.ClassDefinition).%New()
set classDef.Name = "%gitunittest.TestClass"
$$$ThrowOnError(classDef.%Save())
$$$ThrowOnError($system.OBJ.Compile("%gitunittest.TestClass", "ck-d"))

set fullName = ##class(Utils).FullExternalName(internalName)
do $$$AssertNotEquals(fullName, "", "FullExternalName should return a path for % class")

do $$$AssertStatusOK(##class(Utils).AddToSourceControl(internalName))
do $$$AssertTrue(##class(%File).Exists(fullName), "% class file should exist on disk after AddToSourceControl")
}

}
Loading