29static const char *
const kShortHelp =
"usage: rootcp [-h] [-c COMPRESS] [--recreate] [-r|--recursive] [--replace] "
30 "[-v|--verbose] SOURCE [SOURCE ...] DEST\n";
32Copy objects from ROOT files into another
39 -h, --help show this help message and exit
40 -c, --compress COMPRESS
41 change the compression settings of the destination file (if not already
43 --recreate recreate the destination file.
44 -r, --recursive recurse inside directories
45 --replace replace object if already existing
47 -vv be even more verbose
49Note: If an object has been written to a file multiple times, rootcp will copy only the latest version of that object.
51Source and destination files accept the syntax: `protocol://path/to/file.root:path/to/object*` to select specific
52subobjects or directories in the file.
55- rootcp source.root dest.root
56 Copy the latest version of each object in 'source.root' to 'dest.root'.
58- rootcp source.root:hist* dest.root
59 Copy all histograms whose names start with 'hist' from 'source.root' to 'dest.root'.
61- rootcp source1.root:hist1 source2.root:hist2 dest.root
62 Copy histograms 'hist1' from 'source1.root' and 'hist2' from 'source2.root' to 'dest.root'.
64- rootcp --recreate source.root:hist dest.root
65 Recreate 'dest.root' and copy the histogram named 'hist' from 'source.root' into it.
67- rootcp -c 101 source.root:hist dest.root
68 Change compression, if not existing, of 'dest.root' to ZLIB algorithm with compression level 1 and copy the histogram named 'hist' from 'source.root' into it.
69 Meaning of the '-c' argument is given by 'compress = 100 * algorithm + level'.
70 Other examples of usage:
71 * -c 509 : ZSTD with compression level 9
72 * -c 404 : LZ4 with compression level 4
73 * -c 207 : LZMA with compression level 7
74 For more information see https://root.cern.ch/doc/latest-stable/classTFile.html#ad0377adf2f3d88da1a1f77256a140d60
75 and https://root.cern.ch/doc/latest-stable/structROOT_1_1RCompressionSetting.html
102 opts.
AddFlag({
"-r",
"--recursive"});
103 opts.
AddFlag({
"-h",
"--help"});
107 opts.
Parse(args, nArgs);
110 std::cerr <<
err <<
"\n";
140static std::unique_ptr<TFile>
OpenFile(
const char *fileName,
const char *mode)
146 Err() <<
"File " << fileName <<
"does not exist.\n";
155struct RootCpDestination {
165static std::pair<std::string_view, std::string_view>
DecomposePath(std::string_view path)
167 auto lastSlashIdx = path.rfind(
'/');
168 if (lastSlashIdx == std::string_view::npos)
171 auto dirName = path.substr(0, lastSlashIdx);
172 auto pathName = path.substr(lastSlashIdx + 1);
173 return {dirName, pathName};
188 const std::string_view srcDirPath =
189 (
node.fParent == 0) ? std::string_view{}
190 : std::string_view{srcFullPath.data(), srcFullPath.size() -
node.fName.size() - 1};
194 std::string destFullPath;
195 std::string_view destDirPath, destBaseName;
196 if (dest.fIsNewObject || dest.fPath.empty()) {
198 destFullPath = dest.fPath.empty() ? srcFullPath : dest.fPath;
200 destDirPath = decomposed.first;
201 destBaseName = decomposed.second;
202 }
else if (!dest.fPath.empty()) {
204 destDirPath = dest.fPath;
205 destFullPath = std::string(destDirPath) +
"/" +
node.fName;
206 destBaseName =
node.fName;
209 if (src.
fFileName == dest.fFname && srcFullPath == destFullPath) {
210 Err() << src.
fFileName <<
":" << srcFullPath <<
": source and destination cannot be the same\n";
214 Info(2) <<
"cp " << src.
fFileName <<
":" << srcFullPath <<
" -> " << dest.fFname <<
":" << destFullPath <<
"\n";
217 if (!destDirPath.empty()) {
218 Info(3) <<
"mkdir " << destDirPath <<
"\n";
219 destDir = dest.fFile->
mkdir(std::string(destDirPath).c_str(),
"",
228 const TKey *destKey = destDir->
GetKey(std::string(destBaseName).c_str());
230 Err() <<
"an object of type '" << destKey->
GetClassName() <<
"' already exists at " << dest.fFname <<
':'
231 << destFullPath <<
". Use the --replace flag to overwrite existing objects.\n";
238 Err() <<
"failed to get source directory '" << srcDirPath <<
"'\n";
243 Err() <<
"failed to read key of object '" << srcFullPath <<
"'\n";
248 const std::string &className =
node.fClassName;
251 Err() <<
"unknown object type: " << className <<
"; object will be skipped.\n";
255 Info(3) <<
"read object \"" << srcFullPath <<
"\" of type " <<
node.fClassName <<
"\n";
257 Err() <<
"failed to create or get destination directory \"" << dest.fFname <<
":" << destDirPath <<
"\"\n";
262 if (destKey && args.fReplace)
263 destDir->
Delete((std::string(destBaseName) +
";*").c_str());
268 if (cl->InheritsFrom(
"TObject")) {
271 Err() <<
"failed to read object \"" << srcFullPath <<
"\".\n";
275 if (
TTree *old =
dynamic_cast<TTree *
>(obj)) {
278 obj = old->CloneTree(-1,
"fast");
279 if (dest.fIsNewObject) {
280 static_cast<TTree *
>(obj)->
SetName(std::string(destBaseName).c_str());
284 }
else if (cl->InheritsFrom(
"TDirectory")) {
286 if (!args.fRecursive) {
287 Warn() <<
"Directory '" << srcFullPath
288 <<
"' will not be copied. Use the -r option if you need a recursive copy.\n";
291 RootCpDestination dest2 = dest;
292 dest2.fPath = dest.fPath + (dest.fPath.empty() ?
"" :
"/") +
node.fName;
293 for (
auto childIdx =
node.fFirstChild; childIdx <
node.fFirstChild +
node.fNChildren; ++childIdx)
294 CopyNode(src, dest2, childIdx, args);
298 destDir->
WriteObject(obj, std::string(destBaseName).c_str());
302 Warn() <<
"object '" <<
node.fName <<
"' of type '" <<
node.fClassName
303 <<
"' will not be copied, as its type is currently unsupported by rootcp.\n";
312 auto args =
ParseArgs(
const_cast<const char **
>(argv) + 1, argc - 1);
325 const auto destFnameAndPattern = args.fSources.back();
326 args.fSources.pop_back();
329 Err() << splitRes.GetError()->GetReport() <<
"\n";
332 auto [destFname, destPath] = splitRes.Unwrap();
335 std::vector<std::pair<std::string_view, std::string_view>> sourcesFileAndPattern;
336 sourcesFileAndPattern.reserve(args.fSources.size());
337 bool srcIsSameAsDstFile =
false;
338 for (
const auto &src : args.fSources) {
341 Err() << res.GetError()->GetReport() <<
"\n";
344 auto fNameAndPattern = res.Unwrap();
345 if (fNameAndPattern.first == destFname) {
346 srcIsSameAsDstFile =
true;
348 sourcesFileAndPattern.push_back(fNameAndPattern);
353 if (args.fRecreate && srcIsSameAsDstFile) {
354 Err() <<
"cannot recreate destination file if this is also a source file\n";
358 if (args.fCompression &&
gSystem->AccessPathName(std::string(destFname).c_str())) {
359 Err() <<
"can't change compression settings on existing file " << destFname <<
"\n";
363 const char *destFileMode =
364 args.fRecreate ?
"RECREATE_WITHOUT_GLOBALREGISTRATION" :
"UPDATE_WITHOUT_GLOBALREGISTRATION";
365 auto destFile =
OpenFile(std::string(destFname).c_str(), destFileMode);
372 TKey *destDirKey =
nullptr;
373 if (!destPath.empty()) {
374 destDirKey = destFile->GetKey(std::string(destPath).c_str());
376 if (!args.fReplace) {
379 Err() <<
"destination path \"" << destFname <<
":" << destPath <<
"\" already exists (as an object of type "
380 << destDirKey->
GetClassName() <<
"). Use the --replace flag to overwrite it.\n";
383 destDirKey =
nullptr;
391 const bool destIsNewObject = !destPath.empty() && !destDirKey;
392 if (destIsNewObject && args.fSources.size() > 1) {
393 Err() <<
"multiple sources were specified, but destination path \"" << destFname <<
":" << destPath
394 <<
"\" is not a directory.\n";
398 if (args.fCompression)
399 destFile->SetCompressionSettings(*args.fCompression);
403 for (
const auto &[srcFname, srcPattern] : sourcesFileAndPattern) {
405 if (!src.fErrors.empty()) {
406 for (
const auto &
err : src.fErrors)
414 assert(!
gROOT->GetListOfFiles()->Contains(src.fObjectTree.fFile.get()));
417 if (destIsNewObject && src.fObjectTree.fLeafList.size() + src.fObjectTree.fDirList.size() > 1) {
418 Err() <<
"multiple sources were specified but destination path \"" << destFname <<
":" << destPath
419 <<
"\" is not a directory.\n";
426 RootCpDestination dest;
427 dest.fFile = destFile.get();
428 dest.fFname = destFname;
429 dest.fIsNewObject = destIsNewObject;
430 dest.fPath = destPath;
431 for (
auto nodeIdx : src.fObjectTree.fLeafList) {
434 for (
auto nodeIdx : src.fObjectTree.fDirList) {
437 const auto &
node = src.fObjectTree.fNodes[nodeIdx];
438 for (
auto childIdx =
node.fFirstChild; childIdx <
node.fFirstChild +
node.fNChildren; ++childIdx)
439 CopyNode(src, dest, childIdx, args);
446 if (
errors && !srcIsSameAsDstFile) {
448 gSystem->Unlink(std::string(destFname).c_str());
void Info(const char *location, const char *msgfmt,...)
Use this function for informational messages.
externInt_t gErrorIgnoreLevel
errors with level below this value will be ignored. Default is kUnset.
std::vector< double > errors
void AddFlag(std::initializer_list< std::string_view > aliases, EFlagType type=EFlagType::kSwitch, std::string_view help="", std::uint32_t flagOpts=0)
Defines a new flag (either a switch or a flag with argument).
const std::vector< std::string > & GetErrors() const
Returns all parsing errors.
int GetSwitch(std::string_view name) const
If name refers to a previously-defined switch (i.e.
const std::vector< std::string > & GetArgs() const
Retrieves all positional arguments.
void Parse(const char **args, std::size_t nArgs)
std::optional< T > GetFlagValueAs(std::string_view name) const
TClass instances represent classes, structs and namespaces in the ROOT type system.
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
TDirectory * mkdir(const char *name, const char *title="", Bool_t returnExistingDirectory=kFALSE) override
Create a sub-directory "a" or a hierarchy of sub-directories "a/b/c/...".
TDirectory * GetDirectory(const char *apath, Bool_t printError=false, const char *funcname="GetDirectory") override
Find a directory named "apath".
TDirectory::TContext keeps track and restore the current directory.
Describe directory structure in memory.
void Delete(const char *namecycle="") override
Delete Objects or/and keys in a directory.
std::enable_if_t<!std::is_base_of< TObject, T >::value, Int_t > WriteObject(const T *obj, const char *name, Option_t *option="", Int_t bufsize=0)
Write an object with proper type checking.
virtual TKey * GetKey(const char *, Short_t=9999) const
virtual TDirectory * mkdir(const char *name, const char *title="", Bool_t returnExistingDirectory=kFALSE)
Create a sub-directory "a" or a hierarchy of sub-directories "a/b/c/...".
A file, usually with extension .root, that stores data and code in the form of serialized objects in ...
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
Book space in a file, create I/O buffers, to fill them, (un)compress them.
const char * GetTitle() const override
Returns title (title can contain 32x32 xpm thumbnail/icon).
virtual const char * GetClassName() const
Mother of all ROOT objects.
A TTree represents a columnar dataset.
subroutine node(ivo, nuserm, iposp)
void SetLogVerbosity(int verbosity)
void InitLog(const char *name, int defaultVerbosity=1)
ROOT::RResult< std::pair< std::string_view, std::string_view > > SplitIntoFileNameAndPattern(std::string_view sourceRaw)
Given a string like "root://file.root:a/b/c", splits it into { "root://file.root",...
std::string NodeFullPath(const RootObjTree &tree, NodeIdx_t nodeIdx, ENodeFullPathOpt opt)
Given a node, returns its full path. If opt == kIncludeFilename, the path is prepended by "filename....
@ kRecursive
Recurse into subdirectories when matching objects.
RootSource GetMatchingPathsInFile(std::string_view fileName, std::string_view pattern, std::uint32_t flags)
Given a file and a "path pattern", returns a RootSource containing the tree of matched objects.
static const char *const kShortHelp
Command line tool to open a ROOT file on a TBrowser.
static const char *const kLongHelp
static std::unique_ptr< TFile > OpenFile(const char *fileName, const char *mode)
static RootCpArgs ParseArgs(const char **args, int nArgs)
static std::pair< std::string_view, std::string_view > DecomposePath(std::string_view path)
static void CopyNode(const RootSource &src, const RootCpDestination &dest, NodeIdx_t nodeIdx, const RootCpArgs &args)
std::unique_ptr< TFile > fFile
std::vector< RootObjNode > fNodes
std::optional< int > fCompression
std::vector< std::string > fSources