We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Redirect full path to default version
I'd like to be able to create versionless links to the documentation, for use in error messages, code comments, etc. For example, a message like `see https://click.palletsprojects.com/windows for more information`. I don't want to use URLs with versions because I would have to remember to modify all instances of it before releasing a new version.

Currently, only the root path redirects to the default version, other paths raise a 404. Instead, the path should be preserved and appended to the default version path on redirect.

```
Works:
https://click.palletsprojects.com/ -> https://click.palletsprojects.com/en/7.x/

Doesn't work, 404:
https://click.palletsprojects.com/windows -> https://click.palletsprojects.com/en/7.x/windows
```

I do not want to use the "latest" or "stable" versions because I would like the URLs that people land on and share to contain the actual version.

I already do this with the transitional redirects I set up from `click.pocoo.org` to `click.palletsprojects.com`. A similar approach could probably be used to extend RTD's default redirect.

```nginx
location ~ ^/dev(.*)$ {
  return 301 https://click.palletsprojects.com/en/master$1;
}

location ~ ^/(\d)(.*)$ {
  return 301 https://click.palletsprojects.com/en/$1.x$2;
}

location ~ ^/latest(.*)$ {
  return 301 https://click.palletsprojects.com/en/7.x$1;
}

location / {
  return 301 https://click.palletsprojects.com/en/7.x$request_uri;
}
```

