Skip to content

Various performance fixes #318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 23, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Deletion of files in locked environment is now suppressed (#302)
- Failed to import file VS Code popup no longer shows up after overwriting file on server once (#264)
- Don't automatically stage files added to source control (#303)
- Performance improvements (#269, #315)
- Checkout of branches whose names contain slashes via Web UI no longer fails (#295)

## [2.3.0] - 2023-12-06
Expand Down
15 changes: 14 additions & 1 deletion cls/SourceControl/Git/Change.cls
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,19 @@ ClassMethod IsUncommitted(Filename, ByRef ID) As %Boolean
}

/// Goes through Uncommitted queue and removes any items of action 'edit' or 'add' which are ReadOnly or non-existent on the filesystem
ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles) As %Status
ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles, Force As %Boolean = 0) As %Status
{
set lock = $System.AutoLock.Lock("^SourceControl.Git.Refresh",,10)
if lock = $$$NULLOREF {
quit $$$ERROR($$$GeneralError,"Unable to get exclusive lock for refresh of uncommitted changes.")
}
if 'Force {
// 10-second throttle on RefreshUncommitted
if $zdatetime($ztimestamp,-2) - $Get(^IRIS.Temp.gitsourcecontrol("Refresh"),0) < 10 {
merge gitFiles = ^IRIS.Temp.gitsourcecontrol("LastUncommitted")
quit $$$OK
}
}
kill gitFiles

// files from the uncommitted queue
Expand Down Expand Up @@ -124,6 +135,8 @@ ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles)
}
set filename=$order(gitFiles(filename),1,details)
}
set ^IRIS.Temp.gitsourcecontrol("Refresh") = $zdatetime($ztimestamp,-2)
merge ^IRIS.Temp.gitsourcecontrol("LastUncommitted") = gitFiles
quit sc
}

Expand Down
71 changes: 71 additions & 0 deletions cls/SourceControl/Git/File.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/// Has a cache of file internal/external name mappings as of LastModifiedTime.
Class SourceControl.Git.File Extends %Persistent
{

Property ExternalName As %String(MAXLEN = "") [ Required ];

Property ExternalNameHash As %String [ Calculated, SqlComputeCode = {set {*} = $System.Encryption.SHAHash(256,{ExternalName})}, SqlComputed ];

Property InternalName As %String(MAXLEN = 255) [ Required ];

Property LastModifiedTime As %String [ Required ];

Index InternalName On InternalName;

Index ExternalNameHash On ExternalNameHash [ Unique ];

ClassMethod ExternalNameToInternalName(ExternalName As %String) As %String
{
set internalName = ""
if ##class(%File).Exists(ExternalName) {
set lastModified = ##class(%Library.File).GetFileDateModified(ExternalName)
set hash = $System.Encryption.SHAHash(256,ExternalName)
if ..ExternalNameHashExists(hash,.id) {
set inst = ..%OpenId(id,,.sc)
$$$ThrowOnError(sc)
if inst.LastModifiedTime = lastModified {
quit inst.InternalName
} else {
set inst.LastModifiedTime = lastModified
}
} else {
set inst = ..%New()
set inst.ExternalName = ExternalName
set inst.LastModifiedTime = lastModified
}
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 internalName = outName
set inst.InternalName = internalName
$$$ThrowOnError(inst.%Save())
}
}
quit internalName
}

