Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Comment thread
isc-dchui marked this conversation as resolved.

### 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
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