--- 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/core/views/__init__.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 """
4 Core views, including the main homepage,
5 
6 documentation and header rendering, and server errors.
7 """
8 
9 from __future__ import absolute_import
10 from __future__ import division
11 import os
12 import logging
13 
14 from django.conf import settings
15 from django.http import HttpResponseRedirect, Http404, JsonResponse
16 from django.shortcuts import render, get_object_or_404, redirect
17 from django.views.decorators.csrf import csrf_exempt
18 from django.views.generic import TemplateView
19 
20 from readthedocs.builds.models import Version
21 from readthedocs.core.utils import broadcast
22 from readthedocs.projects.models import Project, ImportedFile
23 from readthedocs.projects.tasks import remove_dir
24 from readthedocs.redirects.utils import get_redirect_response
25 
26 log = logging.getLogger(__name__)
27 
28 
29 class NoProjectException(Exception):
30     pass
31 
32 
33 class HomepageView(TemplateView):
34 
35     template_name = 'homepage.html'
36 
37     def get_context_data(self, **kwargs):
38         """Add latest builds and featured projects."""
39         context = super(HomepageView, self).get_context_data(**kwargs)
40         context['featured_list'] = Project.objects.filter(featured=True)
41         context['projects_count'] = Project.objects.count()
42         return context
43 
44 
45 class SupportView(TemplateView):
46     template_name = 'support.html'
47 
48     def get_context_data(self, **kwargs):
49         context = super(SupportView, self).get_context_data(**kwargs)
50         support_email = getattr(settings, 'SUPPORT_EMAIL', None)
51         if not support_email:
52             support_email = 'support@{domain}'.format(
53                 domain=getattr(
54                     settings,
55                     'PRODUCTION_DOMAIN',
56                     'readthedocs.org',
57                 ),
58             )
59 
60         context['support_email'] = support_email
61         return context
62 
63 
64 def random_page(request, project_slug=None):  # pylint: disable=unused-argument
65     imported_file = ImportedFile.objects.order_by('?')
66     if project_slug:
67         imported_file = imported_file.filter(project__slug=project_slug)
68     imported_file = imported_file.first()
69     if imported_file is None:
70         raise Http404
71     url = imported_file.get_absolute_url()
72     return HttpResponseRedirect(url)
73 
74 
75 @csrf_exempt
76 def wipe_version(request, project_slug, version_slug):
77     version = get_object_or_404(
78         Version,
79         project__slug=project_slug,
80         slug=version_slug,
81     )
82     # We need to check by ``for_admin_user`` here to allow members of the
83     # ``Admin`` team (which doesn't own the project) under the corporate site.
84     if version.project not in Project.objects.for_admin_user(user=request.user):
85         raise Http404('You must own this project to wipe it.')
86 
87     if request.method == 'POST':
88         del_dirs = [
89             os.path.join(version.project.doc_path, 'checkouts', version.slug),
90             os.path.join(version.project.doc_path, 'envs', version.slug),
91             os.path.join(version.project.doc_path, 'conda', version.slug),
92         ]
93         for del_dir in del_dirs:
94             broadcast(type='build', task=remove_dir, args=[del_dir])
95         return redirect('project_version_list', project_slug)
96     return render(
97         request,
98         'wipe_version.html',
99         {'version': version, 'project': version.project},
100     )
101 
102 
103 def server_error_500(request, template_name='500.html'):
104     """A simple 500 handler so we get media."""
105     r = render(request, template_name)
106     r.status_code = 500
107     return r
108 
109 
110 def server_error_404(request, exception=None, template_name='404.html'):  # pylint: disable=unused-argument  # noqa
111     """
112     A simple 404 handler so we get media.
113 
114     .. note::
115 
116         Marking exception as optional to make /404/ testing page to work.
117     """
118     response = get_redirect_response(request, path=request.get_full_path())
119     if response:
120         return response
121     r = render(request, template_name)
122     r.status_code = 404
123     return r
124 
125 
126 def do_not_track(request):
127     dnt_header = request.META.get('HTTP_DNT')
128 
129     # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation
130     return JsonResponse({   # pylint: disable=redundant-content-type-for-json-response
131         'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',
132         'same-party': [
133             'readthedocs.org',
134             'readthedocs.com',
135             'readthedocs.io',           # .org Documentation Sites
136             'readthedocs-hosted.com',   # .com Documentation Sites
137         ],
138         'tracking': 'N' if dnt_header == '1' else 'T',
139     }, content_type='application/tracking-status+json')
140
```
--- 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/core/views/__init__.py b/readthedocs/core/views/__init__.py
--- a/readthedocs/core/views/__init__.py
+++ b/readthedocs/core/views/__init__.py
@@ -116,8 +116,16 @@
         Marking exception as optional to make /404/ testing page to work.
     """
     response = get_redirect_response(request, path=request.get_full_path())
+
     if response:
-        return response
+        if response.url == request.build_absolute_uri():
+            # check that we do have a response and avoid infinite redirect
+            log.warning(
+                'Infinite Redirect: FROM URL is the same than TO URL. url=%s',
+                response.url,
+            )
+        else:
+            return response
     r = render(request, template_name)
     r.status_code = 404
     return r

{"golden_diff": "diff --git a/readthedocs/core/views/__init__.py b/readthedocs/core/views/__init__.py\n--- a/readthedocs/core/views/__init__.py\n+++ b/readthedocs/core/views/__init__.py\n@@ -116,8 +116,16 @@\n         Marking exception as optional to make /404/ testing page to work.\n     \"\"\"\n     response = get_redirect_response(request, path=request.get_full_path())\n+\n     if response:\n-        return response\n+        if response.url == request.build_absolute_uri():\n+            # check that we do have a response and avoid infinite redirect\n+            log.warning(\n+                'Infinite Redirect: FROM URL is the same than TO URL. url=%s',\n+                response.url,\n+            )\n+        else:\n+            return response\n     r = render(request, template_name)\n     r.status_code = 404\n     return r\n", "issue": "Redirect full path to default version\nI'd like to be able to create versionless links to the documentation, for use in error messages, code comments, etc. For example, a message like `see https://click.palletsprojects.com/windows for more information`. I don't want to use URLs with versions because I would have to remember to modify all instances of it before releasing a new version.\r\n\r\nCurrently, only the root path redirects to the default version, other paths raise a 404. Instead, the path should be preserved and appended to the default version path on redirect.\r\n\r\n```\r\nWorks:\r\nhttps://click.palletsprojects.com/ -> https://click.palletsprojects.com/en/7.x/\r\n\r\nDoesn't work, 404:\r\nhttps://click.palletsprojects.com/windows -> https://click.palletsprojects.com/en/7.x/windows\r\n```\r\n\r\nI do not want to use the \"latest\" or \"stable\" versions because I would like the URLs that people land on and share to contain the actual version.\r\n\r\nI already do this with the transitional redirects I set up from `click.pocoo.org` to `click.palletsprojects.com`. A similar approach could probably be used to extend RTD's default redirect.\r\n\r\n```nginx\r\nlocation ~ ^/dev(.*)$ {\r\n  return 301 https://click.palletsprojects.com/en/master$1;\r\n}\r\n\r\nlocation ~ ^/(\\d)(.*)$ {\r\n  return 301 https://click.palletsprojects.com/en/$1.x$2;\r\n}\r\n\r\nlocation ~ ^/latest(.*)$ {\r\n  return 301 https://click.palletsprojects.com/en/7.x$1;\r\n}\r\n\r\nlocation / {\r\n  return 301 https://click.palletsprojects.com/en/7.x$request_uri;\r\n}\r\n```\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nCore views, including the main homepage,\n\ndocumentation and header rendering, and server errors.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nimport os\nimport logging\n\nfrom django.conf import settings\nfrom django.http import HttpResponseRedirect, Http404, JsonResponse\nfrom django.shortcuts import render, get_object_or_404, redirect\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import TemplateView\n\nfrom readthedocs.builds.models import Version\nfrom readthedocs.core.utils import broadcast\nfrom readthedocs.projects.models import Project, ImportedFile\nfrom readthedocs.projects.tasks import remove_dir\nfrom readthedocs.redirects.utils import get_redirect_response\n\nlog = logging.getLogger(__name__)\n\n\nclass NoProjectException(Exception):\n    pass\n\n\nclass HomepageView(TemplateView):\n\n    template_name = 'homepage.html'\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Add latest builds and featured projects.\"\"\"\n        context = super(HomepageView, self).get_context_data(**kwargs)\n        context['featured_list'] = Project.objects.filter(featured=True)\n        context['projects_count'] = Project.objects.count()\n        return context\n\n\nclass SupportView(TemplateView):\n    template_name = 'support.html'\n\n    def get_context_data(self, **kwargs):\n        context = super(SupportView, self).get_context_data(**kwargs)\n        support_email = getattr(settings, 'SUPPORT_EMAIL', None)\n        if not support_email:\n            support_email = 'support@{domain}'.format(\n                domain=getattr(\n                    settings,\n                    'PRODUCTION_DOMAIN',\n                    'readthedocs.org',\n                ),\n            )\n\n        context['support_email'] = support_email\n        return context\n\n\ndef random_page(request, project_slug=None):  # pylint: disable=unused-argument\n    imported_file = ImportedFile.objects.order_by('?')\n    if project_slug:\n        imported_file = imported_file.filter(project__slug=project_slug)\n    imported_file = imported_file.first()\n    if imported_file is None:\n        raise Http404\n    url = imported_file.get_absolute_url()\n    return HttpResponseRedirect(url)\n\n\n@csrf_exempt\ndef wipe_version(request, project_slug, version_slug):\n    version = get_object_or_404(\n        Version,\n        project__slug=project_slug,\n        slug=version_slug,\n    )\n    # We need to check by ``for_admin_user`` here to allow members of the\n    # ``Admin`` team (which doesn't own the project) under the corporate site.\n    if version.project not in Project.objects.for_admin_user(user=request.user):\n        raise Http404('You must own this project to wipe it.')\n\n    if request.method == 'POST':\n        del_dirs = [\n            os.path.join(version.project.doc_path, 'checkouts', version.slug),\n            os.path.join(version.project.doc_path, 'envs', version.slug),\n            os.path.join(version.project.doc_path, 'conda', version.slug),\n        ]\n        for del_dir in del_dirs:\n            broadcast(type='build', task=remove_dir, args=[del_dir])\n        return redirect('project_version_list', project_slug)\n    return render(\n        request,\n        'wipe_version.html',\n        {'version': version, 'project': version.project},\n    )\n\n\ndef server_error_500(request, template_name='500.html'):\n    \"\"\"A simple 500 handler so we get media.\"\"\"\n    r = render(request, template_name)\n    r.status_code = 500\n    return r\n\n\ndef server_error_404(request, exception=None, template_name='404.html'):  # pylint: disable=unused-argument  # noqa\n    \"\"\"\n    A simple 404 handler so we get media.\n\n    .. note::\n\n        Marking exception as optional to make /404/ testing page to work.\n    \"\"\"\n    response = get_redirect_response(request, path=request.get_full_path())\n    if response:\n        return response\n    r = render(request, template_name)\n    r.status_code = 404\n    return r\n\n\ndef do_not_track(request):\n    dnt_header = request.META.get('HTTP_DNT')\n\n    # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation\n    return JsonResponse({   # pylint: disable=redundant-content-type-for-json-response\n        'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',\n        'same-party': [\n            'readthedocs.org',\n            'readthedocs.com',\n            'readthedocs.io',           # .org Documentation Sites\n            'readthedocs-hosted.com',   # .com Documentation Sites\n        ],\n        'tracking': 'N' if dnt_header == '1' else 'T',\n    }, content_type='application/tracking-status+json')\n", "path": "readthedocs/core/views/__init__.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\"\nCore views, including the main homepage,\n\ndocumentation and header rendering, and server errors.\n\"\"\"\n\nfrom __future__ import absolute_import\nfrom __future__ import division\nimport os\nimport logging\n\nfrom django.conf import settings\nfrom django.http import HttpResponseRedirect, Http404, JsonResponse\nfrom django.shortcuts import render, get_object_or_404, redirect\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.generic import TemplateView\n\nfrom readthedocs.builds.models import Version\nfrom readthedocs.core.utils import broadcast\nfrom readthedocs.projects.models import Project, ImportedFile\nfrom readthedocs.projects.tasks import remove_dir\nfrom readthedocs.redirects.utils import get_redirect_response\n\nlog = logging.getLogger(__name__)\n\n\nclass NoProjectException(Exception):\n    pass\n\n\nclass HomepageView(TemplateView):\n\n    template_name = 'homepage.html'\n\n    def get_context_data(self, **kwargs):\n        \"\"\"Add latest builds and featured projects.\"\"\"\n        context = super(HomepageView, self).get_context_data(**kwargs)\n        context['featured_list'] = Project.objects.filter(featured=True)\n        context['projects_count'] = Project.objects.count()\n        return context\n\n\nclass SupportView(TemplateView):\n    template_name = 'support.html'\n\n    def get_context_data(self, **kwargs):\n        context = super(SupportView, self).get_context_data(**kwargs)\n        support_email = getattr(settings, 'SUPPORT_EMAIL', None)\n        if not support_email:\n            support_email = 'support@{domain}'.format(\n                domain=getattr(\n                    settings,\n                    'PRODUCTION_DOMAIN',\n                    'readthedocs.org',\n                ),\n            )\n\n        context['support_email'] = support_email\n        return context\n\n\ndef random_page(request, project_slug=None):  # pylint: disable=unused-argument\n    imported_file = ImportedFile.objects.order_by('?')\n    if project_slug:\n        imported_file = imported_file.filter(project__slug=project_slug)\n    imported_file = imported_file.first()\n    if imported_file is None:\n        raise Http404\n    url = imported_file.get_absolute_url()\n    return HttpResponseRedirect(url)\n\n\n@csrf_exempt\ndef wipe_version(request, project_slug, version_slug):\n    version = get_object_or_404(\n        Version,\n        project__slug=project_slug,\n        slug=version_slug,\n    )\n    # We need to check by ``for_admin_user`` here to allow members of the\n    # ``Admin`` team (which doesn't own the project) under the corporate site.\n    if version.project not in Project.objects.for_admin_user(user=request.user):\n        raise Http404('You must own this project to wipe it.')\n\n    if request.method == 'POST':\n        del_dirs = [\n            os.path.join(version.project.doc_path, 'checkouts', version.slug),\n            os.path.join(version.project.doc_path, 'envs', version.slug),\n            os.path.join(version.project.doc_path, 'conda', version.slug),\n        ]\n        for del_dir in del_dirs:\n            broadcast(type='build', task=remove_dir, args=[del_dir])\n        return redirect('project_version_list', project_slug)\n    return render(\n        request,\n        'wipe_version.html',\n        {'version': version, 'project': version.project},\n    )\n\n\ndef server_error_500(request, template_name='500.html'):\n    \"\"\"A simple 500 handler so we get media.\"\"\"\n    r = render(request, template_name)\n    r.status_code = 500\n    return r\n\n\ndef server_error_404(request, exception=None, template_name='404.html'):  # pylint: disable=unused-argument  # noqa\n    \"\"\"\n    A simple 404 handler so we get media.\n\n    .. note::\n\n        Marking exception as optional to make /404/ testing page to work.\n    \"\"\"\n    response = get_redirect_response(request, path=request.get_full_path())\n\n    if response:\n        if response.url == request.build_absolute_uri():\n            # check that we do have a response and avoid infinite redirect\n            log.warning(\n                'Infinite Redirect: FROM URL is the same than TO URL. url=%s',\n                response.url,\n            )\n        else:\n            return response\n    r = render(request, template_name)\n    r.status_code = 404\n    return r\n\n\ndef do_not_track(request):\n    dnt_header = request.META.get('HTTP_DNT')\n\n    # https://w3c.github.io/dnt/drafts/tracking-dnt.html#status-representation\n    return JsonResponse({   # pylint: disable=redundant-content-type-for-json-response\n        'policy': 'https://docs.readthedocs.io/en/latest/privacy-policy.html',\n        'same-party': [\n            'readthedocs.org',\n            'readthedocs.com',\n            'readthedocs.io',           # .org Documentation Sites\n            'readthedocs-hosted.com',   # .com Documentation Sites\n        ],\n        'tracking': 'N' if dnt_header == '1' else 'T',\n    }, content_type='application/tracking-status+json')\n", "path": "readthedocs/core/views/__init__.py"}]}