Storage Default
{
<Data name="FileDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>ExternalName</Value>
</Value>
<Value name="3">
<Value>InternalName</Value>
</Value>
<Value name="4">
<Value>LastModifiedTime</Value>
</Value>
</Data>
<DataLocation>^SourceControl.Git.FileD</DataLocation>
<DefaultData>FileDefaultData</DefaultData>
<IdLocation>^SourceControl.Git.FileD</IdLocation>
<IndexLocation>^SourceControl.Git.FileI</IndexLocation>
<StreamLocation>^SourceControl.Git.FileS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}
6 changes: 3 additions & 3 deletions cls/SourceControl/Git/Settings.cls
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ Method OnAfterConfigure() As %Boolean
// using work queue manager ensures proper OS user context when running ssh-keygen
set workMgr = $System.WorkMgr.%New("")
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).GenerateSSHKeyPair"))
$$$ThrowOnError(workMgr.Sync())
$$$ThrowOnError(workMgr.WaitForComplete())
set pubKeyName = ..privateKeyFile_".pub"
if ##class(%File).Exists(pubKeyName) {
set pubStream = ##class(%Stream.FileCharacter).%OpenId(pubKeyName,,.sc)
Expand Down Expand Up @@ -158,7 +158,7 @@ Method OnAfterConfigure() As %Boolean
// using work queue manager ensures proper OS user context/file ownership
set workMgr = $System.WorkMgr.%New("")
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Init"))
$$$ThrowOnError(workMgr.Sync())
$$$ThrowOnError(workMgr.WaitForComplete())
do ##class(SourceControl.Git.Utils).EmptyInitialCommit()
} elseif (value = 2) {
set response = ##class(%Library.Prompt).GetString("Git remote URL (note: if authentication is required, use SSH, not HTTPS):",.remote,,,,defaultPromptFlag)
Expand All @@ -171,7 +171,7 @@ Method OnAfterConfigure() As %Boolean
// using work queue manager ensures proper OS user context/file ownership
set workMgr = $System.WorkMgr.%New("")
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Clone",remote))
$$$ThrowOnError(workMgr.Sync())
$$$ThrowOnError(workMgr.WaitForComplete())
}
}
}
Expand Down
87 changes: 62 additions & 25 deletions cls/SourceControl/Git/Utils.cls
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ ClassMethod Revert(InternalName As %String) As %Status
set filename = ..FullExternalName(.InternalName)
do ..RunGitCommand("checkout", .errStream, .outStream, "--", filename)
$$$QuitOnError(##class(SourceControl.Git.Change).RemoveUncommitted(filename,0,1))
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1))
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1,,1))
$$$QuitOnError(..ImportItem(InternalName,1))
quit $$$OK
}
Expand All @@ -299,7 +299,7 @@ ClassMethod Commit(InternalName As %String, Message As %String = "example commit
do ..RunGitWithArgs(.errStream, .outStream, "commit", "--author", author, "-m", Message, filename)
do ..PrintStreams(outStream, outStream)
$$$QuitOnError(##class(SourceControl.Git.Change).RemoveUncommitted(filename))
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted())
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(,,,1))
quit $$$OK
}

Expand Down Expand Up @@ -453,7 +453,7 @@ ClassMethod GenerateSSHKeyPair() As %Status
do ##class(%File).CreateDirectoryChain(dir)
set outLog = ##class(%Library.File).TempFilename()
set errLog = ##class(%Library.File).TempFilename()
do $zf(-100,"/SHELL /STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog),
do $zf(-100,"/STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog),
"ssh-keygen",
"-t","ed25519",
"-C",email,
Expand Down Expand Up @@ -776,10 +776,13 @@ ClassMethod Type(InternalName As %String) As %String
} else {
set type ="csp"
}
if (type = "csp") {
quit type
}
}

// For an abstract document, use the GetOther() method to try to determine its "real" class
If ##class(%RoutineMgr).UserType(InternalName,.docclass,.doctype) {
If ..UserTypeCached(InternalName,.docclass,.doctype) {
// Check for a real abstract document subclass (or GetOther() may not work)
If $classmethod(docclass,"%IsA","%Studio.AbstractDocument") && $classmethod(docclass,"%Extends","Ens.Util.AbstractDocument") {
// Grab the actual name
Expand Down Expand Up @@ -966,7 +969,7 @@ ClassMethod IsInSourceControl(InternalName As %String, ByRef sourceControlItem A

ClassMethod FullExternalName(ByRef InternalName As %String, ByRef MappingExists As %Boolean) As %String [ CodeMode = expression ]
{
##class(%File).NormalizeFilename(..TempFolder()_..ExternalName(.InternalName, .MappingExists))
..TempFolder()_..ExternalName(.InternalName, .MappingExists)
}

ClassMethod NormalizeInternalName(ByRef name As %String) As %String
Expand Down Expand Up @@ -1106,7 +1109,7 @@ ClassMethod ImportItem(InternalName As %String, force As %Boolean = 0, verbose A
#dim sc as %Status = $$$OK

if ..IsRoutineOutdated(InternalName) || force {
if ##class(%RoutineMgr).UserType(InternalName,.docclass,.doctype) {
if ..UserTypeCached(InternalName,.docclass,.doctype) {
set routineMgr = ##class(%RoutineMgr).%OpenId(InternalName)
do routineMgr.Code.Rewind()
set source = ##class(%Stream.FileCharacter).%OpenId(filename,,.sc)
Expand Down Expand Up @@ -1169,7 +1172,7 @@ ClassMethod ListItemsInFiles(ByRef itemList, ByRef err) As %Status
set mappedFilePath = ##class(%File).NormalizeFilename(mappedRelativePath, ..TempFolder())

if (##class(%File).DirectoryExists(mappedFilePath)){
if ##class(%Library.RoutineMgr).UserType("foo."_mappingFileType) {
if ..UserTypeCached("foo."_mappingFileType) {
set fileSpec = "*."_$zcvt(mappingFileType,"L")_";*."_$zconvert(mappingFileType,"U")
set files = ##class(%Library.File).FileSetFunc(mappedFilePath,fileSpec)
while files.%Next() {
Expand Down Expand Up @@ -1268,7 +1271,7 @@ ClassMethod ImportRoutines(force As %Boolean = 0) As %Status
if ##class(%File).Exists(filename) && '##class(%File).Delete(filename) {
set ec = $$$ADDSC(ec, ..MakeError("Error while removing "_item))
}
}elseif ##class(%Library.RoutineMgr).UserType(item) {
}elseif ..UserTypeCached(item) {
set ec = $$$ADDSC(ec, ##class(%Library.RoutineMgr).Delete(item))
} else {
set deleted = 0
Expand Down Expand Up @@ -1485,8 +1488,20 @@ ClassMethod RunGitCommandWithInput(command As %String, inFile As %String = "", O
set errLog = ##class(%Library.File).TempFilename()

set command = $extract(..GitBinPath(),2,*-1)
// Need /SHELL on Linux to avoid permissions errors trying to use root's config
set returnCode = $zf(-100,"/SHELL /STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog)_$case(inFile, "":"", :" /STDIN="_$$$QUOTE(inFile)),command,newArgs...)
set baseArgs = "/STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog)_$case(inFile, "":"", :" /STDIN="_$$$QUOTE(inFile))
try {
// Inject instance manager directory as global git config home directory
// On Linux, this avoids trying to use /root/.config/git/attributes for global git config
set env("XDG_CONFIG_HOME") = ##class(%File).ManagerDirectory()
set returnCode = $zf(-100,"/ENV=env... "_baseArgs,command,newArgs...)
} catch e {
if $$$isWINDOWS {
set returnCode = $zf(-100,baseArgs,command,newArgs...)
} else {
// If can't inject XDG_CONFIG_HOME (older IRIS version), need /SHELL on Linux to avoid permissions errors trying to use root's config
set returnCode = $zf(-100,"/SHELL "_baseArgs,command,newArgs...)
}
}

set errStream = ##class(%Stream.FileCharacter).%OpenId(errLog,,.sc)
set outStream = ##class(%Stream.FileCharacter).%OpenId(outLog,,.sc)
Expand Down Expand Up @@ -1538,10 +1553,10 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
set relativePath = context.ResourceReference.Processor.OnItemRelativePath(InternalName)
quit relativePath
}
set usertype=$system.CLS.IsMthd("%Library.RoutineMgr","UserType")

// For an abstract document, use the GetOther() method to try to determine its "real" class
if usertype,##class(%RoutineMgr).UserType(InternalName,.docclass,.doctype) {
if ..UserTypeCached(InternalName,.docclass,.doctype) {
set usertype = 1
// Check for a real abstract document subclass (or GetOther() may not work)
if $classmethod(docclass,"%IsA","%Studio.AbstractDocument") && $classmethod(docclass,"%Extends","Ens.Util.AbstractDocument") {
// Grab the actual name
Expand All @@ -1552,9 +1567,11 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
set InternalName = actualName
}
}
} else {
set usertype = 0
}

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

Expand Down Expand Up @@ -1631,7 +1648,7 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
set InternalName=$extract(InternalName,$length(p)+2,*)
quit $translate(found_$translate(InternalName,"%","_"),"\","/")

} elseif ext="CLS"||(ext="PRJ")||(usertype&&(##class(%RoutineMgr).UserType(InternalName))) {
} elseif ext="CLS"||(ext="PRJ")||usertype {
set nam=$replace(nam,"%", ..PercentClassReplace())
if default{
set nam=$translate(nam,".","/")
Expand All @@ -1647,6 +1664,31 @@ ClassMethod Name(InternalName As %String, ByRef MappingExists As %Boolean) As %S
}
}

/// Implementation copied from %Library.RoutineMgr, but with results cached in a PPG.
ClassMethod UserTypeCached(Name As %String, ByRef Class As %String, ByRef StudioType As %String, ByRef Schema As %String, ByRef StudioIcon As %Integer) As %Boolean
{
Set ext=$zconvert($piece(Name,".",*),"U") If ext="" Quit 0
If $Data(^||UserTypeCache(ext,"NotUserType"))#2 {
Quit 0
}
If $Data(^||UserTypeCache(ext),data)#2 {
Set Class = $Get(^||UserTypeCache(ext,"Class"))
Set StudioType=$list(data),Schema=$listget(data,3),StudioIcon=+$listget(data,4)
Quit 1
}
Do StudioDocument^%SYS.cspServer2(.document)
Set Class="",StudioType="",Schema=""
For Set Class=$order(document(Class)) Quit:Class=""||($data(document(Class,ext),data))
If Class="" {
Set ^||UserTypeCache(ext,"NotUserType") = 1
Quit 0
}
Set StudioType=$list(data),Schema=$listget(data,3),StudioIcon=+$listget(data,4)
Set ^||UserTypeCache(ext) = data
Set ^||UserTypeCache(ext,"Class") = Class
Quit 1
}

/*
NameToInternalName(name): given a Unix-style slash path relative to repo root,
returns the internal name for that file (e.g., cls/SourceControl/Git/Utils.cls -> SourceControl.Git.Utils.CLS)
Expand All @@ -1658,21 +1700,16 @@ ClassMethod NameToInternalName(Name, IgnorePercent = 1, IgnoreNonexistent = 1, V
set context = ##class(SourceControl.Git.PackageManagerContext).%Get()
if (context.IsInGitEnabledPackage) {
if ($zconvert(Name,"U")'[$zconvert(context.Package.Root,"U")) {
set Name = ##class(%File).NormalizeFilename(context.Package.Root_Name)
set Name = context.Package.Root_Name
}
} elseif ($zconvert(Name,"U")'[$zconvert($$$SourceRoot,"U")) {
set Name = ##class(%File).NormalizeFilename(..TempFolder()_Name)
set Name = ..TempFolder()_Name
}
if (##class(%File).Exists(Name)) {
new %SourceControl //don't trigger source hooks with this test load to get the Name
set sc=$system.OBJ.Load(Name,"-d",,.outName,1)
if (($data(outName)=1) || ($data(outName) = 11 && ($order(outName(""),-1) = $order(outName(""))))) && ($zconvert(..Type(outName),"U") '= "CSP") {
//only set if a single Name was returned ... ignore multi-item files
set InternalName=outName
if (context.IsInGitEnabledPackage) {
// Don't need mappings!
return ..NormalizeInternalName(InternalName)
}
set InternalName = ##class(SourceControl.Git.File).ExternalNameToInternalName(Name)
if (InternalName '= "") && (context.IsInGitEnabledPackage) {
// Don't need mappings!
return ..NormalizeInternalName(InternalName)
}
} else {
// check for file in uncommitted queue
Expand Down
8 changes: 6 additions & 2 deletions cls/SourceControl/Git/WebUIDriver.cls
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Class SourceControl.Git.WebUIDriver

ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Output handled As %Boolean = 0, Output %data As %Stream.Object)
{
do %session.Unlock()
set context = ##class(SourceControl.Git.PackageManagerContext).ForInternalName(InternalName)
kill %data
#dim %response as %CSP.Response
Expand Down Expand Up @@ -99,6 +100,8 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
set stdin = $piece(args(1),$char(10),2,*)
set args(1) = $piece(args(1),$char(10))
}
set readOnlyCommands = $listbuild("branch","tag","log","ls-files","ls-tree","show","status","diff")
set baseCommand = $Piece(args(1)," ")

set gitArgs($increment(gitArgs)) = "color.ui=true"

Expand Down Expand Up @@ -154,7 +157,9 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
do %data.WriteLine("Git-Stderr-Length: " _ (errStream.Size + nLines))
do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected
do %data.Rewind()
do ##class(SourceControl.Git.Change).RefreshUncommitted()
if '$listfind(readOnlyCommands,baseCommand) {
do ##class(SourceControl.Git.Change).RefreshUncommitted()
}
set handled = 1
}
}
Expand Down Expand Up @@ -218,4 +223,3 @@ ClassMethod GetSettingsURL(%request As %CSP.Request) As %SystemBase
}

}

1 change: 1 addition & 0 deletions csp/webuidriver.csp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
} catch e {
// ignore; may occur on platform versions without the above properties
}
do %session.Unlock()

// Serve static content when appropriate.
// index.html
Expand Down