prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Cleanup exception that are not logged as error
  After #4495 got merged @agjohnson suggested to have an attribute in the Exception class and check for that attribute before log the exception, instead of defining a list for the warning exceptions as I did at:
  
  https://github.com/rtfd/readthedocs.org/pull/4495/files#diff-ca52b098301dd315a834b3556ab9a7d5R424
  
  Also, there are more exceptions that have to treat in the same way: `ProjectConfigurationError` for example.
  
  https://sentry.io/read-the-docs/readthedocs-org/issues/668248681/
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `readthedocs/vcs_support/base.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 
  3 """Base classes for VCS backends."""
  4 import logging
  5 import os
  6 import shutil
  7 
  8 
  9 log = logging.getLogger(__name__)
  10 
  11 
  12 class VCSVersion:
  13 
  14     """
  15     Represents a Version (tag or branch) in a VCS.
  16 
  17     This class should only be instantiated in BaseVCS subclasses.
  18 
  19     It can act as a context manager to temporarily switch to this tag (eg to
  20     build docs for this tag).
  21     """
  22 
  23     def __init__(self, repository, identifier, verbose_name):
  24         self.repository = repository
  25         self.identifier = identifier
  26         self.verbose_name = verbose_name
  27 
  28     def __repr__(self):
  29         return '<VCSVersion: {}:{}'.format(
  30             self.repository.repo_url,
  31             self.verbose_name,
  32         )
  33 
  34 
  35 class BaseVCS:
  36 
  37     """
  38     Base for VCS Classes.
  39 
  40     VCS commands are ran inside a ``LocalEnvironment``.
  41     """
  42 
  43     supports_tags = False  # Whether this VCS supports tags or not.
  44     supports_branches = False  # Whether this VCS supports branches or not.
  45     supports_submodules = False
  46 
  47     # =========================================================================
  48     # General methods
  49     # =========================================================================
  50 
  51     # Defining a base API, so we'll have unused args
  52     # pylint: disable=unused-argument
  53     def __init__(
  54             self, project, version_slug, environment=None,
  55             verbose_name=None, version_type=None, **kwargs
  56     ):
  57         self.default_branch = project.default_branch
  58         self.project = project
  59         self.name = project.name
  60         self.repo_url = project.clean_repo
  61         self.working_dir = project.checkout_path(version_slug)
  62         # required for External versions
  63         self.verbose_name = verbose_name
  64         self.version_type = version_type
  65 
  66         from readthedocs.doc_builder.environments import LocalEnvironment
  67         self.environment = environment or LocalEnvironment(project)
  68 
  69         # Update the env variables with the proper VCS env variables
  70         self.environment.environment.update(self.env)
  71 
  72     def check_working_dir(self):
  73         if not os.path.exists(self.working_dir):
  74             os.makedirs(self.working_dir)
  75 
  76     def make_clean_working_dir(self):
  77         """Ensures that the working dir exists and is empty."""
  78         shutil.rmtree(self.working_dir, ignore_errors=True)
  79         self.check_working_dir()
  80 
  81     @property
  82     def env(self):
  83         environment = os.environ.copy()
  84 
  85         # TODO: kind of a hack
  86         del environment['PATH']
  87 
  88         return environment
  89 
  90     def update(self):
  91         """
  92         Update a local copy of the repository in self.working_dir.
  93 
  94         If self.working_dir is already a valid local copy of the repository,
  95         update the repository, else create a new local copy of the repository.
  96         """
  97         self.check_working_dir()
  98 
  99     def run(self, *cmd, **kwargs):
  100         kwargs.update({
  101             'cwd': self.working_dir,
  102             'shell': False,
  103         })
  104 
  105         build_cmd = self.environment.run(*cmd, **kwargs)
  106         # Return a tuple to keep compatibility
  107         return (build_cmd.exit_code, build_cmd.output, build_cmd.error)
  108 
  109     # =========================================================================
  110     # Tag / Branch related methods
  111     # These methods only apply if supports_tags = True and/or
  112     # support_branches = True
  113     # =========================================================================
  114 
  115     @property
  116     def tags(self):
  117         """
  118         Returns a list of VCSVersion objects.
  119 
  120         See VCSVersion for more information.
  121         """
  122         raise NotImplementedError
  123 
  124     @property
  125     def branches(self):
  126         """
  127         Returns a list of VCSVersion objects.
  128 
  129         See VCSVersion for more information.
  130         """
  131         raise NotImplementedError
  132 
  133     @property
  134     def commit(self):
  135         """Returns a string representing the current commit."""
  136         raise NotImplementedError
  137 
  138     def checkout(self, identifier=None):
  139         """
  140         Set the state to the given identifier.
  141 
  142         If identifier is None, checkout to the latest revision.
  143 
  144         The type and format of identifier may change from VCS to VCS, so each
  145         backend is responsible to understand it's identifiers.
  146         """
  147         self.check_working_dir()
  148 
  149     def update_submodules(self, config):
  150         """
  151         Update the submodules of the current checkout.
  152 
  153         :type config: readthedocs.config.BuildConfigBase
  154         """
  155         raise NotImplementedError
  156
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py
  --- a/readthedocs/vcs_support/base.py
  +++ b/readthedocs/vcs_support/base.py
  @@ -1,10 +1,11 @@
  -# -*- coding: utf-8 -*-
  -
   """Base classes for VCS backends."""
   import logging
   import os
   import shutil
   
  +from readthedocs.doc_builder.exceptions import BuildEnvironmentWarning
  +from readthedocs.projects.exceptions import RepositoryError
  +
   
   log = logging.getLogger(__name__)
   
  @@ -102,7 +103,13 @@
               'shell': False,
           })
   
  -        build_cmd = self.environment.run(*cmd, **kwargs)
  +        try:
  +            build_cmd = self.environment.run(*cmd, **kwargs)
  +        except BuildEnvironmentWarning as e:
  +            # Re raise as RepositoryError,
  +            # so isn't logged as ERROR.
  +            raise RepositoryError(str(e))
  +
           # Return a tuple to keep compatibility
           return (build_cmd.exit_code, build_cmd.output, build_cmd.error)
  
verification_info: {"golden_diff": "diff --git a/readthedocs/vcs_support/base.py b/readthedocs/vcs_support/base.py\n--- a/readthedocs/vcs_support/base.py\n+++ b/readthedocs/vcs_support/base.py\n@@ -1,10 +1,11 @@\n-# -*- coding: utf-8 -*-\n-\n \"\"\"Base classes for VCS backends.\"\"\"\n import logging\n import os\n import shutil\n \n+from readthedocs.doc_builder.exceptions import BuildEnvironmentWarning\n+from readthedocs.projects.exceptions import RepositoryError\n+\n \n log = logging.getLogger(__name__)\n \n@@ -102,7 +103,13 @@\n             'shell': False,\n         })\n \n-        build_cmd = self.environment.run(*cmd, **kwargs)\n+        try:\n+            build_cmd = self.environment.run(*cmd, **kwargs)\n+        except BuildEnvironmentWarning as e:\n+            # Re raise as RepositoryError,\n+            # so isn't logged as ERROR.\n+            raise RepositoryError(str(e))\n+\n         # Return a tuple to keep compatibility\n         return (build_cmd.exit_code, build_cmd.output, build_cmd.error)\n", "issue": "Cleanup exception that are not logged as error\nAfter #4495 got merged @agjohnson suggested to have an attribute in the Exception class and check for that attribute before log the exception, instead of defining a list for the warning exceptions as I did at:\r\n\r\nhttps://github.com/rtfd/readthedocs.org/pull/4495/files#diff-ca52b098301dd315a834b3556ab9a7d5R424\r\n\r\nAlso, there are more exceptions that have to treat in the same way: `ProjectConfigurationError` for example.\r\n\r\nhttps://sentry.io/read-the-docs/readthedocs-org/issues/668248681/\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"Base classes for VCS backends.\"\"\"\nimport logging\nimport os\nimport shutil\n\n\nlog = logging.getLogger(__name__)\n\n\nclass VCSVersion:\n\n    \"\"\"\n    Represents a Version (tag or branch) in a VCS.\n\n    This class should only be instantiated in BaseVCS subclasses.\n\n    It can act as a context manager to temporarily switch to this tag (eg to\n    build docs for this tag).\n    \"\"\"\n\n    def __init__(self, repository, identifier, verbose_name):\n        self.repository = repository\n        self.identifier = identifier\n        self.verbose_name = verbose_name\n\n    def __repr__(self):\n        return '<VCSVersion: {}:{}'.format(\n            self.repository.repo_url,\n            self.verbose_name,\n        )\n\n\nclass BaseVCS:\n\n    \"\"\"\n    Base for VCS Classes.\n\n    VCS commands are ran inside a ``LocalEnvironment``.\n    \"\"\"\n\n    supports_tags = False  # Whether this VCS supports tags or not.\n    supports_branches = False  # Whether this VCS supports branches or not.\n    supports_submodules = False\n\n    # =========================================================================\n    # General methods\n    # =========================================================================\n\n    # Defining a base API, so we'll have unused args\n    # pylint: disable=unused-argument\n    def __init__(\n            self, project, version_slug, environment=None,\n            verbose_name=None, version_type=None, **kwargs\n    ):\n        self.default_branch = project.default_branch\n        self.project = project\n        self.name = project.name\n        self.repo_url = project.clean_repo\n        self.working_dir = project.checkout_path(version_slug)\n        # required for External versions\n        self.verbose_name = verbose_name\n        self.version_type = version_type\n\n        from readthedocs.doc_builder.environments import LocalEnvironment\n        self.environment = environment or LocalEnvironment(project)\n\n        # Update the env variables with the proper VCS env variables\n        self.environment.environment.update(self.env)\n\n    def check_working_dir(self):\n        if not os.path.exists(self.working_dir):\n            os.makedirs(self.working_dir)\n\n    def make_clean_working_dir(self):\n        \"\"\"Ensures that the working dir exists and is empty.\"\"\"\n        shutil.rmtree(self.working_dir, ignore_errors=True)\n        self.check_working_dir()\n\n    @property\n    def env(self):\n        environment = os.environ.copy()\n\n        # TODO: kind of a hack\n        del environment['PATH']\n\n        return environment\n\n    def update(self):\n        \"\"\"\n        Update a local copy of the repository in self.working_dir.\n\n        If self.working_dir is already a valid local copy of the repository,\n        update the repository, else create a new local copy of the repository.\n        \"\"\"\n        self.check_working_dir()\n\n    def run(self, *cmd, **kwargs):\n        kwargs.update({\n            'cwd': self.working_dir,\n            'shell': False,\n        })\n\n        build_cmd = self.environment.run(*cmd, **kwargs)\n        # Return a tuple to keep compatibility\n        return (build_cmd.exit_code, build_cmd.output, build_cmd.error)\n\n    # =========================================================================\n    # Tag / Branch related methods\n    # These methods only apply if supports_tags = True and/or\n    # support_branches = True\n    # =========================================================================\n\n    @property\n    def tags(self):\n        \"\"\"\n        Returns a list of VCSVersion objects.\n\n        See VCSVersion for more information.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    def branches(self):\n        \"\"\"\n        Returns a list of VCSVersion objects.\n\n        See VCSVersion for more information.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    def commit(self):\n        \"\"\"Returns a string representing the current commit.\"\"\"\n        raise NotImplementedError\n\n    def checkout(self, identifier=None):\n        \"\"\"\n        Set the state to the given identifier.\n\n        If identifier is None, checkout to the latest revision.\n\n        The type and format of identifier may change from VCS to VCS, so each\n        backend is responsible to understand it's identifiers.\n        \"\"\"\n        self.check_working_dir()\n\n    def update_submodules(self, config):\n        \"\"\"\n        Update the submodules of the current checkout.\n\n        :type config: readthedocs.config.BuildConfigBase\n        \"\"\"\n        raise NotImplementedError\n", "path": "readthedocs/vcs_support/base.py"}], "after_files": [{"content": "\"\"\"Base classes for VCS backends.\"\"\"\nimport logging\nimport os\nimport shutil\n\nfrom readthedocs.doc_builder.exceptions import BuildEnvironmentWarning\nfrom readthedocs.projects.exceptions import RepositoryError\n\n\nlog = logging.getLogger(__name__)\n\n\nclass VCSVersion:\n\n    \"\"\"\n    Represents a Version (tag or branch) in a VCS.\n\n    This class should only be instantiated in BaseVCS subclasses.\n\n    It can act as a context manager to temporarily switch to this tag (eg to\n    build docs for this tag).\n    \"\"\"\n\n    def __init__(self, repository, identifier, verbose_name):\n        self.repository = repository\n        self.identifier = identifier\n        self.verbose_name = verbose_name\n\n    def __repr__(self):\n        return '<VCSVersion: {}:{}'.format(\n            self.repository.repo_url,\n            self.verbose_name,\n        )\n\n\nclass BaseVCS:\n\n    \"\"\"\n    Base for VCS Classes.\n\n    VCS commands are ran inside a ``LocalEnvironment``.\n    \"\"\"\n\n    supports_tags = False  # Whether this VCS supports tags or not.\n    supports_branches = False  # Whether this VCS supports branches or not.\n    supports_submodules = False\n\n    # =========================================================================\n    # General methods\n    # =========================================================================\n\n    # Defining a base API, so we'll have unused args\n    # pylint: disable=unused-argument\n    def __init__(\n            self, project, version_slug, environment=None,\n            verbose_name=None, version_type=None, **kwargs\n    ):\n        self.default_branch = project.default_branch\n        self.project = project\n        self.name = project.name\n        self.repo_url = project.clean_repo\n        self.working_dir = project.checkout_path(version_slug)\n        # required for External versions\n        self.verbose_name = verbose_name\n        self.version_type = version_type\n\n        from readthedocs.doc_builder.environments import LocalEnvironment\n        self.environment = environment or LocalEnvironment(project)\n\n        # Update the env variables with the proper VCS env variables\n        self.environment.environment.update(self.env)\n\n    def check_working_dir(self):\n        if not os.path.exists(self.working_dir):\n            os.makedirs(self.working_dir)\n\n    def make_clean_working_dir(self):\n        \"\"\"Ensures that the working dir exists and is empty.\"\"\"\n        shutil.rmtree(self.working_dir, ignore_errors=True)\n        self.check_working_dir()\n\n    @property\n    def env(self):\n        environment = os.environ.copy()\n\n        # TODO: kind of a hack\n        del environment['PATH']\n\n        return environment\n\n    def update(self):\n        \"\"\"\n        Update a local copy of the repository in self.working_dir.\n\n        If self.working_dir is already a valid local copy of the repository,\n        update the repository, else create a new local copy of the repository.\n        \"\"\"\n        self.check_working_dir()\n\n    def run(self, *cmd, **kwargs):\n        kwargs.update({\n            'cwd': self.working_dir,\n            'shell': False,\n        })\n\n        try:\n            build_cmd = self.environment.run(*cmd, **kwargs)\n        except BuildEnvironmentWarning as e:\n            # Re raise as RepositoryError,\n            # so isn't logged as ERROR.\n            raise RepositoryError(str(e))\n\n        # Return a tuple to keep compatibility\n        return (build_cmd.exit_code, build_cmd.output, build_cmd.error)\n\n    # =========================================================================\n    # Tag / Branch related methods\n    # These methods only apply if supports_tags = True and/or\n    # support_branches = True\n    # =========================================================================\n\n    @property\n    def tags(self):\n        \"\"\"\n        Returns a list of VCSVersion objects.\n\n        See VCSVersion for more information.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    def branches(self):\n        \"\"\"\n        Returns a list of VCSVersion objects.\n\n        See VCSVersion for more information.\n        \"\"\"\n        raise NotImplementedError\n\n    @property\n    def commit(self):\n        \"\"\"Returns a string representing the current commit.\"\"\"\n        raise NotImplementedError\n\n    def checkout(self, identifier=None):\n        \"\"\"\n        Set the state to the given identifier.\n\n        If identifier is None, checkout to the latest revision.\n\n        The type and format of identifier may change from VCS to VCS, so each\n        backend is responsible to understand it's identifiers.\n        \"\"\"\n        self.check_working_dir()\n\n    def update_submodules(self, config):\n        \"\"\"\n        Update the submodules of the current checkout.\n\n        :type config: readthedocs.config.BuildConfigBase\n        \"\"\"\n        raise NotImplementedError\n", "path": "readthedocs/vcs_support/base.py"}]}