From a6eaee5f887edb418003887ad84810870958feab Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:51:19 -0500 Subject: [PATCH] fix: explicitly create nested subdirectories on export --- CHANGELOG.md | 1 + cls/SourceControl/Git/Production.cls | 2 +- cls/SourceControl/Git/Utils.cls | 2 +- .../SourceControl/Git/Utils/ExportItem.cls | 33 +++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 test/UnitTest/SourceControl/Git/Utils/ExportItem.cls diff --git a/CHANGELOG.md b/CHANGELOG.md index 449e4ae6..7317471b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed `` error in GetTempFileAndRoutineTS when an OS-level error code was stored as a routine timestamp (#832) - Fixed issue where Generated Files Read-only option didn't entirely work for files not in source control (#712) - Fixed issue where a deleted file not within Embedded Git's management was causing the pull handler to crash and not fully update the Iris instance (#928) +- Fixed exporting items in nested subdirectories in IRIS versions >= 2025.3 (#786) ## [2.15.0] - 2026-01-06 diff --git a/cls/SourceControl/Git/Production.cls b/cls/SourceControl/Git/Production.cls index 4f7c18ef..4754aa58 100644 --- a/cls/SourceControl/Git/Production.cls +++ b/cls/SourceControl/Git/Production.cls @@ -108,7 +108,7 @@ ClassMethod ExportProjectForPTD(productionClass As %String, ptdName As %String, $$$ThrowOnError(project.%Save()) set projContentsList(exportNotesPTDName_".PTD") = "" set projContentsList(project.Name_".PRJ") = "" - $$$ThrowOnError($System.OBJ.Export(.projContentsList, exportPath, "/diffexport=1")) + $$$ThrowOnError($System.OBJ.Export(.projContentsList, exportPath, "/diffexport=1/createdirs=1")) // remove the LastModified timestamp from the exported file set fileStream = ##class(%Stream.FileCharacter).%OpenId(exportPath,,.st) $$$ThrowOnError(st) diff --git a/cls/SourceControl/Git/Utils.cls b/cls/SourceControl/Git/Utils.cls index 7e015fcc..0b407878 100644 --- a/cls/SourceControl/Git/Utils.cls +++ b/cls/SourceControl/Git/Utils.cls @@ -1790,7 +1790,7 @@ ClassMethod ExportItem(InternalName As %String, expand As %Boolean = 1, force As do ..RemoveFromServerSideSourceControl(InternalName) } else { - $$$QuitOnError($SYSTEM.OBJ.ExportUDL(InternalName, filename,"-d/diff")) + $$$QuitOnError($SYSTEM.OBJ.ExportUDL(InternalName, filename,"-d/diff/createdirs=1")) } } if (filename '= "") && ##class(%File).Exists(filename) { diff --git a/test/UnitTest/SourceControl/Git/Utils/ExportItem.cls b/test/UnitTest/SourceControl/Git/Utils/ExportItem.cls new file mode 100644 index 00000000..710a182f --- /dev/null +++ b/test/UnitTest/SourceControl/Git/Utils/ExportItem.cls @@ -0,0 +1,33 @@ +Class UnitTest.SourceControl.Git.Utils.ExportItem Extends UnitTest.SourceControl.Git.AbstractTest +{ + +/// Tests that ExportItem creates intermediate directories when they don't exist +Method TestExportCreatesIntermediateDirectories() +{ + set internalName = "TestGitExport.Deep.Nested.Pkg.MyClass.CLS" + + // Create the class in the database so ExportItem has something to export + set classDef = ##class(%Dictionary.ClassDefinition).%New() + set classDef.Name = "TestGitExport.Deep.Nested.Pkg.MyClass" + $$$ThrowOnError(classDef.%Save()) + + // Determine the expected export path and ensure its directory does NOT exist yet + set filename = ##class(SourceControl.Git.Utils).FullExternalName(internalName) + set dirPath = ##class(%File).GetDirectory(filename) + if ##class(%File).DirectoryExists(dirPath) { + do ##class(%File).RemoveDirectoryTree(dirPath) + } + do $$$AssertNotTrue(##class(%File).DirectoryExists(dirPath), "Directory should not exist before export") + + // Export the item - this should succeed even without pre-existing directories + do $$$AssertStatusOK(##class(SourceControl.Git.Utils).ExportItem(internalName, 0, 1)) + do $$$AssertTrue(##class(%File).Exists(filename)) + + // Clean up + do $system.OBJ.Delete(internalName, "-d") + if ##class(%File).Exists(filename) { + do ##class(%File).Delete(filename) + } +} + +}