Skip to content

Commit 6ad5fe0

Browse files
authored
Merge pull request #459 from intersystems/safeguard-discard
Safeguard discard
2 parents f3fd329 + b8b143c commit 6ad5fe0

File tree

20 files changed

+678
-32
lines changed

20 files changed

+678
-32
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [2.6.0] - Unreleased
99

10-
### Added
10+
### Added
11+
- Discards safeguarded by discard stash and warning modal (#455)
1112
- Files in uncommitted queue in any namespace warn users when opened except for in VSCode (#370)
1213
- Added link back to IRIS management portal from Settings, Git WebUI pages (#449)
1314
- Added Import all and Import All (Force) to basic mode menu (#498)

cls/SourceControl/Git/Build.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ ClassMethod BuildUIForDevMode(devMode As %Boolean, rootDirectory As %String)
1414
write !, $zf(-100, "/SHELL", "npm", "run", "build", "--prefix", webUIDirectory)
1515
}
1616

17-
}
18-
17+
}

cls/SourceControl/Git/Change.cls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ ClassMethod AddDeletedToUncommitted(Filename, InternalName) As %Status
6666
Quit ..SetUncommitted(Filename, "delete", InternalName, $USERNAME, "", 1, "", "", 0)
6767
}
6868

69+
/// The Filename here is an ExternalName formatted name with the full file path
6970
ClassMethod IsUncommitted(Filename, ByRef ID) As %Boolean
7071
{
7172
&SQL(SELECT ID INTO :ID FROM SourceControl_Git.Change WHERE ItemFile = :Filename AND Committed = '0')
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
Class SourceControl.Git.DiscardState Extends (%Persistent, %JSON.Adaptor)
2+
{
3+
4+
Property FullExternalName As %String(MAXLEN = "") [ Required ];
5+
6+
Property Name As %String [ Required ];
7+
8+
Property Contents As %Stream.GlobalCharacter(LOCATION = "^SourceControl.Git.DiscardS");
9+
10+
Property Username As %String [ Required ];
11+
12+
Property Branch As %String [ Required ];
13+
14+
Property Timestamp As %TimeStamp [ Required ];
15+
16+
Property ExternalFile As %Boolean [ Required ];
17+
18+
Index BranchMap On Branch [ Type = bitmap ];
19+
20+
Method RestoreToFileTree()
21+
{
22+
// Make sure directory for file exists
23+
set dir = ##class(%File).GetDirectory(..FullExternalName)
24+
if ('##class(%File).DirectoryExists(dir)) {
25+
do ##class(%File).CreateDirectoryChain(dir)
26+
}
27+
28+
// Recreate File
29+
set fileStream = ##class(%Stream.FileCharacter).%New()
30+
set fileStream.Filename = ..FullExternalName
31+
$$$ThrowOnError(fileStream.CopyFrom(..Contents))
32+
$$$ThrowOnError(fileStream.%Save())
33+
34+
// Add file to source-control / IRIS
35+
if '..ExternalFile {
36+
do ##class(SourceControl.Git.Utils).ImportItem(..Name, 1, 1, 1)
37+
do ##class(SourceControl.Git.Utils).AddToServerSideSourceControl(..Name)
38+
}
39+
40+
// Delete discard record
41+
$$$ThrowOnError(..%DeleteId(..%Id()))
42+
}
43+
44+
ClassMethod SaveDiscardState(InternalName As %String, name As %String) As %Status
45+
{
46+
set discardState = ..%New()
47+
48+
if (InternalName = "") {
49+
// If not in IRIS
50+
set externalName = ##class(%File).Construct(##class(SourceControl.Git.Utils).DefaultTempFolder(),name)
51+
set discardState.FullExternalName = externalName
52+
set discardState.Name = name
53+
set discardState.ExternalFile = 1
54+
} else {
55+
set discardState.FullExternalName = ##class(SourceControl.Git.Utils).FullExternalName(InternalName)
56+
set discardState.Name = InternalName
57+
set discardState.ExternalFile = 0
58+
}
59+
// Copy over file contents
60+
set fileStream = ##class(%Stream.FileCharacter).%New()
61+
set fileStream.Filename = discardState.FullExternalName
62+
do fileStream.%Open()
63+
do discardState.Contents.CopyFrom(fileStream)
64+
do fileStream.%Close()
65+
66+
// Save extra information
67+
set discardState.Username = $USERNAME
68+
set discardState.Branch = ##class(SourceControl.Git.Utils).GetCurrentBranch()
69+
set discardState.Timestamp = $zdatetime($horolog, 3)
70+
71+
set st = discardState.%Save()
72+
73+
quit st
74+
}
75+
76+
ClassMethod DiscardStatesInBranch() As %DynamicArray
77+
{
78+
set currentBranch = ##class(SourceControl.Git.Utils).GetCurrentBranch()
79+
80+
set query = "SELECT ID FROM SourceControl_Git.DiscardState WHERE branch = ?"
81+
set statement = ##class(%SQL.Statement).%New()
82+
set status = statement.%Prepare(query, 0)
83+
$$$ThrowOnError(status)
84+
set resultSet = statement.%Execute(currentBranch)
85+
if (resultSet.%SQLCODE < 0) {
86+
throw ##class(%Exception.SQL).CreateFromSQLCODE(resultSet.%SQLCODE,resultSet.%Message)
87+
}
88+
89+
set discardStates = []
90+
while resultSet.%Next() {
91+
set discardState = ..%OpenId(resultSet.ID)
92+
do discardState.%JSONExportToString(.JSONStr)
93+
set discardStateObject = ##class(%DynamicAbstractObject).%FromJSON(JSONStr)
94+
set discardStateObject.Id = resultSet.ID
95+
do discardStates.%Push(discardStateObject)
96+
}
97+
98+
quit discardStates
99+
}
100+
101+
Storage Default
102+
{
103+
<Data name="DiscardStateDefaultData">
104+
<Value name="1">
105+
<Value>%%CLASSNAME</Value>
106+
</Value>
107+
<Value name="2">
108+
<Value>FullExternalName</Value>
109+
</Value>
110+
<Value name="3">
111+
<Value>InternalName</Value>
112+
</Value>
113+
<Value name="4">
114+
<Value>Contents</Value>
115+
</Value>
116+
<Value name="5">
117+
<Value>Username</Value>
118+
</Value>
119+
<Value name="6">
120+
<Value>Branch</Value>
121+
</Value>
122+
<Value name="7">
123+
<Value>Timestamp</Value>
124+
</Value>
125+
<Value name="8">
126+
<Value>Name</Value>
127+
</Value>
128+
<Value name="9">
129+
<Value>ExternalFile</Value>
130+
</Value>
131+
</Data>
132+
<DataLocation>^SourceControl22B9.DiscardStateD</DataLocation>
133+
<DefaultData>DiscardStateDefaultData</DefaultData>
134+
<IdLocation>^SourceControl22B9.DiscardStateD</IdLocation>
135+
<IndexLocation>^SourceControl22B9.DiscardStateI</IndexLocation>
136+
<StreamLocation>^SourceControl22B9.DiscardStateS</StreamLocation>
137+
<Type>%Storage.Persistent</Type>
138+
}
139+
140+
}

