From 97e1291ffddad778052fec5258eeec6954e565ad Mon Sep 17 00:00:00 2001 From: Ahmed Bilal Date: Tue, 25 Nov 2025 00:36:31 +0500 Subject: [PATCH 1/3] [IO] Fix rootcp --replace flag not working with recursive copy When copying non-tree objects recursively, the --replace flag was checking for 'setName' instead of the actual object name that would be written. This caused objects to be written with new cycles instead of replacing existing ones. The fix determines the actual name (setName if provided, otherwise objectName) and uses that for the replacement check. Fixes #7052 --- main/python/cmdLineUtils.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/main/python/cmdLineUtils.py b/main/python/cmdLineUtils.py index f3af283b197db..795b77231f799 100644 --- a/main/python/cmdLineUtils.py +++ b/main/python/cmdLineUtils.py @@ -692,19 +692,18 @@ def copyRootObjectRecursive(sourceFile, sourcePathSplit, destFile, destPathSplit newT.Write() else: obj = key.ReadObj() - if replaceOption and isExisting(destFile, destPathSplit + [setName]): - changeDirectory(destFile, destPathSplit) - otherObj = getFromDirectory(setName) - retcodeTemp = deleteObject(destFile, destPathSplit + [setName]) + # Determine the actual name that will be written + actualName = setName if setName != "" else objectName + + # Delete existing object if replace option is enabled + if replaceOption and isExisting(destFile, destPathSplit + [actualName]): + retcodeTemp = deleteObject(destFile, destPathSplit + [actualName]) if retcodeTemp: retcode += retcodeTemp + obj.Delete() continue - else: - if isinstance(obj, ROOT.TNamed): - obj.SetName(setName) - changeDirectory(destFile, destPathSplit) - obj.Write() - elif issubclass(obj.__class__, ROOT.TCollection): + + if issubclass(obj.__class__, ROOT.TCollection): # probably the object was written with kSingleKey changeDirectory(destFile, destPathSplit) obj.Write(setName, ROOT.TObject.kSingleKey) From f387a14daa2f7d9a9f436ab5aa77f9b4f5b20cf5 Mon Sep 17 00:00:00 2001 From: Ahmed Bilal Date: Tue, 25 Nov 2025 17:06:45 +0500 Subject: [PATCH 2/3] Fix ruff formatting: remove trailing whitespace --- main/python/cmdLineUtils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/python/cmdLineUtils.py b/main/python/cmdLineUtils.py index 795b77231f799..2c5e1adf5c9f9 100644 --- a/main/python/cmdLineUtils.py +++ b/main/python/cmdLineUtils.py @@ -694,7 +694,7 @@ def copyRootObjectRecursive(sourceFile, sourcePathSplit, destFile, destPathSplit obj = key.ReadObj() # Determine the actual name that will be written actualName = setName if setName != "" else objectName - + # Delete existing object if replace option is enabled if replaceOption and isExisting(destFile, destPathSplit + [actualName]): retcodeTemp = deleteObject(destFile, destPathSplit + [actualName]) @@ -702,7 +702,7 @@ def copyRootObjectRecursive(sourceFile, sourcePathSplit, destFile, destPathSplit retcode += retcodeTemp obj.Delete() continue - + if issubclass(obj.__class__, ROOT.TCollection): # probably the object was written with kSingleKey changeDirectory(destFile, destPathSplit) From d432681dbf979bdc12208dc41009aa9c46d4b206 Mon Sep 17 00:00:00 2001 From: Ahmed Bilal Date: Mon, 1 Dec 2025 13:17:55 +0500 Subject: [PATCH 3/3] Add test for rootcp --replace with recursive copy This test verifies that the --replace flag works correctly when copying objects recursively. It ensures objects are replaced (cycle 1) rather than creating new cycles when re-copying with --replace -r. This test will help ensure the C++ implementation doesn't have the same issue when rootcp.py is replaced. --- roottest/main/CMakeLists.txt | 24 ++++++++++++++++++++---- roottest/main/RootcpReplaceRecursive.ref | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 roottest/main/RootcpReplaceRecursive.ref diff --git a/roottest/main/CMakeLists.txt b/roottest/main/CMakeLists.txt index 8b81e9a5da861..6cfead42b8a9a 100644 --- a/roottest/main/CMakeLists.txt +++ b/roottest/main/CMakeLists.txt @@ -368,11 +368,27 @@ ROOTTEST_ADD_TEST(SimpleRootcp5CheckOutput ROOTTEST_ADD_TEST(SimpleRootcp5Clean COMMAND ${PY_TOOLS_PREFIX}/rootrm${pyext} -r copy5.root FIXTURES_REQUIRED main-SimpleRootcp5CheckOutput-fixture) -######################################################################### -############################# ROOMV TESTS ############################ -ROOTTEST_ADD_TEST(SimpleRootmv1PrepareInput - COMMAND ${PY_TOOLS_PREFIX}/rootcp${pyext} --recreate -r test.root move1.root + +ROOTTEST_ADD_TEST(RootcpReplaceRecursivePrepareInput + COMMAND ${PY_TOOLS_PREFIX}/rootcp${pyext} --recreate -r test.root copy_replace_recursive.root + FIXTURES_SETUP main-RootcpReplaceRecursivePrepareInput-fixture) + +ROOTTEST_ADD_TEST(RootcpReplaceRecursive + COMMAND ${PY_TOOLS_PREFIX}/rootcp${pyext} --replace -r copy_replace_recursive.root:tof copy_replace_recursive.root:tof + FIXTURES_REQUIRED main-RootcpReplaceRecursivePrepareInput-fixture + FIXTURES_SETUP main-RootcpReplaceRecursive-fixture) + +ROOTTEST_ADD_TEST(RootcpReplaceRecursiveCheckOutput + COMMAND ${TOOLS_PREFIX}/rootls${exeext} -1 copy_replace_recursive.root:tof + OUTREF RootcpReplaceRecursive.ref + FIXTURES_REQUIRED main-RootcpReplaceRecursive-fixture + FIXTURES_SETUP main-RootcpReplaceRecursiveCheckOutput-fixture + ENVIRONMENT ${test_env}) + +ROOTTEST_ADD_TEST(RootcpReplaceRecursiveClean + COMMAND ${PY_TOOLS_PREFIX}/rootrm${pyext} -r copy_replace_recursive.root + FIXTURES_REQUIRED main-RootcpReplaceRecursiveCheckOutput-fixture) FIXTURES_SETUP main-SimpleRootmv1PrepareInput-fixture) ROOTTEST_ADD_TEST(SimpleRootmv1 diff --git a/roottest/main/RootcpReplaceRecursive.ref b/roottest/main/RootcpReplaceRecursive.ref new file mode 100644 index 0000000000000..459126e912aeb --- /dev/null +++ b/roottest/main/RootcpReplaceRecursive.ref @@ -0,0 +1,22 @@ +h0_0N +h0_1N +h0_2N +h0_3N +h0_4N +h0_5N +h0_6N +h0_7N +h0_8N +h0_9N +h1_0N +h1_1N +h1_2N +h1_3N +h1_4N +h1_5N +h1_6N +h1_7N +h1_8N +h1_9N +plane0 +plane1