prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Modify stale files warning to ignore .htaccess, .gitignore and .git
  I am not sure if this is the same as issue #167 or not, or if there a better workaround than the setup I am using.
  
  I have the mkdocs project in one folder, and I am building to a sibling folder ( `site_dir: ../site` )
  My site dir contains its own git repo, and .htaccess and .gitignore, which I want to keep there, but I get that stale warning.
  
  This causes two issues:
  1. Someone may actually run this clean command and delete files I want to keep
  2. I cannot run `--clean`
  
  Perhaps the behavior of that warning (and of `--clean`) should be changed to accommodate such scenario? 
  
  If it cannot be solved automatically, then perhaps by a directive in the config? `build_ignore: [file, folder]`
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `mkdocs/utils.py`
  Content:
  ```
  1 # coding: utf-8
  2 
  3 """
  4 Standalone file utils.
  5 
  6 Nothing in this module should have an knowledge of config or the layout
  7 and structure of the site and pages in the site.
  8 """
  9 
  10 import os
  11 import shutil
  12 
  13 from mkdocs.compat import urlparse
  14 
  15 
  16 def copy_file(source_path, output_path):
  17     """
  18     Copy source_path to output_path, making sure any parent directories exist.
  19     """
  20     output_dir = os.path.dirname(output_path)
  21     if not os.path.exists(output_dir):
  22         os.makedirs(output_dir)
  23     shutil.copy(source_path, output_path)
  24 
  25 
  26 def write_file(content, output_path):
  27     """
  28     Write content to output_path, making sure any parent directories exist.
  29     """
  30     output_dir = os.path.dirname(output_path)
  31     if not os.path.exists(output_dir):
  32         os.makedirs(output_dir)
  33     open(output_path, 'wb').write(content)
  34 
  35 
  36 def clean_directory(directory):
  37     """
  38     Remove the content of a directory recursively but not the directory itself.
  39     """
  40     if os.path.exists(directory):
  41         for entry in os.listdir(directory):
  42             path = os.path.join(directory, entry)
  43             if os.path.isdir(path):
  44                 shutil.rmtree(path, True)
  45             else:
  46                 os.unlink(path)
  47 
  48 
  49 def copy_media_files(from_dir, to_dir):
  50     """
  51     Recursively copy all files except markdown and HTML into another directory.
  52     """
  53     for (source_dir, dirnames, filenames) in os.walk(from_dir):
  54         relative_path = os.path.relpath(source_dir, from_dir)
  55         output_dir = os.path.normpath(os.path.join(to_dir, relative_path))
  56 
  57         for filename in filenames:
  58             if not is_markdown_file(filename) and not is_html_file(filename):
  59                 source_path = os.path.join(source_dir, filename)
  60                 output_path = os.path.join(output_dir, filename)
  61                 copy_file(source_path, output_path)
  62 
  63 
  64 def get_html_path(path):
  65     """
  66     Map a source file path to an output html path.
  67 
  68     Paths like 'index.md' will be converted to 'index.html'
  69     Paths like 'about.md' will be converted to 'about/index.html'
  70     Paths like 'api-guide/core.md' will be converted to 'api-guide/core/index.html'
  71     """
  72     path = os.path.splitext(path)[0]
  73     if os.path.basename(path) == 'index':
  74         return path + '.html'
  75     return "/".join((path, 'index.html'))
  76 
  77 
  78 def get_url_path(path, use_directory_urls=True):
  79     """
  80     Map a source file path to an output html path.
  81 
  82     Paths like 'index.md' will be converted to '/'
  83     Paths like 'about.md' will be converted to '/about/'
  84     Paths like 'api-guide/core.md' will be converted to '/api-guide/core/'
  85 
  86     If `use_directory_urls` is `False`, returned URLs will include the a trailing
  87     `index.html` rather than just returning the directory path.
  88     """
  89     path = get_html_path(path)
  90     url = '/' + path.replace(os.path.sep, '/')
  91     if use_directory_urls:
  92         return url[:-len('index.html')]
  93     return url
  94 
  95 
  96 def is_homepage(path):
  97     return os.path.splitext(path)[0] == 'index'
  98 
  99 
  100 def is_markdown_file(path):
  101     """
  102     Return True if the given file path is a Markdown file.
  103 
  104     http://superuser.com/questions/249436/file-extension-for-markdown-files
  105     """
  106     ext = os.path.splitext(path)[1].lower()
  107     return ext in [
  108         '.markdown',
  109         '.mdown',
  110         '.mkdn',
  111         '.mkd',
  112         '.md',
  113     ]
  114 
  115 
  116 def is_css_file(path):
  117     """
  118     Return True if the given file path is a CSS file.
  119     """
  120     ext = os.path.splitext(path)[1].lower()
  121     return ext in [
  122         '.css',
  123     ]
  124 
  125 
  126 def is_javascript_file(path):
  127     """
  128     Return True if the given file path is a Javascript file.
  129     """
  130     ext = os.path.splitext(path)[1].lower()
  131     return ext in [
  132         '.js',
  133         '.javascript'
  134     ]
  135 
  136 
  137 def is_html_file(path):
  138     """
  139     Return True if the given file path is an HTML file.
  140     """
  141     ext = os.path.splitext(path)[1].lower()
  142     return ext in [
  143         '.html',
  144         '.htm',
  145     ]
  146 
  147 
  148 def create_media_urls(nav, url_list):
  149     """
  150     Return a list of URLs that have been processed correctly for inclusion in a page.
  151     """
  152     final_urls = []
  153     for url in url_list:
  154         # Allow links to fully qualified URL's
  155         parsed = urlparse(url)
  156         if parsed.netloc:
  157             final_urls.append(url)
  158         else:
  159             relative_url = '%s/%s' % (nav.url_context.make_relative('/'), url)
  160             final_urls.append(relative_url)
  161     return final_urls
  162
  ```
  --- 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/mkdocs/utils.py b/mkdocs/utils.py
  --- a/mkdocs/utils.py
  +++ b/mkdocs/utils.py
  @@ -54,6 +54,13 @@
           relative_path = os.path.relpath(source_dir, from_dir)
           output_dir = os.path.normpath(os.path.join(to_dir, relative_path))
   
  +        # Filter filenames starting with a '.'
  +        filenames = [f for f in filenames if not f.startswith('.')]
  +
  +        # Filter the dirnames that start with a '.' and update the list in
  +        # place to prevent us walking these.
  +        dirnames[:] = [d for d in dirnames if not d.startswith('.')]
  +
           for filename in filenames:
               if not is_markdown_file(filename) and not is_html_file(filename):
                   source_path = os.path.join(source_dir, filename)
  
