From 506f726f447b455d90fa590f83c7a0feb9e68509 Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:49:27 -0500 Subject: [PATCH 1/2] enh: source control menu has option to take ownership of uncommitted changes to a file --- CHANGELOG.md | 1 + cls/SourceControl/Git/Extension.cls | 12 +++++++++++- cls/SourceControl/Git/Utils.cls | 27 +++++++++++++++++++++++++++ docs/menu-items.md | 3 +++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 908f36e5..80438eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Baseline Export now includes decomposed production items when production decomposition is enabled (#680) +- Take Ownership option in source control menu allows you to take ownership of an item edited by another user (#926) ## Fixed - Fixed issue where Generated Files Read-only option didn't entirely work for files not in source control (#712) diff --git a/cls/SourceControl/Git/Extension.cls b/cls/SourceControl/Git/Extension.cls index 565deac2..07db8e39 100644 --- a/cls/SourceControl/Git/Extension.cls +++ b/cls/SourceControl/Git/Extension.cls @@ -35,12 +35,14 @@ XData Menu +
} @@ -150,6 +152,7 @@ Method LocalizeName(name As %String) As %String "SwitchBranch":$$$Text("@SwitchBranch@Check out an existing branch"), "Revert":$$$Text("@Revert@Discard changes to file"), "Commit":$$$Text("@Commit@Commit changes to file"), + "TakeOwnership":$$$Text("@TakeOwnership@Take ownership of changes to file"), "Sync":$$$Text("@Sync@Sync"), "Push":$$$Text("@Push@Push to remote branch"), "PushForce":$$$Text("@PushForce@Push to remote branch (Force)"), @@ -172,7 +175,7 @@ Method OnSourceMenuItem(name As %String, ByRef Enabled As %String, ByRef Display } if ##class(SourceControl.Git.Utils).IsNamespaceInGit() { - if $listfind($listbuild("AddToSC", "RemoveFromSC", "Revert", "Commit", "ExportProduction"), name) { + if $listfind($listbuild("AddToSC", "RemoveFromSC", "Revert", "Commit", "ExportProduction", "TakeOwnership"), name) { quit ..OnSourceMenuContextItem(InternalName,name,.Enabled,.DisplayName) } @@ -260,6 +263,13 @@ Method OnSourceMenuContextItem(itemName As %String, menuItemName As %String, ByR if '(##class(SourceControl.Git.Change).IsUncommitted(##class(SourceControl.Git.Utils).FullExternalName(itemName))) || ($username '= userCheckedOut) { set Enabled = 0 } + } elseif menuItemName = "TakeOwnership" { + set Enabled = -1 // Hide by default + do ..GetStatus(.itemName, .isInSourceControl, .isEditable,.isCheckedOut,.userCheckedOut) + // Show only if file is checked out by a different user + if isCheckedOut && (userCheckedOut '= $username) { + set Enabled = 1 + } } elseif menuItemName = "ExportProduction" { set itemNameNoExt = $piece(itemName,".",1,*-1) set Enabled = (##class(SourceControl.Git.Production).IsProductionClass(itemNameNoExt,"FullExternalName")) diff --git a/cls/SourceControl/Git/Utils.cls b/cls/SourceControl/Git/Utils.cls index 0e82f223..5aac8323 100644 --- a/cls/SourceControl/Git/Utils.cls +++ b/cls/SourceControl/Git/Utils.cls @@ -339,6 +339,14 @@ ClassMethod UserAction(InternalName As %String, MenuName As %String, ByRef Targe set ec = ..AddToSourceControl(InternalName) } elseif (menuItemName = "RemoveFromSC") { set ec = ..RemoveFromSourceControl(InternalName) + } elseif (menuItemName = "TakeOwnership") { + set filename = ..FullExternalName(InternalName) + set sc = ##class(SourceControl.Git.Change).GetUncommitted(filename, .tAction, .tInternalName, .UncommittedUser, .tSource, .UncommittedLastUpdated) + $$$QuitOnError(sc) + if $get(UncommittedUser) = "" set UncomittedUser = "unknown user" + set Target = "File '"_InternalName_"' has uncommitted changes by '"_UncommittedUser_"'. Do you want to take ownership of these changes?" + set Action = 1 // Show Yes/No confirmation dialog + quit $$$OK } elseif (menuItemName = "Status") { do ..RunGitCommand("status", .errStream, .outStream) write !, !, "Git Status: " @@ -382,6 +390,11 @@ ClassMethod AfterUserAction(Type As %Integer, Name As %String, InternalName As % do ..Push(,1) set Reload = 1 } + } elseif (menuItemName = "TakeOwnership") { + if (Answer = 1) { + set status = ..TakeOwnership(InternalName) + set Reload = 1 + } } elseif (menuItemName = "GitWebUI") { // Always force reload as many things could have possibly changed. set Reload = 1 @@ -424,6 +437,20 @@ ClassMethod Commit(InternalName As %String, Message As %String = "example commit quit $$$OK } +ClassMethod TakeOwnership(InternalName As %String) As %Status +{ + set sc = $$$OK + try { + &sql(update SourceControl_Git.Change set ChangedBy = $username where InternalName = :InternalName) + $$$ThrowSQLIfError(SQLCODE,.%msg) + write !, "Ownership of '"_InternalName_"' transferred to '"_$username_"'" + } catch ex { + set sc = ex.AsStatus() + do $System.OBJ.DisplayError(sc) + } + quit sc +} + ///This function performs git checkout -b [newBranchName] from the current commit.
If the user is in "Basic Mode" AND there is a Default Merge Branch defined, /// then this method first checks out and pulls that default merge branch before creating the new branch. This is diff --git a/docs/menu-items.md b/docs/menu-items.md index 91c7a50c..9b858c6d 100644 --- a/docs/menu-items.md +++ b/docs/menu-items.md @@ -84,3 +84,6 @@ This option imports the versions of the files that are found in the configured d ## Import All (Force) 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. + +## Take Ownership of Changes to File +This option is enabled if the current item has uncommitted changes owned by a different user. Take Ownership will transfer ownership of those uncommitted changes to the current user, allowing the current user to make their own edits. From 71d10ffdc16851ec8c7cd19af293c07548374b41 Mon Sep 17 00:00:00 2001 From: Pravin Barton <9560941+isc-pbarton@users.noreply.github.com> Date: Tue, 17 Feb 2026 17:10:26 -0500 Subject: [PATCH 2/2] test: unit test for take ownership --- .../SourceControl/Git/Utils/TakeOwnership.cls | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/UnitTest/SourceControl/Git/Utils/TakeOwnership.cls diff --git a/test/UnitTest/SourceControl/Git/Utils/TakeOwnership.cls b/test/UnitTest/SourceControl/Git/Utils/TakeOwnership.cls new file mode 100644 index 00000000..69325b42 --- /dev/null +++ b/test/UnitTest/SourceControl/Git/Utils/TakeOwnership.cls @@ -0,0 +1,28 @@ +Class UnitTest.SourceControl.Git.Utils.TakeOwnership Extends UnitTest.SourceControl.Git.AbstractTest +{ + +Method TestTakeOwnership() +{ + set internalName = "Test.Package.TestClass.cls" + + &sql(INSERT INTO SourceControl_Git.Change (InternalName, ChangedBy, ItemFile, Action, Committed) + VALUES (:internalName, 'OtherUser', 'cls/Test/Package/TestClass.cls', 'edit', 0)) + $$$ThrowSQLIfError(SQLCODE,.%msg) + + try { + // Call TakeOwnership and verify it succeeds + do $$$AssertStatusOK(##class(SourceControl.Git.Utils).TakeOwnership(internalName)) + + // Verify ChangedBy was updated + &sql(SELECT ChangedBy INTO :changedBy FROM SourceControl_Git.Change WHERE InternalName = :internalName) + $$$ThrowSQLIfError(SQLCODE,.%msg) + do $$$AssertEquals(changedBy, $username) + } catch ex { + do $$$AssertStatusOK(ex.AsStatus()) + } + + // Clean up + &sql(DELETE FROM SourceControl_Git.Change WHERE InternalName = :internalName) +} + +}