private void performExport(final List<ExplorerNode> rootNodePath,
                               final Path dir,
                               final DocRef initialDocRef,
                               final boolean omitAuditFields,
                               final List<Message> messageList) throws IOException {

        final ImportExportActionHandler importExportActionHandler = importExportActionHandlers.getHandler(
                initialDocRef.getType());
        final List<Message> localMessageList = new ArrayList<>();
        if (importExportActionHandler != null) {

            LOGGER.debug("Exporting: " + initialDocRef);
            final Map<String, byte[]> dataMap = importExportActionHandler.exportDocument(initialDocRef,
                    omitAuditFields,
                    localMessageList);
            final DocRef explorerDocRef;
            final ExplorerNode explorerNode;
            final DocRef docRef;
            if (importExportActionHandler instanceof NonExplorerDocRefProvider docRefProvider) {
                // Find the closest DocRef to this one to give a location to export it.
                explorerDocRef = docRefProvider.findNearestExplorerDocRef(initialDocRef);

                if (explorerDocRef == null) {
                    throw new RuntimeException(
                            "Unable to locate suitable location for export, whilst exporting " +
                            initialDocRef);
                }

                final String docRefName = docRefProvider.findNameOfDocRef(initialDocRef);
                docRef = new DocRef(initialDocRef.getType(), initialDocRef.getUuid(), docRefName);

                explorerNode = ExplorerNode
                        .builder()
                        .type(docRef.getType())
                        .uuid(docRef.getUuid())
                        .name(docRefName)
                        .build();
            } else {
                docRef = initialDocRef;
                explorerDocRef = docRef;
                explorerNode = explorerNodeService.getNode(explorerDocRef).orElse(null);
            }

            try {
                // Get the explorer path to this doc ref.
                List<ExplorerNode> path = explorerNodeService.getPath(explorerDocRef);

                // Check if we've specified a non-system root for the export
                // If we have then remove everything down to that root
                if (rootNodePath != null) {
                    path = this.pruneNodePath(rootNodePath, path);
                }

                // Turn the path into a list of strings but ignore any nodes that aren't folders, e.g. the root.
                List<String> pathElements = path.stream()
                        .filter(ExplorerConstants::isFolder)
                        .map(ExplorerNode::getName).toList();

                // Create directories for the path if not already created by another entity.
                final Path parentDir = createDirs(dir, pathElements);

                // Ensure the parent directory exists.
                if (!Files.isDirectory(parentDir)) {
                    // Don't output the full path here are we don't want users to see the full file system path.
                    localMessageList.add(new Message(Severity.FATAL_ERROR,
                            "Unable to create directory for folder: " + parentDir.getFileName()));

                } else {
                    // Write a file for this explorer entry.
                    final String filePrefix = ImportExportFileNameUtil.createFilePrefix(docRef);

                    writeNodeProperties(explorerNode, pathElements, parentDir, filePrefix, localMessageList);

                    // Write out all associated data.
                    dataMap.forEach((k, v) -> {
                        final String fileName = filePrefix + "." + k;
                        try {
                            final OutputStream outputStream = Files.newOutputStream(parentDir.resolve(fileName));
                            outputStream.write(v);
                            // POSIX standard is for all files to end with a line end (\n) so add one if not there
                            if (isMissingLineEndAsLastChar(v)) {
                                outputStream.write(LINE_END_CHAR_BYTES);
                            }
                            outputStream.close();

                        } catch (final IOException e) {
                            localMessageList.add(new Message(
                                    Severity.ERROR,
                                    "Failed to write file '" + fileName + "': "
                                    + LogUtil.exceptionMessage(e)));
                        }
                    });

                    final List<Message> errors = localMessageList.stream()
                            .filter(message -> Severity.FATAL_ERROR.equals(message.getSeverity())
                                               || Severity.ERROR.equals(message.getSeverity()))
                            .toList();

                    if (errors.isEmpty()) {
                        importExportDocumentEventLog.exportDocument(docRef, null);
                    } else {
                        final String errorText = errors.stream()
                                .map(error -> error.getSeverity() + ": " + error.getMessage())
                                .collect(Collectors.joining(", "));

                        importExportDocumentEventLog.exportDocument(docRef, new RuntimeException(errorText));
                    }
                }
            } catch (Exception e) {
                importExportDocumentEventLog.exportDocument(docRef, e);
                localMessageList.add(new Message(
                        Severity.ERROR,
                        "Error exporting directory '"
                        + NullSafe.get(dir, Path::toAbsolutePath, Path::normalize) + "': "
                        + LogUtil.exceptionMessage(e)));
            } finally {
                messageList.addAll(localMessageList);
            }
        }
    }
Prunes the node path for GitRepo exports. Removes everything in the rootPath
from the nodePath if the rootPath matches the start of the nodePath.
@param rootPath Can be null, in which case this function just returns nodePath.
                Otherwise, the list of items to prune from the start of nodePath.
@param nodePath The node path to prune.
@return The pruned nodePath.
stroom-importexport/stroom-importexport-impl/src/main/java/stroom/importexport/impl/ImportExportSerializerImpl.java