Repository: mkdocs/mkdocs

Issue:
Ignore the site_dir contents if it is in the docs_dir
We have been telling people to not do this, but it comes up again and again. So I wonder if it is a usecase we should support? I don't really like it. Perhaps we just need to error more strongly and tell users rather than causing obscure problems.



Discussion:
It is worth noting that it originally did error, but that broke ReadTheDocs for a large number of users so it was [reduced to a warning](https://github.com/mkdocs/mkdocs/commit/fd9140e7d587be310d1bcbff213f079d0af22b97).

Based on [this comment](https://github.com/mkdocs/mkdocs/issues/796#issuecomment-171914179), it is not clear to me what the appropriate fix is for this. We have three options:
1. Do nothing. A warning will continue to be raised and the weird behavior will continue.
2. Raise an error. Builds will no longer complete if the weird config exists and the weird behavior will be avoided.
3. Apply the fix suggested in this issue, which allows the weird config to remain (possibly with a warning) but eliminates the weird behavior.

It seems clear that we don't want option 1. However, the previously mentioned comment seems to suggest we want option 2, but this issue is for option 3 and is tagged with the 0.16 milestone, potentially making it a release blocker. I'm not inclined to work on a fix until I know which way we want to go (my vote is for option 2).

I also vote for option 2. It will block readthedocs from upgrading to 0.16, but that should be easy to fix on their end.

Agreed - go for 2.
Break things now.

Supporting weird for a long time will drive you crazy.


Resolving patch:
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -229,6 +229,15 @@ def run_validation(self, value):
 
         return os.path.abspath(value)
 
+    def post_validation(self, config, key_name):
+
+        # Validate that the dir is not the parent dir of the config file.
+        if os.path.dirname(config['config_file_path']) == config[key_name]:
+            raise ValidationError(
+                ("The '{0}' should not be the parent directory of the config "
+                 "file. Use a child directory instead so that the config file "
+                 "is a sibling of the config file.").format(key_name))
+
 
 class SiteDir(Dir):
     """
@@ -239,6 +248,8 @@ class SiteDir(Dir):
 
     def post_validation(self, config, key_name):
 
+        super(SiteDir, self).post_validation(config, key_name)
+
         # Validate that the docs_dir and site_dir don't contain the
         # other as this will lead to copying back and forth on each
         # and eventually make a deep nested mess.
@@ -250,7 +261,7 @@ def post_validation(self, config, key_name):
                  "(site_dir: '{0}', docs_dir: '{1}')"
                  ).format(config['site_dir'], config['docs_dir']))
         elif (config['site_dir'] + os.sep).startswith(config['docs_dir'] + os.sep):
-            self.warnings.append(
+            raise ValidationError(
                 ("The 'site_dir' should not be within the 'docs_dir' as this "
                  "leads to the build directory being copied into itself and "
                  "duplicate nested files in the 'site_dir'."


Test patch:
diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py
--- a/mkdocs/tests/build_tests.py
+++ b/mkdocs/tests/build_tests.py
@@ -26,6 +26,8 @@ def load_config(cfg=None):
     cfg = cfg or {}
     if 'site_name' not in cfg:
         cfg['site_name'] = 'Example'
+    if 'config_file_path' not in cfg:
+        cfg['config_file_path'] = os.path.join(os.path.abspath('.'), 'mkdocs.yml')
     conf = config.Config(schema=config.DEFAULT_SCHEMA)
     conf.load_dict(cfg)
 
diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py
--- a/mkdocs/tests/config/config_options_tests.py
+++ b/mkdocs/tests/config/config_options_tests.py
@@ -186,6 +186,20 @@ def test_incorrect_type_type_error(self):
         self.assertRaises(config_options.ValidationError,
                           option.validate, [])
 
+    def test_doc_dir_is_config_dir(self):
+
+        test_config = {
+            'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml'),
+            'docs_dir': '.'
+        }
+
+        docs_dir = config_options.Dir()
+
+        test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
+
+        self.assertRaises(config_options.ValidationError,
+                          docs_dir.post_validation, test_config, 'docs_dir')
+
 
 class SiteDirTest(unittest.TestCase):
 
@@ -207,12 +221,13 @@ def test_doc_dir_in_site_dir(self):
         )
 
         for test_config in test_configs:
+            test_config['config_file_path'] = j(os.path.abspath('..'), 'mkdocs.yml')
 
             test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
             test_config['site_dir'] = option.validate(test_config['site_dir'])
 
             self.assertRaises(config_options.ValidationError,
-                              option.post_validation, test_config, 'key')
+                              option.post_validation, test_config, 'site_dir')
 
     def test_site_dir_in_docs_dir(self):
 
@@ -225,6 +240,7 @@ def test_site_dir_in_docs_dir(self):
         )
 
         for test_config in test_configs:
+            test_config['config_file_path'] = j(os.path.abspath('..'), 'mkdocs.yml')
 
             docs_dir = config_options.Dir()
             option = config_options.SiteDir()
@@ -232,11 +248,8 @@ def test_site_dir_in_docs_dir(self):
             test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
             test_config['site_dir'] = option.validate(test_config['site_dir'])
 
-            option.post_validation(test_config, 'key')
-            self.assertEqual(len(option.warnings), 1)
-            self.assertEqual(
-                option.warnings[0][:50],
-                "The 'site_dir' should not be within the 'docs_dir'")
+            self.assertRaises(config_options.ValidationError,
+                              option.post_validation, test_config, 'site_dir')
 
 
 class ThemeTest(unittest.TestCase):
diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py
--- a/mkdocs/tests/config/config_tests.py
+++ b/mkdocs/tests/config/config_tests.py
@@ -135,7 +135,8 @@ def test_default_pages(self):
             conf = config.Config(schema=config.DEFAULT_SCHEMA)
             conf.load_dict({
                 'site_name': 'Example',
-                'docs_dir': tmp_dir
+                'docs_dir': tmp_dir,
+                'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
             })
             conf.validate()
             self.assertEqual(['index.md', 'about.md'], conf['pages'])
@@ -159,7 +160,8 @@ def test_default_pages_nested(self):
             conf = config.Config(schema=config.DEFAULT_SCHEMA)
             conf.load_dict({
                 'site_name': 'Example',
-                'docs_dir': tmp_dir
+                'docs_dir': tmp_dir,
+                'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml')
             })
             conf.validate()
             self.assertEqual([
@@ -197,6 +199,7 @@ def test_doc_dir_in_site_dir(self):
 
         conf = {
             'site_name': 'Example',
+            'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml')
         }
 
         for test_config in test_configs: