We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Edit on GitHub link is broken for branches with e.g. slash in the name
If you have docs built from a branch with e.g. slash in the name, like `v1.x/master`, then RTD will "slugify" it and use v1.x-master in URLs. That's OK.

The problem is that RTD use this slugified branch name to construct the "Edit on GitHub" URL as well, which then becomes a broken link.


--- 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/builds/utils.py`
Content:
```
1 import re
2 
3 GH_REGEXS = [
4     re.compile('github.com/(.+)/(.+)(?:\.git){1}'),
5     re.compile('github.com/(.+)/(.+)'),
6     re.compile('github.com:(.+)/(.+).git'),
7 ]
8 
9 BB_REGEXS = [
10     re.compile('bitbucket.org/(.+)/(.+)/'),
11     re.compile('bitbucket.org/(.+)/(.+)'),
12     re.compile('bitbucket.org:(.+)/(.+)\.git'),
13 ]
14 
15 def get_github_username_repo(version):
16     repo_url = version.project.repo
17     if 'github' in repo_url:
18         for regex in GH_REGEXS:
19             match = regex.search(repo_url)
20             if match:
21                 return match.groups()
22     return (None, None)
23 
24 def get_bitbucket_username_repo(version):
25     repo_url = version.project.repo
26     if 'bitbucket' in repo_url:
27         for regex in BB_REGEXS:
28             match = regex.search(repo_url)
29             if match:
30                 return match.groups()
31     return (None, None)
32 
33 def get_vcs_version(version):
34     if version.slug == 'latest':
35         if version.project.default_branch:
36             return version.project.default_branch
37         else:
38             return version.project.vcs_repo().fallback_branch
39     else:
40         return version.slug
41 
42 def get_conf_py_path(version):
43     conf_py_path = version.project.conf_file(version.slug)
44     conf_py_path = conf_py_path.replace(
45         version.project.checkout_path(version.slug), '')
46     return conf_py_path.replace('conf.py', '')
47
```

Path: `readthedocs/doc_builder/backends/sphinx.py`
Content:
```
1 import os
2 import shutil
3 import codecs
4 import logging
5 import zipfile
6 
7 from django.template import Template, Context
8 from django.contrib.auth.models import SiteProfileNotAvailable
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.conf import settings
11 
12 from builds import utils as version_utils
13 from core.utils import copy_to_app_servers, copy_file_to_app_servers
14 from doc_builder.base import BaseBuilder, restoring_chdir
15 from projects.utils import run
16 from tastyapi import apiv2
17 
18 log = logging.getLogger(__name__)
19 
20 
21 RTD_CONF_ADDITIONS = """
22 {% load projects_tags %}
23 #Add RTD Template Path.
24 if 'templates_path' in globals():
25     templates_path.insert(0, '{{ template_path }}')
26 else:
27     templates_path = ['{{ template_path }}', 'templates', '_templates',
28                       '.templates']
29 
30 # Add RTD Static Path. Add to the end because it overwrites previous files.
31 if 'html_static_path' in globals():
32     html_static_path.append('{{ static_path }}')
33 else:
34     html_static_path = ['_static', '{{ static_path }}']
35 
36 # Add RTD Theme Path. 
37 if 'html_theme_path' in globals():
38     html_theme_path.append('{{ template_path }}')
39 else:
40     html_theme_path = ['_themes', '{{ template_path }}']
41 
42 # Add RTD Theme only if they aren't overriding it already
43 using_rtd_theme = False
44 if 'html_theme' in globals():
45     if html_theme in ['default']:
46         # Allow people to bail with a hack of having an html_style
47         if not 'html_style' in globals():
48             html_theme = 'sphinx_rtd_theme'
49             html_style = None
50             html_theme_options = {}
51             using_rtd_theme = True
52 else:
53     html_theme = 'sphinx_rtd_theme'
54     html_style = None
55     html_theme_options = {}
56     using_rtd_theme = True
57 
58 # Force theme on setting
59 if globals().get('RTD_NEW_THEME', False):
60     html_theme = 'sphinx_rtd_theme'
61     html_style = None
62     html_theme_options = {}
63     using_rtd_theme = True
64 
65 if globals().get('RTD_OLD_THEME', False):
66     html_style = 'rtd.css'
67     html_theme = 'default'
68 
69 #Add project information to the template context.
70 context = {
71     'using_theme': using_rtd_theme,
72     'html_theme': html_theme,
73     'current_version': "{{ current_version }}",
74     'MEDIA_URL': "{{ settings.MEDIA_URL }}",
75     'PRODUCTION_DOMAIN': "{{ settings.PRODUCTION_DOMAIN }}",
76     'versions': [{% for version in versions|sort_version_aware %}
77     ("{{ version.slug }}", "/en/{{ version.slug }}/"),{% endfor %}
78     ],
79     'downloads': [ {% for key, val in downloads.items %}
80     ("{{ key }}", "{{ val }}"),{% endfor %}
81     ],
82     'slug': '{{ project.slug }}',
83     'name': u'{{ project.name }}',
84     'rtd_language': u'{{ project.language }}',
85     'canonical_url': '{{ project.canonical_url }}',
86     'analytics_code': '{{ project.analytics_code }}',
87     'conf_py_path': '{{ conf_py_path }}',
88     'github_user': '{{ github_user }}',
89     'github_repo': '{{ github_repo }}',
90     'github_version': '{{ github_version }}',
91     'display_github': {{ display_github }},
92     'READTHEDOCS': True,
93     'using_theme': (html_theme == "default"),
94     'new_theme': (html_theme == "sphinx_rtd_theme"),
95 }
96 if 'html_context' in globals():
97     html_context.update(context)
98 else:
99     html_context = context
100 
101 # Add custom RTD extension
102 if 'extensions' in globals():
103     extensions.append("readthedocs_ext.readthedocs")
104     extensions.append("readthedocs_ext.readthedocshtmldir")
105 else:
106     extensions = ["readthedocs_ext.readthedocs", "readthedocs_ext.readthedocshtmldir"]
107 """
108 
109 TEMPLATE_DIR = '%s/readthedocs/templates/sphinx' % settings.SITE_ROOT
110 STATIC_DIR = '%s/_static' % TEMPLATE_DIR
111 
112 
113 class Builder(BaseBuilder):
114     """
115     The parent for most sphinx builders.
116 
117     Also handles the default sphinx output of html.
118     """
119 
120     def _whitelisted(self, **kwargs):
121         """Modify the given ``conf.py`` file from a whitelisted user's project.
122         """
123         project = self.version.project
124         #Open file for appending.
125         outfile = codecs.open(project.conf_file(self.version.slug),
126                               encoding='utf-8', mode='a')
127         outfile.write("\n")
128         conf_py_path = version_utils.get_conf_py_path(self.version)
129         remote_version = version_utils.get_vcs_version(self.version)
130         github_info = version_utils.get_github_username_repo(self.version)
131         bitbucket_info = version_utils.get_bitbucket_username_repo(self.version)
132         if github_info[0] is None:
133             display_github = False
134         else:
135             display_github = True
136         if bitbucket_info[0] is None:
137             display_bitbucket = False
138         else:
139             display_bitbucket = True
140 
141         rtd_ctx = Context({
142             'versions': project.api_versions(),
143             'downloads': self.version.get_downloads(pretty=True),
144             'current_version': self.version.slug,
145             'project': project,
146             'settings': settings,
147             'static_path': STATIC_DIR,
148             'template_path': TEMPLATE_DIR,
149             'conf_py_path': conf_py_path,
150             'downloads': apiv2.version(self.version.pk).downloads.get()['downloads'],
151             # GitHub
152             'github_user': github_info[0],
153             'github_repo': github_info[1],
154             'github_version':  remote_version,
155             'display_github': display_github,
156             # BitBucket
157             'bitbucket_user': bitbucket_info[0],
158             'bitbucket_repo': bitbucket_info[1],
159             'bitbucket_version':  remote_version,
160             'display_bitbucket': display_bitbucket,
161         })
162         rtd_string = Template(RTD_CONF_ADDITIONS).render(rtd_ctx)
163         outfile.write(rtd_string)
164 
165     def clean(self, **kwargs):
166         try:
167             self._whitelisted()
168         except (OSError, SiteProfileNotAvailable, ObjectDoesNotExist):
169             log.error("Conf file not found. Error writing to disk.",
170                       exc_info=True)
171             return ('', 'Conf file not found. Error writing to disk.', -1)
172 
173     @restoring_chdir
174     def build(self, **kwargs):
175         project = self.version.project
176         os.chdir(project.conf_dir(self.version.slug))
177         force_str = " -E " if self.force else ""
178         if project.use_virtualenv:
179             build_command = "%s %s -b readthedocs -D language=%s . _build/html " % (
180                 project.venv_bin(version=self.version.slug,
181                                  bin='sphinx-build'),
182                 force_str,
183                 project.language)
184         else:
185             build_command = ("sphinx-build %s -b readthedocs -D language=%s . _build/html"
186                              % (force_str, project.language))
187         build_results = run(build_command, shell=True)
188         self._zip_html()
189         if 'no targets are out of date.' in build_results[1]:
190             self._changed = False
191         return build_results
192 
193     @restoring_chdir
194     def _zip_html(self, **kwargs):
195         from_path = self.version.project.full_build_path(self.version.slug)
196         to_path = self.version.project.checkout_path(self.version.slug)
197         to_file = os.path.join(to_path, '%s.zip' % self.version.project.slug)
198 
199         log.info("Creating zip file from %s" % from_path)
200         # Create a <slug>.zip file containing all files in file_path
201         os.chdir(from_path)
202         archive = zipfile.ZipFile(to_file, 'w')
203         for root, subfolders, files in os.walk('.'):
204             for file in files:
205                 to_write = os.path.join(root, file)
206                 archive.write(
207                     filename=to_write,
208                     arcname=os.path.join("%s-%s" % (self.version.project.slug,
209                                                     self.version.slug),
210                                          to_write)
211                 )
212         archive.close()
213         return to_file
214 
215     def move(self, **kwargs):
216         project = self.version.project
217         if project.full_build_path(self.version.slug):
218             #Copy the html files.
219             target = project.rtd_build_path(self.version.slug)
220             if "_" in project.slug:
221                 new_slug = project.slug.replace('_', '-')
222                 new_target = target.replace(project.slug, new_slug)
223                 #Only replace 1, so user_builds doesn't get replaced >:x
224                 targets = [target, new_target]
225             else:
226                 targets = [target]
227             for target in targets:
228                 if getattr(settings, "MULTIPLE_APP_SERVERS", None):
229                     log.info("Copying docs to remote server.")
230                     copy_to_app_servers(
231                         project.full_build_path(self.version.slug), target)
232                 else:
233                     if os.path.exists(target):
234                         shutil.rmtree(target)
235                     log.info("Copying docs on the local filesystem")
236                     shutil.copytree(
237                         project.full_build_path(self.version.slug), target)
238 
239                 #Copy the zip file.
240                 to_path = os.path.join(settings.MEDIA_ROOT, 'htmlzip',
241                                        project.slug, self.version.slug)
242                 to_file = os.path.join(to_path, '%s.zip' % project.slug)
243                 from_path = project.checkout_path(self.version.slug)
244                 from_file = os.path.join(from_path, '%s.zip' % project.slug)
245                 if getattr(settings, "MULTIPLE_APP_SERVERS", None):
246                     copy_file_to_app_servers(from_file, to_file)
247                 else:
248                     if not os.path.exists(to_path):
249                         os.makedirs(to_path)
250                     run('mv -f %s %s' % (from_file, to_file))
251         else:
252             log.warning("Not moving docs, because the build dir is unknown.")
253
```
--- 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()

```

