code: def _copytree(
      src: Union[Path, str],
      dst: Union[Path, str],
      ignore_functions: Optional[List[_IGNORE_FUNCTION]] = None,
      dirs_exist_ok=False,
      dry_run=False,
  ) -> List[str]:
      """Vendor in from `shutil.copytree` to support ignoring files recursively based on `.lightningignore`, like `git`
      does with `.gitignore`. Also removed a few checks from the original copytree related to symlink checks. Differences
      between original and this function are.
  
      1. It supports a list of ignore function instead of a single one in the
          original. We can use this for filtering out files based on nested
          .lightningignore files
      2. It supports a dry run. When enabled, this function will not copy anything but just recursively
          find the source files which are not-ignored and return them. It is useful while calculating
          the hash or checking the size of files
      3. This function returns a list of copied files unlike the original which was returning the
          destination directory
  
      Recursively copy a directory tree and return the destination directory.
  
      Parameters
      ----------
      src:
          Source directory path to copy from
      dst:
          Destination directory path to copy to
      ignore_functions:
          List of functions that will be used to filter out files
          and directories. This isn't required to be passed when calling from outside but will be
          autopopulated by the recursive calls in this function itself (Original copytree doesn't have this argument)
      dirs_exist_ok:
          If true, the destination directory will be created if it doesn't exist.
      dry_run:
          If true, this function will not copy anything (this is not present in the original copytree)
  
  
      If exception(s) occur, an Error is raised with a list of reasons.
  
      """
      files_copied = []
  
      if ignore_functions is None:
          ignore_functions = []
  
      _ignore_filename_spell_check(src)
      src = Path(src)
      dst = Path(dst)
      ignore_filepath = src / DOT_IGNORE_FILENAME
      if ignore_filepath.is_file():
          patterns = _read_lightningignore(ignore_filepath)
          ignore_fn = partial(_filter_ignored, src, patterns)
          # creating new list so we won't modify the original
          ignore_functions = [*ignore_functions, ignore_fn]
  
      if not dry_run:
          os.makedirs(dst, exist_ok=dirs_exist_ok)
  
      errors = []
  
      entries = list(src.iterdir())
      for fn in ignore_functions:
          # ignore function return only the entries that are not ignored
          entries = fn(src, entries)
  
      for srcentry in entries:
          dstpath = dst / srcentry.name
          try:
              if srcentry.is_dir():
                  _files = _copytree(
                      src=srcentry,
                      dst=dstpath,
                      ignore_functions=ignore_functions,
                      dirs_exist_ok=dirs_exist_ok,
                      dry_run=dry_run,
                  )
                  files_copied.extend(_files)
              else:
                  files_copied.append(str(srcentry))
                  if not dry_run:
                      # Will raise a SpecialFileError for unsupported file types
                      copy2(srcentry, dstpath)
          # catch the Error from the recursive copytree so that we can
          # continue with other files
          except Error as err:
              errors.extend(err.args[0])
          except OSError as why:
              errors.append((srcentry, dstpath, str(why)))
      try:
          if not dry_run:
              copystat(src, dst)
      except OSError as why:
          # Copying file access times may fail on Windows
          if getattr(why, "winerror", None) is None:
              errors.append((src, dst, str(why)))
      if errors:
          raise Error(errors)
      return files_copied
  
file_path: pytorch-lightning/src/lightning/app/source_code/copytree.py
imports: import fnmatch
  import os
  from functools import partial
  from pathlib import Path
  from shutil import Error, copy2, copystat
  from typing import Callable, List, Optional, Set, Tuple, Union
  from lightning.app.core.constants import DOT_IGNORE_FILENAME
  from lightning.app.utilities.app_helpers import Logger
label_desc: Use image node_id 1 for calling a global function with example usage: _copytree(src, dst, ignore_functions, dirs_exist_ok, dry_run) and returns: files_copied