Repository: mkdocs/mkdocs

Issue:
Advice: Do not set site_dir to /
Just a warning to anyone who might want to change their project build directory to the root of the project, with a .yml file like this:

```yaml
site_name: My Docs
site_dir: /
pages:
  - Home: index.md
theme: material
```

It won't do what you think it will. If you run ```mkdocs build --clean``` you will have it recursively delete everything it can from your system root folder until it runs into a file it can't delete without sudo. You will lose the entire contents of your home folder as well as your Ubuntu profile, and do unknown damage to your system.

Time to reformat. :D


Discussion:
I think you would need to set it to `~` as `/` would be the root of the system and thus not your home folder. However, it would be worth a check to make sure users don't use `~` or `/`.

I'd accept a change that warned users and forced them to type continue or something similar
Also, we don't delete any "hidden" (dotfiles), so `.profile` wouldn't be deleted.
A warning would be a good idea is what I'm thinking. It would have saved me from that really dumb, costly mistake.

What I actually wanted to do there was build to the root of the *project* folder, because our GitHub pages is set up to serve from root of master.
In config validation, the first thing we generally do after confirming it is a string, is pass the value to `os.path.abspath`. It's only after that that we actually check the location. So we wouldn't notice if it was absolute to start.

Although, I'm surprised the site_dir-can't-be-in-docs_dir and docs_dir-can't-be-in-site_dir checks didn't catch this. If one of them is at the system root, then the other must be in a subdir (of some level).

The general use case is that all local paths in the config file are expected to be relative to the location of the config file. I guess we just assume that everyone would always use relative paths. However, there may be a valid usecase for a user to use an absolute path, so we probably should issue a warning (and require explicit verification) before proceeding when an absolute path is provided. Something like:

```
Local paths in the config are generally expected to be relative to the config file location. 
You provided an absolute path from the root of your local file system.
setting_name: "site_dir"; value: "/"
Are you sure you want to continue? [y/N]:
```
A warning without the explicit verification is not of much value as the damage will already be started by the time the user sees the warning. Of course, with explicit verification, we would need to also provide a CLI flag to bypass the check for those users who need to automate with an absolute value.

The [docs_dir-can't-be-in-site_dir][1] check didn't catch it because we do:

```python
if (config['docs_dir'] + os.sep).startswith(config['site_dir'] + os.sep):
```
The `+ os.sep` breaks things for `/` (ex: the string `/anything/` does not start with `//`). Normally, `os.path.abspath` will return a directory without a slash on the end, so we didn't bother to explicitly remove (or check for) a slash as the last character. Of course, the one exception is the path `/` (`os.path.abspath('/foo/')` returns `'/foo'` whereas `os.path.abspath('/')` returns `/`).

And while removing the `+ os.sep` from those checks would solve this problem, it would reintroduce #1011, so they need to stay.

Perhaps the fix here would be to just do `value.rstrip(os.sep)`. In the rare event a user actually provides `/` as the value, the modified path would not exist, which would be an error. It would also be harmless, as there should be no situation where `/` is actually the correct value.

[1]: https://github.com/mkdocs/mkdocs/blob/master/mkdocs/config/config_options.py#L256

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
@@ -253,14 +253,14 @@ def post_validation(self, 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.
-        if (config['docs_dir'] + os.sep).startswith(config['site_dir'] + os.sep):
+        if (config['docs_dir'] + os.sep).startswith(config['site_dir'].rstrip(os.sep) + os.sep):
             raise ValidationError(
                 ("The 'docs_dir' should not be within the 'site_dir' as this "
                  "can mean the source files are overwritten by the output or "
                  "it will be deleted if --clean is passed to mkdocs build."
                  "(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):
+        elif (config['site_dir'] + os.sep).startswith(config['docs_dir'].rstrip(os.sep) + os.sep):
             raise ValidationError(
                 ("The 'site_dir' should not be within the 'docs_dir' as this "
                  "leads to the build directory being copied into itself and "


Test patch:
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
@@ -203,11 +203,21 @@ def test_doc_dir_is_config_dir(self):
 
 class SiteDirTest(unittest.TestCase):
 
+    def validate_config(self, config):
+        """ Given a config with values for site_dir and doc_dir, run site_dir post_validation. """
+        site_dir = config_options.SiteDir()
+        docs_dir = config_options.Dir()
+
+        config['config_file_path'] = os.path.join(os.path.abspath('..'), 'mkdocs.yml')
+
+        config['docs_dir'] = docs_dir.validate(config['docs_dir'])
+        config['site_dir'] = site_dir.validate(config['site_dir'])
+        site_dir.post_validation(config, 'site_dir')
+        return True  # No errors were raised
+
     def test_doc_dir_in_site_dir(self):
 
         j = os.path.join
-        option = config_options.SiteDir()
-        docs_dir = config_options.Dir()
         # The parent dir is not the same on every system, so use the actual dir name
         parent_dir = mkdocs.__file__.split(os.sep)[-3]
 
@@ -218,16 +228,12 @@ def test_doc_dir_in_site_dir(self):
             {'docs_dir': 'docs', 'site_dir': ''},
             {'docs_dir': '', 'site_dir': ''},
             {'docs_dir': j('..', parent_dir, 'docs'), 'site_dir': 'docs'},
+            {'docs_dir': 'docs', 'site_dir': '/'}
         )
 
         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, 'site_dir')
+                              self.validate_config, test_config)
 
     def test_site_dir_in_docs_dir(self):
 
@@ -237,19 +243,23 @@ def test_site_dir_in_docs_dir(self):
             {'docs_dir': 'docs', 'site_dir': j('docs', 'site')},
             {'docs_dir': '.', 'site_dir': 'site'},
             {'docs_dir': '', 'site_dir': 'site'},
+            {'docs_dir': '/', 'site_dir': 'site'},
         )
 
         for test_config in test_configs:
-            test_config['config_file_path'] = j(os.path.abspath('..'), 'mkdocs.yml')
+            self.assertRaises(config_options.ValidationError,
+                              self.validate_config, test_config)
 
-            docs_dir = config_options.Dir()
-            option = config_options.SiteDir()
+    def test_common_prefix(self):
+        """ Legitimate settings with common prefixes should not fail validation. """
 
-            test_config['docs_dir'] = docs_dir.validate(test_config['docs_dir'])
-            test_config['site_dir'] = option.validate(test_config['site_dir'])
+        test_configs = (
+            {'docs_dir': 'docs', 'site_dir': 'docs-site'},
+            {'docs_dir': 'site-docs', 'site_dir': 'site'},
+        )
 
-            self.assertRaises(config_options.ValidationError,
-                              option.post_validation, test_config, 'site_dir')
+        for test_config in test_configs:
+            assert self.validate_config(test_config)
 
 
 class ThemeTest(unittest.TestCase):