diff --git a/readthedocs/builds/utils.py b/readthedocs/builds/utils.py
--- a/readthedocs/builds/utils.py
+++ b/readthedocs/builds/utils.py
@@ -30,14 +30,22 @@
                 return match.groups()
     return (None, None)
 
-def get_vcs_version(version):
+def get_vcs_version_slug(version):
+    slug = None
     if version.slug == 'latest':
         if version.project.default_branch:
-            return version.project.default_branch
+            slug = version.project.default_branch
         else:
-            return version.project.vcs_repo().fallback_branch
+            slug = version.project.vcs_repo().fallback_branch
     else:
-        return version.slug
+        slug = version.slug
+    # https://github.com/rtfd/readthedocs.org/issues/561
+    # version identifiers with / characters in branch name need to un-slugify
+    # the branch name for remote links to work
+    if slug.replace('-', '/') in version.identifier:
+        slug = slug.replace('-', '/')
+    return slug
+
 
 def get_conf_py_path(version):
     conf_py_path = version.project.conf_file(version.slug)
diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py
--- a/readthedocs/doc_builder/backends/sphinx.py
+++ b/readthedocs/doc_builder/backends/sphinx.py
@@ -126,7 +126,7 @@
                               encoding='utf-8', mode='a')
         outfile.write("\n")
         conf_py_path = version_utils.get_conf_py_path(self.version)
