Skip to content

Commit fed34cc

Browse files
authored
Merge pull request #255 from intersystems/simplify-pull-and-configuration
Simplify pull and configuration
2 parents 18d1a7b + f6c22fa commit fed34cc

File tree

11 files changed

+495
-18
lines changed

11 files changed

+495
-18
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +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.2.0] - Unreleased
9+
10+
### Added
11+
- Page to support deployment (git pull and run pull event handler) with verbose output
12+
- Support for git clone to initialize namespace via Settings page and `##class(SourceControl.Git.API).Configure()` (#234, #237)
13+
- Support for automatically creating SSH keys for use as deploy keys via Settings page and `Configure()` (#33)
14+
15+
### Fixed
16+
- Protect against Favorites links containing control characters (#254)
17+
- Green checks for valid paths shown consistently (#229)
18+
819
## [2.1.1] - 2023-02-24
920

1021
### Fixed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
```
1919
d ##class(SourceControl.Git.API).Configure()
2020
```
21+
This will also allow you to generate an SSH key for use as (e.g.) a deploy key and to initialize or clone a git repo.
2122
3. If using VSCode: Set up `isfs` server-side editing. First, save your current workspace in which you have the code open. Then, open the `.code-workspace` file generated by VS Code and add the following to the list of folders:
2223
```
2324
{

cls/SourceControl/Git/API.cls

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ ClassMethod Configure()
4040
}
4141

4242
/// API for git pull - just wraps Utils
43-
ClassMethod Pull()
43+
ClassMethod Pull(preview As %Boolean = 0)
4444
{
45-
quit ##class(SourceControl.Git.Utils).Pull()
45+
quit ##class(SourceControl.Git.Utils).Pull(,.preview)
4646
}
4747

4848
/// Locks the environment to prevent changes to code other than through git pull.

cls/SourceControl/Git/Extension.cls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,3 +335,4 @@ Method AddToSourceControl(InternalName As %String, Description As %String = "")
335335
}
336336

337337
}
338+

cls/SourceControl/Git/PullEventHandler/IncrementalLoad.cls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,4 @@ Method OnPull() As %Status
3131
}
3232

3333
}
34+

cls/SourceControl/Git/Settings.cls

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Property gitBinPath As %String(MAXLEN = "");
1111
/// Local git repo root folder
1212
Property namespaceTemp As %String(MAXLEN = "") [ InitialExpression = {##class(SourceControl.Git.Utils).TempFolder()}, Required ];
1313

14-
/// Path to private key file (for ssh remotes)
14+
/// Path to private key file for SSH remotes; if file does not exist, later prompts will help set it up with proper ownership
1515
Property privateKeyFile As %String(MAXLEN = "") [ InitialExpression = {##class(SourceControl.Git.Utils).PrivateKeyFile()} ];
1616

1717
/// Event handler class for git pull
@@ -104,8 +104,67 @@ ClassMethod Configure() As %Boolean [ CodeMode = objectgenerator ]
104104
}
105105
do %code.WriteLine(" $$$ThrowOnError(inst.%Save())")
106106
do %code.WriteLine(" write !,""Settings saved.""")
107+
do %code.WriteLine(" do inst.OnAfterConfigure()")
107108
do %code.WriteLine(" quit 1")
108109
}
109110

111+
Method OnAfterConfigure() As %Boolean
112+
{
113+
set defaultPromptFlag = $$$DisableBackupCharMask + $$$TrapCtrlCMask + $$$EnableQuitCharMask + $$$DisableHelpCharMask + $$$DisableHelpContextCharMask + $$$TrapErrorMask
114+
if (..privateKeyFile '= "") && '##class(%File).Exists(..privateKeyFile) {
115+
set value = 1
116+
set response = ##class(%Library.Prompt).GetYesNo("Do you wish to create a new SSH key pair?",.value,,defaultPromptFlag)
117+
if (response '= $$$SuccessResponse) {
118+
quit
119+
}
120+
if value {
121+
#dim workMgr As %SYSTEM.AbstractWorkMgr
122+
// using work queue manager ensures proper OS user context when running ssh-keygen
123+
set workMgr = $System.WorkMgr.%New("")
124+
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).GenerateSSHKeyPair"))
125+
$$$ThrowOnError(workMgr.Sync())
126+
set pubKeyName = ..privateKeyFile_".pub"
127+
if ##class(%File).Exists(pubKeyName) {
128+
set pubStream = ##class(%Stream.FileCharacter).%OpenId(pubKeyName,,.sc)
129+
$$$ThrowOnError(sc)
130+
Write !,"Public key (for use as ""deploy key"", etc.):",!
131+
do pubStream.OutputToDevice()
132+
Write !
133+
}
134+
}
135+
}
136+
137+
set gitDir = ##class(%File).NormalizeDirectory(..namespaceTemp)_".git"
138+
if '##class(%File).DirectoryExists(gitDir) {
139+
set list(1) = "Initialize empty repo"
140+
set list(2) = "Clone..."
141+
set list(3) = "Do nothing"
142+
set value = ""
143+
while ('+$get(value)) {
144+
set response = ##class(%Library.Prompt).GetMenu("No git repo exists in "_..namespaceTemp_". Choose an option:",.value,.list,,defaultPromptFlag + $$$InitialDisplayMask)
145+
if (response '= $$$SuccessResponse) && (response '= $$$BackupResponse) {
146+
return
147+
}
148+
}
149+
if (value = 1) {
150+
// using work queue manager ensures proper OS user context/file ownership
151+
set workMgr = $System.WorkMgr.%New("")
152+
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Init"))
153+
$$$ThrowOnError(workMgr.Sync())
154+
} elseif (value = 2) {
155+
set response = ##class(%Library.Prompt).GetString("Git remote URL (note: if authentication is required, use SSH, not HTTPS):",.remote,,,,defaultPromptFlag)
156+
if (response '= $$$SuccessResponse) {
157+
quit
158+
}
159+
if (remote = "") {
160+
quit
161+
}
162+
// using work queue manager ensures proper OS user context/file ownership
163+
set workMgr = $System.WorkMgr.%New("")
164+
$$$ThrowOnError(workMgr.Queue("##class(SourceControl.Git.Utils).Clone",remote))
165+
$$$ThrowOnError(workMgr.Sync())
166+
}
167+
}
110168
}
111169

170+
}

cls/SourceControl/Git/Utils.cls

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,7 @@ ClassMethod UserAction(InternalName As %String, MenuName As %String, ByRef Targe
180180
// cleanup items info
181181
kill @..#Storage@("items")
182182
kill @..#Storage@("TSH")
183-
do ..RunGitCommand("init",.errStream,.outStream)
184-
$$$NewLineIfNonEmptyStream(errStream)
185-
do errStream.OutputToDevice()
186-
$$$NewLineIfNonEmptyStream(outStream)
187-
do outStream.OutputToDevice()
183+
do ..Init()
188184
} else {
189185
set ec = ..MakeError("Unable to create folder "_..TempFolder())
190186
}
@@ -271,6 +267,16 @@ ClassMethod AfterUserAction(Type As %Integer, Name As %String, InternalName As %
271267
quit $$$OK
272268
}
273269

270+
ClassMethod Init() As %Status
271+
{
272+
do ..RunGitCommand("init",.errStream,.outStream)
273+
$$$NewLineIfNonEmptyStream(errStream)
274+
do errStream.OutputToDevice()
275+
$$$NewLineIfNonEmptyStream(outStream)
276+
do outStream.OutputToDevice()
277+
quit $$$OK
278+
}
279+
274280
ClassMethod Revert(InternalName As %String) As %Status
275281
{
276282
set filename = ..FullExternalName(.InternalName)
@@ -342,7 +348,7 @@ ClassMethod Fetch(ByRef diffFiles) As %Status
342348
quit $$$OK
343349
}
344350

345-
ClassMethod Pull(remote As %String = "origin") As %Status
351+
ClassMethod Pull(remote As %String = "origin", preview As %Boolean = 0) As %Status
346352
{
347353
#define Force 1
348354
do ##class(SourceControl.Git.Utils).RunGitCommandWithInput("branch",,.errStream,.outStream,"--show-current")
@@ -376,7 +382,12 @@ ClassMethod Pull(remote As %String = "origin") As %Status
376382
}
377383
if ('$data(files)) {
378384
write !, ?4, "None"
379-
write !, "Already up to date. Git Pull will not be executed."
385+
if preview {
386+
quit $$$OK
387+
}
388+
write !, "Already up to date."
389+
quit $$$OK
390+
} elseif preview {
380391
quit $$$OK
381392
}
382393

@@ -423,6 +434,52 @@ ClassMethod Pull(remote As %String = "origin") As %Status
423434
quit event.OnPull()
424435
}
425436

437+
ClassMethod Clone(remote As %String) As %Status
438+
{
439+
set settings = ##class(SourceControl.Git.Settings).%New()
440+
// TODO: eventually use /ENV flag with GIT_TERMINAL_PROMPT=0. (This isn't doc'd yet and is only in really new versions.)
441+
set sc = ..RunGitWithArgs(.errStream, .outStream, "clone", remote, settings.namespaceTemp)
442+
$$$NewLineIfNonEmptyStream(errStream)
443+
while 'errStream.AtEnd {
444+
write errStream.ReadLine(),!
445+
}
446+
$$$NewLineIfNonEmptyStream(outStream)
447+
while 'outStream.AtEnd {
448+
write outStream.ReadLine(),!
449+
}
450+
quit $$$OK
451+
}
452+
453+
ClassMethod GenerateSSHKeyPair() As %Status
454+
{
455+
set settings = ##class(SourceControl.Git.Settings).%New()
456+
set filename = settings.privateKeyFile
457+
set email = settings.gitUserEmail
458+
set dir = ##class(%File).GetDirectory(filename)
459+
if ##class(%File).Exists(filename) {
460+
Throw ##class(%Exception.General).%New("File "_filename_" already exists")
461+
}
462+
do ##class(%File).CreateDirectoryChain(dir)
463+
set outLog = ##class(%Library.File).TempFilename()
464+
set errLog = ##class(%Library.File).TempFilename()
465+
do $zf(-100,"/SHELL /STDOUT="_$$$QUOTE(outLog)_" /STDERR="_$$$QUOTE(errLog),
466+
"ssh-keygen",
467+
"-t","ed25519",
468+
"-C",email,
469+
"-f",filename,
470+
"-N","")
471+
472+
set errStream = ##class(%Stream.FileCharacter).%OpenId(errLog,,.sc)
473+
set outStream = ##class(%Stream.FileCharacter).%OpenId(outLog,,.sc)
474+
set outStream.TranslateTable="UTF8"
475+
for stream=errStream,outStream {
476+
set stream.RemoveOnClose = 1
477+
}
478+
do outStream.OutputToDevice()
479+
do errStream.OutputToDevice()
480+
quit $$$OK
481+
}
482+
426483
ClassMethod IsNamespaceInGit() As %Boolean [ CodeMode = expression ]
427484
{
428485
##class(%File).Exists(..TempFolder()_".git")
@@ -1789,15 +1846,22 @@ ClassMethod ConfigureWeb()
17891846
set installNamespace = $Namespace
17901847
new $Namespace
17911848
set $Namespace = "%SYS"
1792-
write !,"Adding favorite for all users... "
1849+
write !,"Adding favorites for all users:"
17931850
set sql = "insert or update into %SYS_Portal.Users (Username, Page, Data) "_
17941851
"select ID,?,? from Security.Users"
17951852
set caption = "Git: "_installNamespace
17961853
set link = "/isc/studio/usertemplates/gitsourcecontrol/webuidriver.csp/"_installNamespace_"/"
1797-
do ##class(%SQL.Statement).%ExecDirect(,sql,caption,link).%Display()
1854+
write !,"Adding Git favorite... "
1855+
set statement = ##class(%SQL.Statement).%New()
1856+
set statement.%SelectMode = 0
1857+
do ##class(%SQL.Statement).%ExecDirect(statement,sql,caption,link).%Display()
1858+
set caption = "Git Pull: "_installNamespace
1859+
set link = "/isc/studio/usertemplates/gitsourcecontrol/pull.csp?$NAMESPACE="_installNamespace
1860+
write !,"Adding Git Pull favorite... "
1861+
do ##class(%SQL.Statement).%ExecDirect(statement,sql,caption,link).%Display()
17981862
write !,"Setting GroupById to %ISCMgtPortal for /isc/studio/usertemplates... "
17991863
set sql = "update Security.Applications set GroupById='%ISCMgtPortal' where ID = '/isc/studio/usertemplates'"
1800-
do ##class(%SQL.Statement).%ExecDirect(,sql).%Display()
1864+
do ##class(%SQL.Statement).%ExecDirect(statement,sql).%Display()
18011865
}
18021866

18031867
ClassMethod CheckInitialization()
@@ -1919,4 +1983,3 @@ ClassMethod BuildCEInstallationPackage(ByRef destination As %String) As %Status
19191983
}
19201984

19211985
}
1922-

0 commit comments

Comments
 (0)