verification_info: {"golden_diff": "diff --git a/mkdocs/utils.py b/mkdocs/utils.py\n--- a/mkdocs/utils.py\n+++ b/mkdocs/utils.py\n@@ -54,6 +54,13 @@\n         relative_path = os.path.relpath(source_dir, from_dir)\n         output_dir = os.path.normpath(os.path.join(to_dir, relative_path))\n \n+        # Filter filenames starting with a '.'\n+        filenames = [f for f in filenames if not f.startswith('.')]\n+\n+        # Filter the dirnames that start with a '.' and update the list in\n+        # place to prevent us walking these.\n+        dirnames[:] = [d for d in dirnames if not d.startswith('.')]\n+\n         for filename in filenames:\n             if not is_markdown_file(filename) and not is_html_file(filename):\n                 source_path = os.path.join(source_dir, filename)\n", "issue": "Modify stale files warning to ignore .htaccess, .gitignore and .git\nI am not sure if this is the same as issue #167 or not, or if there a better workaround than the setup I am using.\n\nI have the mkdocs project in one folder, and I am building to a sibling folder ( `site_dir: ../site` )\nMy site dir contains its own git repo, and .htaccess and .gitignore, which I want to keep there, but I get that stale warning.\n\nThis causes two issues:\n1. Someone may actually run this clean command and delete files I want to keep\n2. I cannot run `--clean`\n\nPerhaps the behavior of that warning (and of `--clean`) should be changed to accommodate such scenario? \n\nIf it cannot be solved automatically, then perhaps by a directive in the config? `build_ignore: [file, folder]`\n\n", "before_files": [{"content": "# coding: utf-8\n\n\"\"\"\nStandalone file utils.\n\nNothing in this module should have an knowledge of config or the layout\nand structure of the site and pages in the site.\n\"\"\"\n\nimport os\nimport shutil\n\nfrom mkdocs.compat import urlparse\n\n\ndef copy_file(source_path, output_path):\n    \"\"\"\n    Copy source_path to output_path, making sure any parent directories exist.\n    \"\"\"\n    output_dir = os.path.dirname(output_path)\n    if not os.path.exists(output_dir):\n        os.makedirs(output_dir)\n    shutil.copy(source_path, output_path)\n\n\ndef write_file(content, output_path):\n    \"\"\"\n    Write content to output_path, making sure any parent directories exist.\n    \"\"\"\n    output_dir = os.path.dirname(output_path)\n    if not os.path.exists(output_dir):\n        os.makedirs(output_dir)\n    open(output_path, 'wb').write(content)\n\n\ndef clean_directory(directory):\n    \"\"\"\n    Remove the content of a directory recursively but not the directory itself.\n    \"\"\"\n    if os.path.exists(directory):\n        for entry in os.listdir(directory):\n            path = os.path.join(directory, entry)\n            if os.path.isdir(path):\n                shutil.rmtree(path, True)\n            else:\n                os.unlink(path)\n\n\ndef copy_media_files(from_dir, to_dir):\n    \"\"\"\n    Recursively copy all files except markdown and HTML into another directory.\n    \"\"\"\n    for (source_dir, dirnames, filenames) in os.walk(from_dir):\n        relative_path = os.path.relpath(source_dir, from_dir)\n        output_dir = os.path.normpath(os.path.join(to_dir, relative_path))\n\n        for filename in filenames:\n            if not is_markdown_file(filename) and not is_html_file(filename):\n                source_path = os.path.join(source_dir, filename)\n                output_path = os.path.join(output_dir, filename)\n                copy_file(source_path, output_path)\n\n\ndef get_html_path(path):\n    \"\"\"\n    Map a source file path to an output html path.\n\n    Paths like 'index.md' will be converted to 'index.html'\n    Paths like 'about.md' will be converted to 'about/index.html'\n    Paths like 'api-guide/core.md' will be converted to 'api-guide/core/index.html'\n    \"\"\"\n    path = os.path.splitext(path)[0]\n    if os.path.basename(path) == 'index':\n        return path + '.html'\n    return \"/\".join((path, 'index.html'))\n\n\ndef get_url_path(path, use_directory_urls=True):\n    \"\"\"\n    Map a source file path to an output html path.\n\n    Paths like 'index.md' will be converted to '/'\n    Paths like 'about.md' will be converted to '/about/'\n    Paths like 'api-guide/core.md' will be converted to '/api-guide/core/'\n\n    If `use_directory_urls` is `False`, returned URLs will include the a trailing\n    `index.html` rather than just returning the directory path.\n    \"\"\"\n    path = get_html_path(path)\n    url = '/' + path.replace(os.path.sep, '/')\n    if use_directory_urls:\n        return url[:-len('index.html')]\n    return url\n\n\ndef is_homepage(path):\n    return os.path.splitext(path)[0] == 'index'\n\n\ndef is_markdown_file(path):\n    \"\"\"\n    Return True if the given file path is a Markdown file.\n\n    http://superuser.com/questions/249436/file-extension-for-markdown-files\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.markdown',\n        '.mdown',\n        '.mkdn',\n        '.mkd',\n        '.md',\n    ]\n\n\ndef is_css_file(path):\n    \"\"\"\n    Return True if the given file path is a CSS file.\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.css',\n    ]\n\n\ndef is_javascript_file(path):\n    \"\"\"\n    Return True if the given file path is a Javascript file.\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.js',\n        '.javascript'\n    ]\n\n\ndef is_html_file(path):\n    \"\"\"\n    Return True if the given file path is an HTML file.\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.html',\n        '.htm',\n    ]\n\n\ndef create_media_urls(nav, url_list):\n    \"\"\"\n    Return a list of URLs that have been processed correctly for inclusion in a page.\n    \"\"\"\n    final_urls = []\n    for url in url_list:\n        # Allow links to fully qualified URL's\n        parsed = urlparse(url)\n        if parsed.netloc:\n            final_urls.append(url)\n        else:\n            relative_url = '%s/%s' % (nav.url_context.make_relative('/'), url)\n            final_urls.append(relative_url)\n    return final_urls\n", "path": "mkdocs/utils.py"}], "after_files": [{"content": "# coding: utf-8\n\n\"\"\"\nStandalone file utils.\n\nNothing in this module should have an knowledge of config or the layout\nand structure of the site and pages in the site.\n\"\"\"\n\nimport os\nimport shutil\n\nfrom mkdocs.compat import urlparse\n\n\ndef copy_file(source_path, output_path):\n    \"\"\"\n    Copy source_path to output_path, making sure any parent directories exist.\n    \"\"\"\n    output_dir = os.path.dirname(output_path)\n    if not os.path.exists(output_dir):\n        os.makedirs(output_dir)\n    shutil.copy(source_path, output_path)\n\n\ndef write_file(content, output_path):\n    \"\"\"\n    Write content to output_path, making sure any parent directories exist.\n    \"\"\"\n    output_dir = os.path.dirname(output_path)\n    if not os.path.exists(output_dir):\n        os.makedirs(output_dir)\n    open(output_path, 'wb').write(content)\n\n\ndef clean_directory(directory):\n    \"\"\"\n    Remove the content of a directory recursively but not the directory itself.\n    \"\"\"\n    if os.path.exists(directory):\n        for entry in os.listdir(directory):\n            path = os.path.join(directory, entry)\n            if os.path.isdir(path):\n                shutil.rmtree(path, True)\n            else:\n                os.unlink(path)\n\n\ndef copy_media_files(from_dir, to_dir):\n    \"\"\"\n    Recursively copy all files except markdown and HTML into another directory.\n    \"\"\"\n    for (source_dir, dirnames, filenames) in os.walk(from_dir):\n        relative_path = os.path.relpath(source_dir, from_dir)\n        output_dir = os.path.normpath(os.path.join(to_dir, relative_path))\n\n        # Filter filenames starting with a '.'\n        filenames = [f for f in filenames if not f.startswith('.')]\n\n        # Filter the dirnames that start with a '.' and update the list in\n        # place to prevent us walking these.\n        dirnames[:] = [d for d in dirnames if not d.startswith('.')]\n\n        for filename in filenames:\n            if not is_markdown_file(filename) and not is_html_file(filename):\n                source_path = os.path.join(source_dir, filename)\n                output_path = os.path.join(output_dir, filename)\n                copy_file(source_path, output_path)\n\n\ndef get_html_path(path):\n    \"\"\"\n    Map a source file path to an output html path.\n\n    Paths like 'index.md' will be converted to 'index.html'\n    Paths like 'about.md' will be converted to 'about/index.html'\n    Paths like 'api-guide/core.md' will be converted to 'api-guide/core/index.html'\n    \"\"\"\n    path = os.path.splitext(path)[0]\n    if os.path.basename(path) == 'index':\n        return path + '.html'\n    return \"/\".join((path, 'index.html'))\n\n\ndef get_url_path(path, use_directory_urls=True):\n    \"\"\"\n    Map a source file path to an output html path.\n\n    Paths like 'index.md' will be converted to '/'\n    Paths like 'about.md' will be converted to '/about/'\n    Paths like 'api-guide/core.md' will be converted to '/api-guide/core/'\n\n    If `use_directory_urls` is `False`, returned URLs will include the a trailing\n    `index.html` rather than just returning the directory path.\n    \"\"\"\n    path = get_html_path(path)\n    url = '/' + path.replace(os.path.sep, '/')\n    if use_directory_urls:\n        return url[:-len('index.html')]\n    return url\n\n\ndef is_homepage(path):\n    return os.path.splitext(path)[0] == 'index'\n\n\ndef is_markdown_file(path):\n    \"\"\"\n    Return True if the given file path is a Markdown file.\n\n    http://superuser.com/questions/249436/file-extension-for-markdown-files\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.markdown',\n        '.mdown',\n        '.mkdn',\n        '.mkd',\n        '.md',\n    ]\n\n\ndef is_css_file(path):\n    \"\"\"\n    Return True if the given file path is a CSS file.\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.css',\n    ]\n\n\ndef is_javascript_file(path):\n    \"\"\"\n    Return True if the given file path is a Javascript file.\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.js',\n        '.javascript'\n    ]\n\n\ndef is_html_file(path):\n    \"\"\"\n    Return True if the given file path is an HTML file.\n    \"\"\"\n    ext = os.path.splitext(path)[1].lower()\n    return ext in [\n        '.html',\n        '.htm',\n    ]\n\n\ndef create_media_urls(nav, url_list):\n    \"\"\"\n    Return a list of URLs that have been processed correctly for inclusion in a page.\n    \"\"\"\n    final_urls = []\n    for url in url_list:\n        # Allow links to fully qualified URL's\n        parsed = urlparse(url)\n        if parsed.netloc:\n            final_urls.append(url)\n        else:\n            relative_url = '%s/%s' % (nav.url_context.make_relative('/'), url)\n            final_urls.append(relative_url)\n    return final_urls\n", "path": "mkdocs/utils.py"}]}