-        remote_version = version_utils.get_vcs_version(self.version)
+        remote_version = version_utils.get_vcs_version_slug(self.version)
         github_info = version_utils.get_github_username_repo(self.version)
         bitbucket_info = version_utils.get_bitbucket_username_repo(self.version)
         if github_info[0] is None:

{"golden_diff": "diff --git a/readthedocs/builds/utils.py b/readthedocs/builds/utils.py\n--- a/readthedocs/builds/utils.py\n+++ b/readthedocs/builds/utils.py\n@@ -30,14 +30,22 @@\n                 return match.groups()\n     return (None, None)\n \n-def get_vcs_version(version):\n+def get_vcs_version_slug(version):\n+    slug = None\n     if version.slug == 'latest':\n         if version.project.default_branch:\n-            return version.project.default_branch\n+            slug = version.project.default_branch\n         else:\n-            return version.project.vcs_repo().fallback_branch\n+            slug = version.project.vcs_repo().fallback_branch\n     else:\n-        return version.slug\n+        slug = version.slug\n+    # https://github.com/rtfd/readthedocs.org/issues/561\n+    # version identifiers with / characters in branch name need to un-slugify\n+    # the branch name for remote links to work\n+    if slug.replace('-', '/') in version.identifier:\n+        slug = slug.replace('-', '/')\n+    return slug\n+\n \n def get_conf_py_path(version):\n     conf_py_path = version.project.conf_file(version.slug)\ndiff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py\n--- a/readthedocs/doc_builder/backends/sphinx.py\n+++ b/readthedocs/doc_builder/backends/sphinx.py\n@@ -126,7 +126,7 @@\n                               encoding='utf-8', mode='a')\n         outfile.write(\"\\n\")\n         conf_py_path = version_utils.get_conf_py_path(self.version)\n-        remote_version = version_utils.get_vcs_version(self.version)\n+        remote_version = version_utils.get_vcs_version_slug(self.version)\n         github_info = version_utils.get_github_username_repo(self.version)\n         bitbucket_info = version_utils.get_bitbucket_username_repo(self.version)\n         if github_info[0] is None:\n", "issue": "Edit on GitHub link is broken for branches with e.g. slash in the name\nIf you have docs built from a branch with e.g. slash in the name, like `v1.x/master`, then RTD will \"slugify\" it and use v1.x-master in URLs. That's OK.\n\nThe problem is that RTD use this slugified branch name to construct the \"Edit on GitHub\" URL as well, which then becomes a broken link.\n\n", "before_files": [{"content": "import re\n\nGH_REGEXS = [\n    re.compile('github.com/(.+)/(.+)(?:\\.git){1}'),\n    re.compile('github.com/(.+)/(.+)'),\n    re.compile('github.com:(.+)/(.+).git'),\n]\n\nBB_REGEXS = [\n    re.compile('bitbucket.org/(.+)/(.+)/'),\n    re.compile('bitbucket.org/(.+)/(.+)'),\n    re.compile('bitbucket.org:(.+)/(.+)\\.git'),\n]\n\ndef get_github_username_repo(version):\n    repo_url = version.project.repo\n    if 'github' in repo_url:\n        for regex in GH_REGEXS:\n            match = regex.search(repo_url)\n            if match:\n                return match.groups()\n    return (None, None)\n\ndef get_bitbucket_username_repo(version):\n    repo_url = version.project.repo\n    if 'bitbucket' in repo_url:\n        for regex in BB_REGEXS:\n            match = regex.search(repo_url)\n            if match:\n                return match.groups()\n    return (None, None)\n\ndef get_vcs_version(version):\n    if version.slug == 'latest':\n        if version.project.default_branch:\n            return version.project.default_branch\n        else:\n            return version.project.vcs_repo().fallback_branch\n    else:\n        return version.slug\n\ndef get_conf_py_path(version):\n    conf_py_path = version.project.conf_file(version.slug)\n    conf_py_path = conf_py_path.replace(\n        version.project.checkout_path(version.slug), '')\n    return conf_py_path.replace('conf.py', '')\n", "path": "readthedocs/builds/utils.py"}, {"content": "import os\nimport shutil\nimport codecs\nimport logging\nimport zipfile\n\nfrom django.template import Template, Context\nfrom django.contrib.auth.models import SiteProfileNotAvailable\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.conf import settings\n\nfrom builds import utils as version_utils\nfrom core.utils import copy_to_app_servers, copy_file_to_app_servers\nfrom doc_builder.base import BaseBuilder, restoring_chdir\nfrom projects.utils import run\nfrom tastyapi import apiv2\n\nlog = logging.getLogger(__name__)\n\n\nRTD_CONF_ADDITIONS = \"\"\"\n{% load projects_tags %}\n#Add RTD Template Path.\nif 'templates_path' in globals():\n    templates_path.insert(0, '{{ template_path }}')\nelse:\n    templates_path = ['{{ template_path }}', 'templates', '_templates',\n                      '.templates']\n\n# Add RTD Static Path. Add to the end because it overwrites previous files.\nif 'html_static_path' in globals():\n    html_static_path.append('{{ static_path }}')\nelse:\n    html_static_path = ['_static', '{{ static_path }}']\n\n# Add RTD Theme Path. \nif 'html_theme_path' in globals():\n    html_theme_path.append('{{ template_path }}')\nelse:\n    html_theme_path = ['_themes', '{{ template_path }}']\n\n# Add RTD Theme only if they aren't overriding it already\nusing_rtd_theme = False\nif 'html_theme' in globals():\n    if html_theme in ['default']:\n        # Allow people to bail with a hack of having an html_style\n        if not 'html_style' in globals():\n            html_theme = 'sphinx_rtd_theme'\n            html_style = None\n            html_theme_options = {}\n            using_rtd_theme = True\nelse:\n    html_theme = 'sphinx_rtd_theme'\n    html_style = None\n    html_theme_options = {}\n    using_rtd_theme = True\n\n# Force theme on setting\nif globals().get('RTD_NEW_THEME', False):\n    html_theme = 'sphinx_rtd_theme'\n    html_style = None\n    html_theme_options = {}\n    using_rtd_theme = True\n\nif globals().get('RTD_OLD_THEME', False):\n    html_style = 'rtd.css'\n    html_theme = 'default'\n\n#Add project information to the template context.\ncontext = {\n    'using_theme': using_rtd_theme,\n    'html_theme': html_theme,\n    'current_version': \"{{ current_version }}\",\n    'MEDIA_URL': \"{{ settings.MEDIA_URL }}\",\n    'PRODUCTION_DOMAIN': \"{{ settings.PRODUCTION_DOMAIN }}\",\n    'versions': [{% for version in versions|sort_version_aware %}\n    (\"{{ version.slug }}\", \"/en/{{ version.slug }}/\"),{% endfor %}\n    ],\n    'downloads': [ {% for key, val in downloads.items %}\n    (\"{{ key }}\", \"{{ val }}\"),{% endfor %}\n    ],\n    'slug': '{{ project.slug }}',\n    'name': u'{{ project.name }}',\n    'rtd_language': u'{{ project.language }}',\n    'canonical_url': '{{ project.canonical_url }}',\n    'analytics_code': '{{ project.analytics_code }}',\n    'conf_py_path': '{{ conf_py_path }}',\n    'github_user': '{{ github_user }}',\n    'github_repo': '{{ github_repo }}',\n    'github_version': '{{ github_version }}',\n    'display_github': {{ display_github }},\n    'READTHEDOCS': True,\n    'using_theme': (html_theme == \"default\"),\n    'new_theme': (html_theme == \"sphinx_rtd_theme\"),\n}\nif 'html_context' in globals():\n    html_context.update(context)\nelse:\n    html_context = context\n\n# Add custom RTD extension\nif 'extensions' in globals():\n    extensions.append(\"readthedocs_ext.readthedocs\")\n    extensions.append(\"readthedocs_ext.readthedocshtmldir\")\nelse:\n    extensions = [\"readthedocs_ext.readthedocs\", \"readthedocs_ext.readthedocshtmldir\"]\n\"\"\"\n\nTEMPLATE_DIR = '%s/readthedocs/templates/sphinx' % settings.SITE_ROOT\nSTATIC_DIR = '%s/_static' % TEMPLATE_DIR\n\n\nclass Builder(BaseBuilder):\n    \"\"\"\n    The parent for most sphinx builders.\n\n    Also handles the default sphinx output of html.\n    \"\"\"\n\n    def _whitelisted(self, **kwargs):\n        \"\"\"Modify the given ``conf.py`` file from a whitelisted user's project.\n        \"\"\"\n        project = self.version.project\n        #Open file for appending.\n        outfile = codecs.open(project.conf_file(self.version.slug),\n                              encoding='utf-8', mode='a')\n        outfile.write(\"\\n\")\n        conf_py_path = version_utils.get_conf_py_path(self.version)\n        remote_version = version_utils.get_vcs_version(self.version)\n        github_info = version_utils.get_github_username_repo(self.version)\n        bitbucket_info = version_utils.get_bitbucket_username_repo(self.version)\n        if github_info[0] is None:\n            display_github = False\n        else:\n            display_github = True\n        if bitbucket_info[0] is None:\n            display_bitbucket = False\n        else:\n            display_bitbucket = True\n\n        rtd_ctx = Context({\n            'versions': project.api_versions(),\n            'downloads': self.version.get_downloads(pretty=True),\n            'current_version': self.version.slug,\n            'project': project,\n            'settings': settings,\n            'static_path': STATIC_DIR,\n            'template_path': TEMPLATE_DIR,\n            'conf_py_path': conf_py_path,\n            'downloads': apiv2.version(self.version.pk).downloads.get()['downloads'],\n            # GitHub\n            'github_user': github_info[0],\n            'github_repo': github_info[1],\n            'github_version':  remote_version,\n            'display_github': display_github,\n            # BitBucket\n            'bitbucket_user': bitbucket_info[0],\n            'bitbucket_repo': bitbucket_info[1],\n            'bitbucket_version':  remote_version,\n            'display_bitbucket': display_bitbucket,\n        })\n        rtd_string = Template(RTD_CONF_ADDITIONS).render(rtd_ctx)\n        outfile.write(rtd_string)\n\n    def clean(self, **kwargs):\n        try:\n            self._whitelisted()\n        except (OSError, SiteProfileNotAvailable, ObjectDoesNotExist):\n            log.error(\"Conf file not found. Error writing to disk.\",\n                      exc_info=True)\n            return ('', 'Conf file not found. Error writing to disk.', -1)\n\n    @restoring_chdir\n    def build(self, **kwargs):\n        project = self.version.project\n        os.chdir(project.conf_dir(self.version.slug))\n        force_str = \" -E \" if self.force else \"\"\n        if project.use_virtualenv:\n            build_command = \"%s %s -b readthedocs -D language=%s . _build/html \" % (\n                project.venv_bin(version=self.version.slug,\n                                 bin='sphinx-build'),\n                force_str,\n                project.language)\n        else:\n            build_command = (\"sphinx-build %s -b readthedocs -D language=%s . _build/html\"\n                             % (force_str, project.language))\n        build_results = run(build_command, shell=True)\n        self._zip_html()\n        if 'no targets are out of date.' in build_results[1]:\n            self._changed = False\n        return build_results\n\n    @restoring_chdir\n    def _zip_html(self, **kwargs):\n        from_path = self.version.project.full_build_path(self.version.slug)\n        to_path = self.version.project.checkout_path(self.version.slug)\n        to_file = os.path.join(to_path, '%s.zip' % self.version.project.slug)\n\n        log.info(\"Creating zip file from %s\" % from_path)\n        # Create a <slug>.zip file containing all files in file_path\n        os.chdir(from_path)\n        archive = zipfile.ZipFile(to_file, 'w')\n        for root, subfolders, files in os.walk('.'):\n            for file in files:\n                to_write = os.path.join(root, file)\n                archive.write(\n                    filename=to_write,\n                    arcname=os.path.join(\"%s-%s\" % (self.version.project.slug,\n                                                    self.version.slug),\n                                         to_write)\n                )\n        archive.close()\n        return to_file\n\n    def move(self, **kwargs):\n        project = self.version.project\n        if project.full_build_path(self.version.slug):\n            #Copy the html files.\n            target = project.rtd_build_path(self.version.slug)\n            if \"_\" in project.slug:\n                new_slug = project.slug.replace('_', '-')\n                new_target = target.replace(project.slug, new_slug)\n                #Only replace 1, so user_builds doesn't get replaced >:x\n                targets = [target, new_target]\n            else:\n                targets = [target]\n            for target in targets:\n                if getattr(settings, \"MULTIPLE_APP_SERVERS\", None):\n                    log.info(\"Copying docs to remote server.\")\n                    copy_to_app_servers(\n                        project.full_build_path(self.version.slug), target)\n                else:\n                    if os.path.exists(target):\n                        shutil.rmtree(target)\n                    log.info(\"Copying docs on the local filesystem\")\n                    shutil.copytree(\n                        project.full_build_path(self.version.slug), target)\n\n                #Copy the zip file.\n                to_path = os.path.join(settings.MEDIA_ROOT, 'htmlzip',\n                                       project.slug, self.version.slug)\n                to_file = os.path.join(to_path, '%s.zip' % project.slug)\n                from_path = project.checkout_path(self.version.slug)\n                from_file = os.path.join(from_path, '%s.zip' % project.slug)\n                if getattr(settings, \"MULTIPLE_APP_SERVERS\", None):\n                    copy_file_to_app_servers(from_file, to_file)\n                else:\n                    if not os.path.exists(to_path):\n                        os.makedirs(to_path)\n                    run('mv -f %s %s' % (from_file, to_file))\n        else:\n            log.warning(\"Not moving docs, because the build dir is unknown.\")\n", "path": "readthedocs/doc_builder/backends/sphinx.py"}], "after_files": [{"content": "import re\n\nGH_REGEXS = [\n    re.compile('github.com/(.+)/(.+)(?:\\.git){1}'),\n    re.compile('github.com/(.+)/(.+)'),\n    re.compile('github.com:(.+)/(.+).git'),\n]\n\nBB_REGEXS = [\n    re.compile('bitbucket.org/(.+)/(.+)/'),\n    re.compile('bitbucket.org/(.+)/(.+)'),\n    re.compile('bitbucket.org:(.+)/(.+)\\.git'),\n]\n\ndef get_github_username_repo(version):\n    repo_url = version.project.repo\n    if 'github' in repo_url:\n        for regex in GH_REGEXS:\n            match = regex.search(repo_url)\n            if match:\n                return match.groups()\n    return (None, None)\n\ndef get_bitbucket_username_repo(version):\n    repo_url = version.project.repo\n    if 'bitbucket' in repo_url:\n        for regex in BB_REGEXS:\n            match = regex.search(repo_url)\n            if match:\n                return match.groups()\n    return (None, None)\n\ndef get_vcs_version_slug(version):\n    slug = None\n    if version.slug == 'latest':\n        if version.project.default_branch:\n            slug = version.project.default_branch\n        else:\n            slug = version.project.vcs_repo().fallback_branch\n    else:\n        slug = version.slug\n    # https://github.com/rtfd/readthedocs.org/issues/561\n    # version identifiers with / characters in branch name need to un-slugify\n    # the branch name for remote links to work\n    if slug.replace('-', '/') in version.identifier:\n        slug = slug.replace('-', '/')\n    return slug\n\n\ndef get_conf_py_path(version):\n    conf_py_path = version.project.conf_file(version.slug)\n    conf_py_path = conf_py_path.replace(\n        version.project.checkout_path(version.slug), '')\n    return conf_py_path.replace('conf.py', '')\n", "path": "readthedocs/builds/utils.py"}, {"content": "import os\nimport shutil\nimport codecs\nimport logging\nimport zipfile\n\nfrom django.template import Template, Context\nfrom django.contrib.auth.models import SiteProfileNotAvailable\nfrom django.core.exceptions import ObjectDoesNotExist\nfrom django.conf import settings\n\nfrom builds import utils as version_utils\nfrom core.utils import copy_to_app_servers, copy_file_to_app_servers\nfrom doc_builder.base import BaseBuilder, restoring_chdir\nfrom projects.utils import run\nfrom tastyapi import apiv2\n\nlog = logging.getLogger(__name__)\n\n\nRTD_CONF_ADDITIONS = \"\"\"\n{% load projects_tags %}\n#Add RTD Template Path.\nif 'templates_path' in globals():\n    templates_path.insert(0, '{{ template_path }}')\nelse:\n    templates_path = ['{{ template_path }}', 'templates', '_templates',\n                      '.templates']\n\n# Add RTD Static Path. Add to the end because it overwrites previous files.\nif 'html_static_path' in globals():\n    html_static_path.append('{{ static_path }}')\nelse:\n    html_static_path = ['_static', '{{ static_path }}']\n\n# Add RTD Theme Path. \nif 'html_theme_path' in globals():\n    html_theme_path.append('{{ template_path }}')\nelse:\n    html_theme_path = ['_themes', '{{ template_path }}']\n\n# Add RTD Theme only if they aren't overriding it already\nusing_rtd_theme = False\nif 'html_theme' in globals():\n    if html_theme in ['default']:\n        # Allow people to bail with a hack of having an html_style\n        if not 'html_style' in globals():\n            html_theme = 'sphinx_rtd_theme'\n            html_style = None\n            html_theme_options = {}\n            using_rtd_theme = True\nelse:\n    html_theme = 'sphinx_rtd_theme'\n    html_style = None\n    html_theme_options = {}\n    using_rtd_theme = True\n\n# Force theme on setting\nif globals().get('RTD_NEW_THEME', False):\n    html_theme = 'sphinx_rtd_theme'\n    html_style = None\n    html_theme_options = {}\n    using_rtd_theme = True\n\nif globals().get('RTD_OLD_THEME', False):\n    html_style = 'rtd.css'\n    html_theme = 'default'\n\n#Add project information to the template context.\ncontext = {\n    'using_theme': using_rtd_theme,\n    'html_theme': html_theme,\n    'current_version': \"{{ current_version }}\",\n    'MEDIA_URL': \"{{ settings.MEDIA_URL }}\",\n    'PRODUCTION_DOMAIN': \"{{ settings.PRODUCTION_DOMAIN }}\",\n    'versions': [{% for version in versions|sort_version_aware %}\n    (\"{{ version.slug }}\", \"/en/{{ version.slug }}/\"),{% endfor %}\n    ],\n    'downloads': [ {% for key, val in downloads.items %}\n    (\"{{ key }}\", \"{{ val }}\"),{% endfor %}\n    ],\n    'slug': '{{ project.slug }}',\n    'name': u'{{ project.name }}',\n    'rtd_language': u'{{ project.language }}',\n    'canonical_url': '{{ project.canonical_url }}',\n    'analytics_code': '{{ project.analytics_code }}',\n    'conf_py_path': '{{ conf_py_path }}',\n    'github_user': '{{ github_user }}',\n    'github_repo': '{{ github_repo }}',\n    'github_version': '{{ github_version }}',\n    'display_github': {{ display_github }},\n    'READTHEDOCS': True,\n    'using_theme': (html_theme == \"default\"),\n    'new_theme': (html_theme == \"sphinx_rtd_theme\"),\n}\nif 'html_context' in globals():\n    html_context.update(context)\nelse:\n    html_context = context\n\n# Add custom RTD extension\nif 'extensions' in globals():\n    extensions.append(\"readthedocs_ext.readthedocs\")\n    extensions.append(\"readthedocs_ext.readthedocshtmldir\")\nelse:\n    extensions = [\"readthedocs_ext.readthedocs\", \"readthedocs_ext.readthedocshtmldir\"]\n\"\"\"\n\nTEMPLATE_DIR = '%s/readthedocs/templates/sphinx' % settings.SITE_ROOT\nSTATIC_DIR = '%s/_static' % TEMPLATE_DIR\n\n\nclass Builder(BaseBuilder):\n    \"\"\"\n    The parent for most sphinx builders.\n\n    Also handles the default sphinx output of html.\n    \"\"\"\n\n    def _whitelisted(self, **kwargs):\n        \"\"\"Modify the given ``conf.py`` file from a whitelisted user's project.\n        \"\"\"\n        project = self.version.project\n        #Open file for appending.\n        outfile = codecs.open(project.conf_file(self.version.slug),\n                              encoding='utf-8', mode='a')\n        outfile.write(\"\\n\")\n        conf_py_path = version_utils.get_conf_py_path(self.version)\n        remote_version = version_utils.get_vcs_version_slug(self.version)\n        github_info = version_utils.get_github_username_repo(self.version)\n        bitbucket_info = version_utils.get_bitbucket_username_repo(self.version)\n        if github_info[0] is None:\n            display_github = False\n        else:\n            display_github = True\n        if bitbucket_info[0] is None:\n            display_bitbucket = False\n        else:\n            display_bitbucket = True\n\n        rtd_ctx = Context({\n            'versions': project.api_versions(),\n            'downloads': self.version.get_downloads(pretty=True),\n            'current_version': self.version.slug,\n            'project': project,\n            'settings': settings,\n            'static_path': STATIC_DIR,\n            'template_path': TEMPLATE_DIR,\n            'conf_py_path': conf_py_path,\n            'downloads': apiv2.version(self.version.pk).downloads.get()['downloads'],\n            # GitHub\n            'github_user': github_info[0],\n            'github_repo': github_info[1],\n            'github_version':  remote_version,\n            'display_github': display_github,\n            # BitBucket\n            'bitbucket_user': bitbucket_info[0],\n            'bitbucket_repo': bitbucket_info[1],\n            'bitbucket_version':  remote_version,\n            'display_bitbucket': display_bitbucket,\n        })\n        rtd_string = Template(RTD_CONF_ADDITIONS).render(rtd_ctx)\n        outfile.write(rtd_string)\n\n    def clean(self, **kwargs):\n        try:\n            self._whitelisted()\n        except (OSError, SiteProfileNotAvailable, ObjectDoesNotExist):\n            log.error(\"Conf file not found. Error writing to disk.\",\n                      exc_info=True)\n            return ('', 'Conf file not found. Error writing to disk.', -1)\n\n    @restoring_chdir\n    def build(self, **kwargs):\n        project = self.version.project\n        os.chdir(project.conf_dir(self.version.slug))\n        force_str = \" -E \" if self.force else \"\"\n        if project.use_virtualenv:\n            build_command = \"%s %s -b readthedocs -D language=%s . _build/html \" % (\n                project.venv_bin(version=self.version.slug,\n                                 bin='sphinx-build'),\n                force_str,\n                project.language)\n        else:\n            build_command = (\"sphinx-build %s -b readthedocs -D language=%s . _build/html\"\n                             % (force_str, project.language))\n        build_results = run(build_command, shell=True)\n        self._zip_html()\n        if 'no targets are out of date.' in build_results[1]:\n            self._changed = False\n        return build_results\n\n    @restoring_chdir\n    def _zip_html(self, **kwargs):\n        from_path = self.version.project.full_build_path(self.version.slug)\n        to_path = self.version.project.checkout_path(self.version.slug)\n        to_file = os.path.join(to_path, '%s.zip' % self.version.project.slug)\n\n        log.info(\"Creating zip file from %s\" % from_path)\n        # Create a <slug>.zip file containing all files in file_path\n        os.chdir(from_path)\n        archive = zipfile.ZipFile(to_file, 'w')\n        for root, subfolders, files in os.walk('.'):\n            for file in files:\n                to_write = os.path.join(root, file)\n                archive.write(\n                    filename=to_write,\n                    arcname=os.path.join(\"%s-%s\" % (self.version.project.slug,\n                                                    self.version.slug),\n                                         to_write)\n                )\n        archive.close()\n        return to_file\n\n    def move(self, **kwargs):\n        project = self.version.project\n        if project.full_build_path(self.version.slug):\n            #Copy the html files.\n            target = project.rtd_build_path(self.version.slug)\n            if \"_\" in project.slug:\n                new_slug = project.slug.replace('_', '-')\n                new_target = target.replace(project.slug, new_slug)\n                #Only replace 1, so user_builds doesn't get replaced >:x\n                targets = [target, new_target]\n            else:\n                targets = [target]\n            for target in targets:\n                if getattr(settings, \"MULTIPLE_APP_SERVERS\", None):\n                    log.info(\"Copying docs to remote server.\")\n                    copy_to_app_servers(\n                        project.full_build_path(self.version.slug), target)\n                else:\n                    if os.path.exists(target):\n                        shutil.rmtree(target)\n                    log.info(\"Copying docs on the local filesystem\")\n                    shutil.copytree(\n                        project.full_build_path(self.version.slug), target)\n\n                #Copy the zip file.\n                to_path = os.path.join(settings.MEDIA_ROOT, 'htmlzip',\n                                       project.slug, self.version.slug)\n                to_file = os.path.join(to_path, '%s.zip' % project.slug)\n                from_path = project.checkout_path(self.version.slug)\n                from_file = os.path.join(from_path, '%s.zip' % project.slug)\n                if getattr(settings, \"MULTIPLE_APP_SERVERS\", None):\n                    copy_file_to_app_servers(from_file, to_file)\n                else:\n                    if not os.path.exists(to_path):\n                        os.makedirs(to_path)\n                    run('mv -f %s %s' % (from_file, to_file))\n        else:\n            log.warning(\"Not moving docs, because the build dir is unknown.\")\n", "path": "readthedocs/doc_builder/backends/sphinx.py"}]}