Skip to content

Commit 7e06861

Browse files
committed
Merge branch 'main' into bugfix/exportAll-better-handle-deleted-items
2 parents d366208 + 76ba8fa commit 7e06861

File tree

8 files changed

+227
-11
lines changed

8 files changed

+227
-11
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
- Expanded Baseline Export to include XSL Transforms (#815)
12+
- Enhanced "Check out branch" to first refresh list of remote branches to eliminate failure due to stale information (#823)
1213

1314
### Fixed
1415
- Fix Import All not importing items that do not already exist when compileOnImport is not set (#798)
@@ -17,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1718
- Fixed another instance of deletes showing as owned by undefined user (#812)
1819
- Fix Revert not syncing files with IRIS (#789)
1920
- Fix "Export All" stopping prematurely because a tracked item no longer exist in the namespace (#821)
21+
- Import All now outputs a warning instead of an error when an item is in the wrong path (#291)
22+
2023

2124
## [2.12.2] - 2025-07-08
2225

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ We encourage use of [Conventional Commits](https://www.conventionalcommits.org/e
1414

1515
Generally speaking, just try to match the conventions you see in the code you are reading. For this project, these include:
1616

17-
* Do not use shortened command and function names. For example, use `set` instead of `s` instead of `set` and `$piece` instead of `$p`
17+
* Do not use shortened command and function names. For example, use `set` instead of `s` and `$piece` instead of `$p`
1818
* One command per line
1919
* Do not use dot syntax
2020
* Indentation with tabs

cls/SourceControl/Git/Utils.cls

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -443,13 +443,14 @@ ClassMethod NewBranch(newBranchName As %String) As %Status
443443

444444
ClassMethod SwitchBranch(targetBranchName As %String) As %Status
445445
{
446-
// Make sure that branch exists first (-a lists both local and remote)
447-
do ..RunGitWithArgs(.errStream,.outStream,"branch", "-a")
446+
// First, make sure that branch exists
447+
do ..RunGitCommand("fetch", .errStream, .outStream, "--prune") // refresh list of remote branches
448+
do ..RunGitWithArgs(.errStream, .outStream, "branch", "-a") // -a lists both local and remote
448449
set branches = outStream.Read()
449-
if ('$find(branches,targetBranchName)) {
450+
if ('$FIND(branches, targetBranchName)) {
450451
quit $$$ERROR($$$GeneralError, "Selected branch does not exist"_$C(10))
451452
}
452-
k outStream, errStream
453+
kill outStream, errStream
453454
do ..RunGitWithArgs(.errStream, .outStream, "checkout", targetBranchName)
454455
do ..PrintStreams(errStream, outStream)
455456
// Checkout can fail due to unstaged changes
@@ -1536,7 +1537,7 @@ ClassMethod ListItemsInFiles(ByRef itemList, ByRef err) As %Status
15361537
}
15371538

15381539
if $get(err) > 0 {
1539-
write !, "There were some errors while importing files"
1540+
write !, "Warning: unrecognized files found in the sources directory:"
15401541
for i=1:1:err {
15411542
write !, err(i)
15421543
}
@@ -2819,7 +2820,7 @@ ClassMethod GetSourceControlInclude(prefix As %String = {%request.URLPrefix}) As
28192820

28202821
XData ProductionConfigScript [ MimeType = text/javascript ]
28212822
{
2822-
function checkProductionConfigLoad() {
2823+
function checkProductionConfigLoad() {
28232824
timerState(false);
28242825
}
28252826

docs/menu-items.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# git-source-control Menu Items
22

3-
43
## Status
54
This menu option is analogous to the [git status](https://git-scm.com/docs/git-status) command and prints the status of the repository to the output.
5+
66
## Settings
77
This option opens the GUI's settings page project specific git-source-control settings can be configured. This includes the settings that were configured when running:
88
```
@@ -12,31 +12,49 @@ d ##class(SourceControl.Git.API).Configure()
1212
This page also includes the mappings configurations.
1313

1414
Any changes made to the settings must be saved using the 'Save' button in order to take effect.
15+
1516
## Launch Git UI
1617
This menu option opens the git-source-control GUI. From here commit messages can be written, files can be staged and committed, branches can be viewed.
18+
1719
## Add
1820
This menu option is analogous to the [git add](https://git-scm.com/docs/git-add) command. It will perform 'git add' on the currently open file, adding it to the files that can be staged.
21+
1922
## Remove
2023
This menu option will only appear if the currently open file has been already added using the 'Add' menu option. It undoes the effect of adding the file, similar to running [git reset](https://git-scm.com/docs/git-reset) on a specific file.
24+
2125
## Push to remote branch
2226
This option pushes the commits in the branch to the remote repository. This exhibits the same behavior as the [git push](https://git-scm.com/docs/git-push) command.
27+
2328
## Push to remote branch (force)
2429
This option forcibly pushes the commits in the branch to the remote repository. This is potentially destructive and may overwrite the commit history of the remote branch. This exhibits the same behavior as the [git push --force](https://git-scm.com/docs/git-push) command.
30+
2531
## Fetch from remote
26-
Much like the [git fetch](https://git-scm.com/docs/git-fetch) command, this option fetches the most recent versions of the branch without merging that version into the local copy of the branch.
32+
This option first [fetches](https://git-scm.com/docs/git-fetch) the most recent version of the branch without merging that version into the local copy of the branch. It will then list all files modified between the current version and the remote version.
33+
34+
This also has the effect of refreshing the list of all remote branches and pruning any references that no longer exist in the remote. (see: [git fetch --prune](https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---prune))
35+
2736
## Pull changes from remote branch
2837
Much like the [git pull](https://git-scm.com/docs/git-pull) command, this menu option pulls the most recent version of the current branch from a remote source, merging the changes into the local copy.
38+
2939
## Sync
3040
This option will synchronize a local repo with the remote repo. The sync operation is only enabled in basic mode. It encapsulates the pattern of fetching, pulling, committing and then pushing into one menu action. If there is no defined remote repository, it will simply commit any uncommitted files.
41+
3142
## Create new branch
3243
This menu option creates a new branch in the repository for changes to be committed to. It also changes the current branch to be the created branch. This mimics the behavior of the [git checkout -b](https://git-scm.com/docs/git-checkout) command.
44+
3345
## Check out an existing branch
34-
This option changes the currently checkout branch to a chosen branch. This mimics the behavior of the [git checkout](https://git-scm.com/docs/git-checkout) command.
46+
This option refreshes the local list of branches available in the upstream repository, and then changes the currently checkedout branch to the provided branch. This mimics the behavior of the [git fetch --prune](https://git-scm.com/docs/git-fetch#Documentation/git-fetch.txt---prune) and [git checkout](https://git-scm.com/docs/git-checkout) commands.
47+
48+
If the desired branch does not exist in your local or in the remote, then you will receive the "Selected branch does not exist" error message.
49+
3550
## Export all
3651
This option exports class files to the local file tree at the configured location.
52+
3753
## Export all (force)
3854
This option exports all class files regardless of whether they're already up to date in the local file tree or not.
55+
3956
## Import all
4057
This option imports the versions of the files that are found in the configured directory into the project. Files that are out of date or the same as the files in the project won't be imported.
58+
4159
## Import all (force)
42-
This menu option behaves similarly to the regular import but forces the files to be imported regardless of whether the on-disk version is the same or older.
60+
This menu option behaves similarly to the regular import but forces the files to be imported regardless of whether the on-disk version is the same or older.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Class UnitTest.SourceControl.Git.AbstractTest Extends %UnitTest.TestCase
2+
{
3+
4+
Property InitialExtension As %String [ InitialExpression = {##class(%Studio.SourceControl.Interface).SourceControlClassGet()} ];
5+
6+
Property SourceControlGlobal [ MultiDimensional ];
7+
8+
Method %OnNew(initvalue) As %Status
9+
{
10+
Merge ..SourceControlGlobal = ^SYS("SourceControl")
11+
Kill ^SYS("SourceControl")
12+
Set settings = ##class(SourceControl.Git.Settings).%New()
13+
Set settings.namespaceTemp = ##class(%Library.File).TempFilename()_"dir"
14+
Set settings.Mappings("CLS","*")="cls/"
15+
Do settings.%Save()
16+
Do ##class(%Studio.SourceControl.Interface).SourceControlClassSet("SourceControl.Git.Extension")
17+
Quit ##super(initvalue)
18+
}
19+
20+
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
21+
{
22+
Do ##class(%Studio.SourceControl.Interface).SourceControlClassSet(..InitialExtension)
23+
Kill ^SYS("SourceControl")
24+
Merge ^SYS("SourceControl") = ..SourceControlGlobal
25+
Quit $$$OK
26+
}
27+
28+
ClassMethod WriteFile(filePath, contents)
29+
{
30+
set dirPath = ##class(%File).GetDirectory(filePath)
31+
if '##class(%File).CreateDirectoryChain(dirPath,.ret) {
32+
$$$ThrowStatus($$$ERROR($$$GeneralError,"failed to create directory: "_ret))
33+
}
34+
set fileStream = ##class(%Stream.FileCharacter).%OpenId(filePath,,.sc)
35+
$$$ThrowOnError(sc)
36+
do fileStream.Write(contents)
37+
$$$ThrowOnError(fileStream.%Save())
38+
}
39+
40+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
Class UnitTest.SourceControl.Git.ImportAll Extends %UnitTest.TestCase
2+
{
3+
4+
Property InitialExtension As %String [ InitialExpression = {##class(%Studio.SourceControl.Interface).SourceControlClassGet()} ];
5+
6+
Property SourceControlGlobal [ MultiDimensional ];
7+
8+
Method %OnNew(initvalue) As %Status
9+
{
10+
Merge ..SourceControlGlobal = ^SYS("SourceControl")
11+
Kill ^SYS("SourceControl")
12+
Set settings = ##class(SourceControl.Git.Settings).%New()
13+
Set settings.namespaceTemp = ##class(%Library.File).TempFilename()_"dir"
14+
Set settings.Mappings("MAC","*")="rtn/"
15+
$$$ThrowOnError(settings.%Save())
16+
Do ##class(%Studio.SourceControl.Interface).SourceControlClassSet("SourceControl.Git.Extension")
17+
Quit ##super(initvalue)
18+
}
19+
20+
Method %OnClose() As %Status [ Private, ServerOnly = 1 ]
21+
{
22+
Do ##class(%Studio.SourceControl.Interface).SourceControlClassSet(..InitialExtension)
23+
Kill ^SYS("SourceControl")
24+
Merge ^SYS("SourceControl") = ..SourceControlGlobal
25+
Quit $$$OK
26+
}
27+
28+
Method TestImportAll()
29+
{
30+
do ..CreateTestRoutine()
31+
$$$ThrowOnError(##class(SourceControl.Git.Utils).AddToSourceControl("test.mac"))
32+
do ..CreateStrayFileInRtn()
33+
$$$ThrowOnError(##class(%Routine).Delete("test.mac"))
34+
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportAll(1))
35+
do $$$AssertTrue(##class(%Routine).Exists("test.mac"))
36+
}
37+
38+
Method CreateTestRoutine()
39+
{
40+
if '##class(%Routine).Exists("test.mac") {
41+
set r = ##class(%Routine).%New("test.mac")
42+
do r.WriteLine(" write 22,!")
43+
do r.Save()
44+
do r.Compile()
45+
}
46+
}
47+
48+
/// creates a text file in the routines directory that is not really a routine
49+
Method CreateStrayFileInRtn()
50+
{
51+
set fileStream = ##class(%Stream.FileCharacter).%OpenId(
52+
##class(%File).NormalizeFilename(
53+
"test.txt",
54+
##class(%File).GetDirectory(##class(SourceControl.Git.Utils).FullExternalName("test.mac")))
55+
,,.sc)
56+
$$$ThrowOnError(sc)
57+
$$$ThrowOnError(fileStream.Write("hello world!"))
58+
$$$ThrowOnError(fileStream.%Save())
59+
}
60+
61+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Class UnitTest.SourceControl.Git.Pull Extends UnitTest.SourceControl.Git.AbstractTest
2+
{
3+
4+
Method TestPull()
5+
{
6+
// initialize remote repository on filesystem
7+
set remoteDir = ##class(%Library.File).TempFilename()_"d"
8+
if '##class(%File).CreateDirectoryChain(remoteDir_"/cls",.ret) {
9+
$$$ThrowStatus($$$ERROR($$$GeneralError,"failed to create directory: "_ret))
10+
}
11+
do ..WriteFile(remoteDir_"/cls/TestGit/SampleClass1.cls","Class TestGit.SampleClass1 {}")
12+
do ..WriteFile(remoteDir_"/cls/TestGit/SampleClass2.cls","Class TestGit.SampleClass2 {}")
13+
do $zf(-100,"/SHELL","git","init",remoteDir)
14+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "config", "user.email", "[email protected]")
15+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "config", "user.name", "Unit Test")
16+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "add", ".")
17+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "commit", "-m", "initial commit in remote for unit test")
18+
// initialize local repo, cloning remote.
19+
$$$ThrowOnError(##class(SourceControl.Git.Utils).Clone(remoteDir_"/.git"))
20+
// import all and confirm classes exist
21+
do $System.OBJ.Delete("TestGit.SampleClass1,TestGit.SampleClass2")
22+
$$$ThrowOnError(##class(SourceControl.Git.Utils).ImportAll(1))
23+
do $$$AssertTrue($$$comClassDefined("TestGit.SampleClass1"))
24+
do $$$AssertTrue($$$comClassDefined("TestGit.SampleClass2"))
25+
// delete, add, and modify classes on remote. add and commit them all on remote.
26+
if '##class(%File).Delete(remoteDir_"/cls/TestGit/SampleClass1.cls",.ret) {
27+
$$$ThrowStatus($$$ERROR($$$GeneralError,"failed to delete class file"))
28+
}
29+
do ..WriteFile(remoteDir_"/cls/TestGit/SampleClass2.cls","Class TestGit.SampleClass2 { Parameter foo = ""bar""; }")
30+
do ..WriteFile(remoteDir_"/cls/TestGit/SampleClass3.cls","Class TestGit.SampleClass3 {}")
31+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "add", ".")
32+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "commit", "-m", "delete, modify, and add classes on remote")
33+
// pull on local and confirm changes were loaded.
34+
$$$ThrowOnError(##class(SourceControl.Git.API).Pull())
35+
do $$$AssertNotTrue($$$comClassDefined("TestGit.SampleClass1"))
36+
do $$$AssertEquals(##class(TestGit.SampleClass2).#foo, "bar")
37+
do $$$AssertTrue($$$comClassDefined("TestGit.SampleClass3"))
38+
}
39+
40+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Class UnitTest.SourceControl.Git.Sync Extends UnitTest.SourceControl.Git.AbstractTest
2+
{
3+
4+
Method TestSync()
5+
{
6+
do $$$LogMessage("set up remote repo on filesystem")
7+
set remoteDir = ##class(%Library.File).TempFilename()_"d"
8+
if '##class(%File).CreateDirectoryChain(remoteDir,.ret) {
9+
$$$ThrowStatus($$$ERROR($$$GeneralError,"failed to create directory: "_ret))
10+
}
11+
do $zf(-100,"/SHELL","git","init",remoteDir)
12+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "config", "user.email", "[email protected]")
13+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "config", "user.name", "Unit Test")
14+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "checkout", "-b", "live")
15+
do ..WriteFile(remoteDir_"/cls/TestGit/SampleClass1.cls","Class TestGit.SampleClass1 {}")
16+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "add", ".")
17+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "commit", "-m", "initial commit in remote for unit test")
18+
do $$$LogMessage("initialize local repo cloning remote")
19+
$$$ThrowOnError(##class(SourceControl.Git.Utils).Clone(remoteDir_"/.git"))
20+
do $$$LogMessage("set default merge branch to live and enable basic mode")
21+
set settings = ##class(SourceControl.Git.Settings).%New()
22+
set settings.defaultMergeBranch = "live"
23+
set settings.basicMode = "system"
24+
set settings.systemBasicMode = 1
25+
$$$ThrowOnError(settings.%Save())
26+
do $$$LogMessage("check out live branch on local")
27+
$$$ThrowOnError(##class(SourceControl.Git.Utils).SwitchBranch("live"))
28+
do $$$LogMessage("create a class through IRIS, add it to source control, and sync")
29+
do $System.OBJ.Delete("TestGit.SampleClass2")
30+
set classDef = ##class(%Dictionary.ClassDefinition).%New("TestGit.SampleClass2")
31+
$$$ThrowOnError(classDef.%Save())
32+
$$$ThrowOnError($System.OBJ.Compile("TestGit.SampleClass2"))
33+
$$$ThrowOnError(##class(SourceControl.Git.Utils).AddToSourceControl("TestGit.SampleClass2.cls"))
34+
$$$ThrowOnError(##class(SourceControl.Git.Utils).Sync("should not commit"))
35+
do $$$LogMessage("sync should NOT have committed the new file since we are on the live branch.")
36+
do $$$AssertTrue(##class(SourceControl.Git.Change).IsUncommitted(##class(SourceControl.Git.Utils).FullExternalName("TestGit.SampleClass2.cls")))
37+
do $$$LogMessage("now, check out an interface branch")
38+
$$$ThrowOnError(##class(SourceControl.Git.Utils).NewBranch("interface"))
39+
do $$$LogMessage("simulate another developer's change going live")
40+
do ..WriteFile(remoteDir_"/cls/TestGit/SampleClass1.cls","Class TestGit.SampleClass1 { Parameter foo = ""bar""; }")
41+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "add", ".")
42+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "commit", "-m", "initial commit in remote for unit test")
43+
do $$$LogMessage("check out an interface branch and sync")
44+
$$$ThrowOnError(##class(SourceControl.Git.Utils).Sync("should commit"))
45+
do $$$LogMessage("sync should have rebased the other developer's change, and committed the new file.")
46+
do $$$AssertEquals(##class(TestGit.SampleClass1).#foo, "bar")
47+
do $$$AssertNotTrue(##class(SourceControl.Git.Change).IsUncommitted(##class(SourceControl.Git.Utils).FullExternalName("TestGit.SampleClass2.cls")))
48+
do $$$LogMessage("simulate a merge request on the remote from the interface branch to live.")
49+
do $zf(-100,"/SHELL","git", "-C", remoteDir, "merge", "interface")
50+
do $$$AssertTrue(##class(%File).Exists(remoteDir_"/cls/TestGit/SampleClass2.cls"))
51+
}
52+
53+
}

0 commit comments

Comments
 (0)