Repository: readthedocs/readthedocs.org

Issue:
"Default branch: latest" does not exist
* Read the Docs project: https://readthedocs.org/projects/test-builds/

Going to `Advanced settings` the `Default branch` selector shows `latest` as an option. I think this is invalid since the repo has no `latest` branch: https://github.com/rtfd/test-builds/branches/all?utf8=%E2%9C%93&query=latest

In this particular project seems that this is not a real problem, since the build that was triggered after saving it to this branch [picked the `origin/master` anyway](https://readthedocs.org/projects/test-builds/builds/8694899/) but I saw other projects failing for this (in the corporate site).

Anyway, `latest` should not be an option since it's not a valid branch on the repository.


Discussion:
@humitos I went Through the Code for this. it seems like we are creating a duplicate version for `LATEST` an `STABLE`

![screenshot from 2019-03-05 00-34-44](https://user-images.githubusercontent.com/24854406/53754601-a0073700-3ede-11e9-9591-baf3bbf47efe.png)

and in the forms we are showing all the `Versions` as the `default_branch` choices
https://github.com/rtfd/readthedocs.org/blob/c428dd336992fd8716fc53180b626ba7925ba9b1/readthedocs/projects/forms.py#L231-L241

so should we remove latest and stable from the choices as they are not valid branch ?
if I'm Wrong please let me know how it should be handled.
The project has a `latest` version https://readthedocs.org/projects/test-builds/versions/, it points to master. That version we create automatically.
I think the related issue is because of https://github.com/rtfd/readthedocs.org/issues/5358
Also, I think of this more like an alias. Actually the name is incorrect, it should be "default version" (but we already use that name for another setting!).

I say this because, it can be a tag too.
The extra origin issue is fixed in https://github.com/rtfd/readthedocs.org/pull/5523
I propose _re-propossing_ this issue to change the name of this setting to something more accurate p:
If a project doesn't have a _branch_ or _tag_ called `latest` and you select `Default branch: latest` from the admin, does not fail the build when triggering `latest`?
With the current PR no, it's doesn't. A build is triggered to master or whatever alias was used
OK. I still think that `latest` should not appear there, but not failing is better than what we have now :)
Yeah, latest shouldn't be there. What do you think about including `stable`? I think neither should be listed there (actually they should if the user put those branches/tasg)., we should filter for `machine=False` I think.
> Yeah, latest shouldn't be there. What do you think about including `stable`? I think neither should be listed there (actually they should if the user put those branches/tasg)., we should filter for `machine=False` I think.

I was proposing something like this on my initial response. I think this is a good way to go :+1: 
@stsewd yeah, `stable` shouldn't be there either. Filtering by `machine=False` looks good to me :+1: 

Resolving patch:
diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py
--- a/readthedocs/projects/forms.py
+++ b/readthedocs/projects/forms.py
@@ -242,14 +242,13 @@ def __init__(self, *args, **kwargs):
         self.helper.add_input(Submit('save', _('Save')))
 
         default_choice = (None, '-' * 9)
-        # commit_name is used as the id because it handles
-        # the special cases of LATEST and STABLE.
-        all_versions_choices = [
-            (v.commit_name, v.verbose_name)
-            for v in self.instance.versions.all()
-        ]
+        versions_choices = self.instance.versions.filter(
+            machine=False).values_list('verbose_name', flat=True)
+
         self.fields['default_branch'].widget = forms.Select(
-            choices=[default_choice] + all_versions_choices,
+            choices=[default_choice] + list(
+                zip(versions_choices, versions_choices)
+            ),
         )
 
         active_versions = self.get_all_active_versions()


Test patch:
diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py
--- a/readthedocs/rtd_tests/tests/test_project_forms.py
+++ b/readthedocs/rtd_tests/tests/test_project_forms.py
@@ -5,7 +5,7 @@
 from django_dynamic_fixture import get
 from textclassifier.validators import ClassifierValidator
 
-from readthedocs.builds.constants import LATEST
+from readthedocs.builds.constants import LATEST, STABLE
 from readthedocs.builds.models import Version
 from readthedocs.projects.constants import (
     PRIVATE,
@@ -261,31 +261,127 @@ def test_list_only_active_versions_on_default_version(self):
             {'latest', 'public-1', 'public-2', 'private', 'protected'},
         )
 
-    def test_list_all_versions_on_default_branch(self):
+    def test_default_version_field_if_no_active_version(self):
+        project_1 = get(Project)
+        project_1.versions.filter(active=True).update(active=False)
+
+        # No active versions of project exists
+        self.assertFalse(project_1.versions.filter(active=True).exists())
+
+        form = ProjectAdvancedForm(instance=project_1)
+        self.assertTrue(form.fields['default_version'].widget.attrs['readonly'])
+        self.assertEqual(form.fields['default_version'].initial, 'latest')
+
+
+class TestProjectAdvancedFormDefaultBranch(TestCase):
+
+    def setUp(self):
+        self.project = get(Project)
+        user_created_stable_version = get(
+            Version,
+            project=self.project,
+            slug='stable',
+            active=True,
+            privacy_level=PUBLIC,
+            identifier='ab96cbff71a8f40a4340aaf9d12e6c10',
+            verbose_name='stable',
+        )
+        get(
+            Version,
+            project=self.project,
+            slug='public-1',
+            active=True,
+            privacy_level=PUBLIC,
+            identifier='public-1',
+            verbose_name='public-1',
+        )
+        get(
+            Version,
+            project=self.project,
+            slug='private',
+            active=True,
+            privacy_level=PRIVATE,
+            identifier='private',
+            verbose_name='private',
+        )
+        get(
+            Version,
+            project=self.project,
+            slug='protected',
+            active=True,
+            privacy_level=PROTECTED,
+            identifier='protected',
+            verbose_name='protected',
+        )
+
+    def test_list_only_non_auto_generated_versions_on_default_branch(self):
         form = ProjectAdvancedForm(instance=self.project)
         # This version is created automatically by the project on save
-        self.assertTrue(self.project.versions.filter(slug=LATEST).exists())
+        latest = self.project.versions.filter(slug=LATEST)
+        self.assertTrue(latest.exists())
+        # show only the versions that are not auto generated as choices
         self.assertEqual(
             {
                 identifier
                 for identifier, _ in form.fields['default_branch'].widget.choices
             },
             {
-                None, 'master', 'public-1', 'public-2',
-                'public-3', 'public/4', 'protected', 'private',
+                None, 'stable', 'public-1', 'protected', 'private',
             },
         )
+        # Auto generated version `latest` should not be among the choices
+        self.assertNotIn(
+            latest.first().verbose_name,
+            [identifier for identifier, _ in form.fields[
+                'default_branch'].widget.choices],
+        )
 
-    def test_default_version_field_if_no_active_version(self):
-        project_1 = get(Project)
-        project_1.versions.filter(active=True).update(active=False)
+    def test_list_user_created_latest_and_stable_versions_on_default_branch(self):
+        self.project.versions.filter(slug=LATEST).first().delete()
+        user_created_latest_version = get(
+            Version,
+            project=self.project,
+            slug='latest',
+            active=True,
+            privacy_level=PUBLIC,
+            identifier='ab96cbff71a8f40a4240aaf9d12e6c10',
+            verbose_name='latest',
+        )
+        form = ProjectAdvancedForm(instance=self.project)
+        # This version is created by the user
+        latest = self.project.versions.filter(slug=LATEST)
+        # This version is created by the user
+        stable = self.project.versions.filter(slug=STABLE)
 
-        # No active versions of project exists
-        self.assertFalse(project_1.versions.filter(active=True).exists())
+        self.assertIn(
+            latest.first().verbose_name,
+            [identifier for identifier, _ in form.fields[
+                'default_branch'].widget.choices],
+        )
+        self.assertIn(
+            stable.first().verbose_name,
+            [identifier for identifier, _ in form.fields[
+                'default_branch'].widget.choices],
+        )
 
-        form = ProjectAdvancedForm(instance=project_1)
-        self.assertTrue(form.fields['default_version'].widget.attrs['readonly'])
-        self.assertEqual(form.fields['default_version'].initial, 'latest')
+    def test_commit_name_not_in_default_branch_choices(self):
+        form = ProjectAdvancedForm(instance=self.project)
+        # This version is created by the user
+        latest = self.project.versions.filter(slug=LATEST)
+        # This version is created by the user
+        stable = self.project.versions.filter(slug=STABLE)
+
+        # `commit_name` can not be used as the value for the choices
+        self.assertNotIn(
+            latest.first().commit_name,
+            [identifier for identifier, _ in form.fields[
+                'default_branch'].widget.choices],
+        )
+        self.assertNotIn(
+            stable.first().commit_name,
+            [identifier for identifier, _ in form.fields[
+                'default_branch'].widget.choices],
+        )
 
 
 class TestTranslationForms(TestCase):