cls/SourceControl/Git/File.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,4 @@ Storage Default
6868
<Type>%Storage.Persistent</Type>
6969
}
7070

71-
}
72-
71+
}

cls/SourceControl/Git/Modification.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ Property internalName As %String;
1111
/// Type of change (A|C|D|M|R|T|U|X|B). See git diff documentation.
1212
Property changeType As %String;
1313

14-
}
15-
14+
}

cls/SourceControl/Git/PackageManagerContext.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,4 @@ Method Dump()
5050
write !?4,"Git-enabled? ",$select(..IsInGitEnabledPackage:"Yes",1:"No"),!
5151
}
5252

53-
}
54-
53+
}

cls/SourceControl/Git/PullEventHandler.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,4 @@ ClassMethod ForInternalNames(InternalName As %String) As %Status
4444
quit ..ForModifications(.files)
4545
}
4646

47-
}
48-
47+
}

cls/SourceControl/Git/PullEventHandler/Default.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@ Method OnPull() As %Status
1515
quit ##class(SourceControl.Git.PullEventHandler.IncrementalLoad)$this.OnPull()
1616
}
1717

18-
}
19-
18+
}

cls/SourceControl/Git/PullEventHandler/PackageManager.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ Method OnPull() As %Status
1111
quit ##class(%ZPM.PackageManager).Shell("load "_..LocalRoot)
1212
}
1313

14-
}
15-
14+
}

cls/SourceControl/Git/Util/Buffer.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,4 @@ Method UsePreviousDeviceAndSettings() [ Internal, Private ]
280280
}
281281
}
282282

283-
}
284-
283+
}

cls/SourceControl/Git/Util/ProductionConflictResolver.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,4 @@ ClassMethod ResolveStream(stream As %Stream.Object)
153153
Quit 1
154154
}
155155

156-
}
157-
156+
}

cls/SourceControl/Git/Util/Singleton.cls

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,4 @@ Method %RemoveOref() As %Status [ CodeMode = objectgenerator, Final, Internal, P
166166
Quit $$$OK
167167
}
168168

169-
}
170-
169+
}

cls/SourceControl/Git/Utils.cls

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ ClassMethod Init() As %Status
339339
ClassMethod Revert(InternalName As %String) As %Status
340340
{
341341
set filename = ..FullExternalName(.InternalName)
342+
do ##class(SourceControl.Git.DiscardState).SaveDiscardState(InternalName)
342343
do ..RunGitCommand("checkout", .errStream, .outStream, "--", filename)
343344
$$$QuitOnError(##class(SourceControl.Git.Change).RemoveUncommitted(filename,0,1))
344345
$$$QuitOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1,,1))

cls/SourceControl/Git/WebUIDriver.cls

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
9292
do %request.Set("EXPIRES",0)
9393
do ##class(%CSP.StreamServer).OnPreHTTP() // Need to call this to set headers properly
9494
set %stream = 1 // Leak this to webuidriver.csp
95-
} elseif $match(pathStart,"git-command|git|dirname|hostname|viewonly|contexts") {
95+
} elseif $match(pathStart,"git-command|git|dirname|hostname|viewonly|discarded-states|restore-discarded|contexts") {
9696
if (%request.Method = "GET") {
9797
set %data = ##class(%Stream.TmpCharacter).%New()
9898
// Things not handled from Python backend:
@@ -141,6 +141,10 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
141141
} elseif (pathStart = "dirname") {
142142
do %data.Write(##class(SourceControl.Git.Utils).TempFolder())
143143
set handled = 1
144+
} elseif (pathStart = "discarded-states") {
145+
set discardsInBranch = ##class(SourceControl.Git.DiscardState).DiscardStatesInBranch()
146+
do discardsInBranch.%ToJSON(%data)
147+
set handled = 1
144148
} elseif (pathStart = "contexts") {
145149
set contexts = ##class(SourceControl.Git.Utils).GetContexts()
146150
do contexts.%ToJSON(%data)
@@ -232,6 +236,30 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
232236
set requestBody = ##class(%Library.DynamicObject).%FromJSON(%request.Content)
233237
set command = requestBody.command
234238

239+
set gitCmd = command.%Get(0)
240+
241+
set discardedFiles = []
242+
if gitCmd = "restore" {
243+
set iter = command.%GetIterator()
244+
set isFile = 0
245+
while iter.%GetNext(,.value) {
246+
if isFile {
247+
set internalName = ##class(SourceControl.Git.Utils).NameToInternalName(value)
248+
do ##class(SourceControl.Git.DiscardState).SaveDiscardState(internalName, value)
249+
if (internalName'="") {
250+
set externalName = ##class(SourceControl.Git.Utils).FullExternalName(internalName)
251+
$$$ThrowOnError(##class(SourceControl.Git.Change).RemoveUncommitted(externalName,0,1))
252+
do discardedFiles.%Push(externalName)
253+
}
254+
255+
}
256+
if value = "--"{
257+
set isFile = 1
258+
}
259+
}
260+
}
261+
$$$ThrowOnError(##class(SourceControl.Git.Change).RefreshUncommitted(0,1,,1))
262+
235263
set argsArr = ""
236264
set argsArr($increment(argsArr)) = "color.ui=true"
237265
set iterator = command.%GetIterator()
@@ -262,6 +290,32 @@ ClassMethod HandleRequest(pagePath As %String, InternalName As %String = "", Out
262290
do %data.Write("Git-Return-Code: " _ returnCode) // No ending newline expected
263291
do %data.Rewind()
264292
set handled = 1
293+
294+
// Make sure discarded items are not in the uncommitted queue
295+
set item = discardedFiles.%Pop()
296+
while (item '= "") {
297+
do ##class(SourceControl.Git.Change).RemoveUncommitted(item,0,1)
298+
set item = discardedFiles.%Pop()
299+
}
300+
301+
} elseif (pathStart = "restore-discarded") {
302+
merge data = %request.Data
303+
set fileId = data("file",1)
304+
305+
set %data = ##class(%Stream.TmpCharacter).%New()
306+
307+
set discardState = ##class(SourceControl.Git.DiscardState).%OpenId(fileId)
308+
do ##class(SourceControl.Git.Change).RefreshUncommitted(,,,1)
309+
if ##class(SourceControl.Git.Change).IsUncommitted(discardState.FullExternalName) {
310+
do %data.WriteLine("Please commit changes to file before restoring discarded state")
311+
} else {
312+
do discardState.RestoreToFileTree()
313+
do %data.WriteLine("Successfully restored discarded file state")
314+
}
315+
316+
do %data.Rewind()
317+
do ##class(SourceControl.Git.Change).RefreshUncommitted(,,,1)
318+
set handled = 1
265319
}
266320
}
267321
}

git-webui/release/share/git-webui/webui/css/git-webui.css

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,9 @@ body {
205205
#sidebar #sidebar-content #sidebar-stash h4:before {
206206
content: url(../img/inboxes.svg);
207207
}
208+
#sidebar #sidebar-content #sidebarDiscarded h4:before {
209+
content: url(../img/discarded.svg);
210+
}
208211
#sidebar #sidebar-content #sidebar-remote h4:before {
209212
content: url(../img/daemon.svg);
210213
}
@@ -973,3 +976,52 @@ body {
973976
-webkit-flex: 1 1 0px;
974977
overflow: auto;
975978
}
979+
#discardedView {
980+
width: 100%;
981+
}
982+
#discardedView #discardedList {
983+
height: 100%;
984+
border-right: 1px #dddddd solid;
985+
}
986+
#discardedView #discardedList h4 {
987+
text-align: center;
988+
padding-top: 100px;
989+
}
990+
#discardedView .active {
991+
background-color: rgba(13, 110, 253, 0.9) !important;
992+
color: #fff;
993+
}
994+
#discardedView header {
995+
display: flex;
996+
display: -webkit-flex;
997+
min-height: 0;
998+
min-width: 0;
999+
}
1000+
#discardedView header .file-internalname {
1001+
display: inline-block;
1002+
}
1003+
#discardedView header .discard-date {
1004+
margin-left: auto;
1005+
}
1006+
#discardedView .restore-discarded {
1007+
padding-top: 15px;
1008+
margin-left: 15px;
1009+
margin-right: 15px;
1010+
}
1011+
#discardedView .contents-menu {
1012+
display: flex;
1013+
display: -webkit-flex;
1014+
min-height: 0;
1015+
min-width: 0;
1016+
}
1017+
#discardedView .contents-menu .external-name {
1018+
font-size: 1rem;
1019+
padding: 20px 15px 0 0;
1020+
}
1021+
#discardedView .has-items {
1022+
padding-bottom: 20px;
1023+
border-bottom: 1px solid lightgrey;
1024+
}
1025+
#discardedView .file-contents {
1026+
padding-top: 20px;
1027+
}

0 commit comments

Comments
 (0)