#4936 Add the possibility to set the default branch at project creation
Merged 4 years ago by pingou. Opened 4 years ago by pingou.

file modified
+3
@@ -146,6 +146,9 @@ 

      EPLUGINCHANGENOTALLOWED = "This plugin cannot be changed"

      EPLUGINNOTINSTALLED = "Project doesn't have this plugin installed"

      ENOTAG = "Tag not found"

+     EMIRRORINGDISABLED = (

+         "Mirroring external project has been disabled in this instance"

+     )

  

  

  def get_authorized_api_project(session, repo, user=None, namespace=None):

file modified
+17
@@ -1389,6 +1389,10 @@ 

      |                            |         |              |   added to the project on |

      |                            |         |              |   creation.               |

      +----------------------------+---------+--------------+---------------------------+

+     | ``default_branch``         | stringn | Optional     | | Name of the default     |

+     |                            |         |              |   branch of the git       |

+     |                            |         |              |   repository.             |

+     +----------------------------+---------+--------------+---------------------------+

      | ``private``                | boolean | Optional     | | A boolean to specify if |

      |                            |         |              |   the project to create   |

      |                            |         |              |   is private.             |
@@ -1397,6 +1401,11 @@ 

      |                            |         |              |   projects, confirm this  |

      |                            |         |              |   with your administrators|

      +----------------------------+---------+--------------+---------------------------+

+     | ``mirrored_from``          | stringn | Optional     | | The public URL of a git |

+     |                            |         |              |   repository that this    |

+     |                            |         |              |   project is mirroring on |

+     |                            |         |              |   this pagure instance.   |

+     +----------------------------+---------+--------------+---------------------------+

      | ``ignore_existing_repos``  | boolean | Optional     | | Only available to admins|

      |                            |         |              |   this option allows them |

      |                            |         |              |   to make project creation|
@@ -1472,6 +1481,12 @@ 

          else:

              ignore_existing_repos = False

  

+         mirrored_from = form.mirrored_from.data

+         if mirrored_from and pagure_config.get("DISABLE_MIRROR_IN", False):

+             raise pagure.exceptions.APIError(

+                 400, error_code=APIERROR.EMIRRORINGDISABLED

+             )

+ 

          try:

              task = pagure.lib.query.new_project(

                  flask.g.session,
@@ -1484,6 +1499,7 @@ 

                  url=url,

                  avatar_email=avatar_email,

                  user=flask.g.fas_user.username,

+                 mirrored_from=mirrored_from,

                  blacklist=pagure_config["BLACKLISTED_PROJECTS"],

                  allowed_prefix=pagure_config["ALLOWED_PREFIX"],

                  add_readme=create_readme,
@@ -1492,6 +1508,7 @@ 

                      "OLD_VIEW_COMMIT_ENABLED", False

                  ),

                  user_ns=pagure_config.get("USER_NAMESPACE", False),

+                 default_branch=form.default_branch.data,

              )

              flask.g.session.commit()

              output = {"message": "Project creation queued", "taskid": task.id}

file modified
+3
@@ -206,6 +206,9 @@ 

          coerce=convert_value,

          default=pagure_config["REPOSPANNER_NEW_REPO"],

      )

+     default_branch = wtforms.StringField(

+         "Default branch", [wtforms.validators.optional()],

+     )

  

      def __init__(self, *args, **kwargs):

          """ Calls the default constructor with the normal argument but

file modified
+7 -1
@@ -1616,6 +1616,7 @@ 

      user_ns=False,

      ignore_existing_repo=False,

      private=False,

+     default_branch=None,

  ):

      """ Create a new project based on the information provided.

  
@@ -1712,7 +1713,12 @@ 

      )

  

      return pagure.lib.tasks.create_project.delay(

-         user_obj.username, namespace, name, add_readme, ignore_existing_repo

+         user_obj.username,

+         namespace,

+         name,

+         add_readme,

+         ignore_existing_repo,

+         default_branch,

      )

  

  

file modified
+18 -1
@@ -213,7 +213,14 @@ 

  @conn.task(queue=pagure_config.get("FAST_CELERY_QUEUE", None), bind=True)

  @pagure_task

  def create_project(

-     self, session, username, namespace, name, add_readme, ignore_existing_repo

+     self,

+     session,

+     username,

+     namespace,

+     name,

+     add_readme,

+     ignore_existing_repo,

+     default_branch=None,

  ):

      """ Create a project.

  
@@ -231,6 +238,9 @@ 

      :kwarg ignore_existing_repo: a boolean specifying whether the creation

          of the project should fail if the repo exists on disk or not

      :type ignore_existing_repo: bool

+     :kwarg default_branch: the name of the default branch to create and set

+         as default.

+     :type default_branch: str or None

  

      """

      project = pagure.lib.query._get_project(
@@ -272,12 +282,19 @@ 

          session.commit()

          raise

  

+     if default_branch:

+         path = project.repopath("main")

+         repo_obj = pygit2.Repository(path)

+         repo_obj.set_head("refs/heads/%s" % default_branch)

+ 

      if add_readme:

          with project.lock("WORKER"):

              with pagure.lib.git.TemporaryClone(

                  project, "main", "add_readme"

              ) as tempclone:

                  temp_gitrepo = tempclone.repo

+                 if default_branch:

+                     temp_gitrepo.set_head("refs/heads/%s" % default_branch)

  

                  # Add README file

                  author = userobj.fullname or userobj.user

@@ -18,6 +18,7 @@ 

              {{ render_bootstrap_field(form.name, field_description="the name of your project") }}

              {{ render_bootstrap_field(form.description, field_description="short description of the project") }}

              {{ render_bootstrap_field(form.namespace, field_description="namespace of the project") }}

+             {{ render_bootstrap_field(form.default_branch, field_description="Name of the default branch in the git repo") }}

              {{ render_bootstrap_field(form.url, field_description="url of the project's website") }}

              {{ render_bootstrap_field(form.avatar_email, field_description="libravatar email address avatar email") }}

              {% if config.get('PRIVATE_PROJECTS', False) %}

@@ -290,23 +290,24 @@ 

                      {% endif %}

            </div>

        </div>

-     {% if g.repo_obj and g.repo_obj.is_empty and repo.mirrored_from %}

+     {% if g.repo_obj and (g.repo_obj.is_empty or g.repo_obj.head_is_unborn) and repo.mirrored_from %}

          <div class="alert {% if category == 'error' %}alert-warning{% else %}alert-info{%endif%}" role="alert">

            <p>This repo is brand new and meant to be mirrored from {{

                  repo.mirrored_from }} !</p>

            <p>Mirrored projects are refreshed regularly, please seat tight, code will

            come land soon!</p>

          </div>

-     {% elif g.repo_obj and g.repo_obj.is_empty %}

+     {% elif g.repo_obj and (g.repo_obj.is_empty or g.repo_obj.head_is_unborn) %}

          <div class="alert {% if category == 'error' %}alert-warning{% else %}alert-info{%endif%}" role="alert">

            <p>This repo is brand new!</p>

              {% if g.authenticated and g.repo_committer %}

                <p>If you already have a git repo:</p>

                <pre>git remote add origin {{ config.get('GIT_URL_SSH') }}{{ repo.path }}

- git push -u origin master</pre>

+ git push -u origin {{ g.repo_obj | get_default_branch or "<your main branch>"}}</pre>

  

                <p>If you have not created your git repo yet:</p>

                <pre>git clone {{ config.get('GIT_URL_SSH') }}{{ repo.path }}

+ git checkout -b {{ g.repo_obj | get_default_branch or "<your main branch>"}}

  cd {{ repo.name }}

  touch README.rst

  git add README.rst

file modified
+1
@@ -1074,6 +1074,7 @@ 

                  ),

                  user_ns=pagure_config.get("USER_NAMESPACE", False),

                  ignore_existing_repo=ignore_existing_repos,

+                 default_branch=form.default_branch.data,

              )

              flask.g.session.commit()

              return pagure.utils.wait_for_task(task)

file modified
+25 -1
@@ -17,12 +17,14 @@ 

  

  import textwrap

  import logging

+ import os

  from os.path import splitext

  

  import arrow

+ import bleach

  import flask

  import six

- import bleach

+ import pygit2

  

  from six.moves.urllib.parse import urlparse, parse_qsl

  from jinja2 import escape
@@ -861,3 +863,25 @@ 

          _log.exception("Failed to get stats on a patch")

          output = {}

      return output

+ 

+ 

+ @UI_NS.app_template_filter("get_default_branch")

+ def get_default_branch(repo):

+     """ Given a pygit2.Repository object, extracts the default branch. """

+     default_branch = None

+ 

+     try:

+         default_branch = repo.head.shorthand

+     except pygit2.GitError:

+         pass

+ 

+     if not default_branch:

+         path = repo.path

+         path_head = os.path.join(path, "HEAD")

+         if os.path.exists(path_head):

+             with open(path_head) as stream:

+                 data = stream.read().strip()

+             if data:

+                 default_branch = data.split("refs/heads/", 1)[-1]

+ 

+     return default_branch

@@ -176,7 +176,7 @@ 

          output = self.app.get("/api/0/-/error_codes")

          self.assertEqual(output.status_code, 200)

          data = json.loads(output.get_data(as_text=True))

-         self.assertEqual(len(data), 45)

+         self.assertEqual(len(data), 46)

          self.assertEqual(

              sorted(data.keys()),

              sorted(
@@ -226,6 +226,7 @@ 

                      "EPLUGINCHANGENOTALLOWED",

                      "EPLUGINNOTINSTALLED",

                      "ENOTAG",

+                     "EMIRRORINGDISABLED",

                  ]

              ),

          )

@@ -1987,156 +1987,6 @@ 

                  json.loads(output.get_data(as_text=True)), expected_data

              )

  

-     def test_api_new_project(self):

-         """ Test the api_new_project method of the flask api. """

- 

-         tests.create_projects(self.session)

-         tests.create_projects_git(os.path.join(self.path, "tickets"))

-         tests.create_tokens(self.session)

-         tests.create_tokens_acl(self.session)

- 

-         headers = {"Authorization": "token foo_token"}

- 

-         # Invalid token

-         output = self.app.post("/api/0/new", headers=headers)

-         self.assertEqual(output.status_code, 401)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertEqual(

-             sorted(data.keys()), ["error", "error_code", "errors"]

-         )

-         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])

-         self.assertEqual(

-             pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]

-         )

-         self.assertEqual(data["errors"], "Missing ACLs: create_project")

- 

-         headers = {"Authorization": "token aaabbbcccddd"}

- 

-         # No input

-         output = self.app.post("/api/0/new", headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {

-                     "name": ["This field is required."],

-                     "description": ["This field is required."],

-                 },

-             },

-         )

- 

-         data = {"name": "test"}

- 

-         # Incomplete request

-         output = self.app.post("/api/0/new", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"description": ["This field is required."]},

-             },

-         )

- 

-         data = {"name": "test", "description": "Just a small test project"}

- 

-         # Valid request but repo already exists

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": 'It is not possible to create the repo "test"',

-                 "error_code": "ENOCODE",

-             },

-         )

- 

-         data = {

-             "name": "api1",

-             "description": "Mighty mighty description",

-             "avatar_email": 123,

-         }

- 

-         # invalid avatar_email - number

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"avatar_email": ["avatar_email must be an email"]},

-             },

-         )

- 

-         data = {

-             "name": "api1",

-             "description": "Mighty mighty description",

-             "avatar_email": [1, 2, 3],

-         }

- 

-         # invalid avatar_email - list

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"avatar_email": ["avatar_email must be an email"]},

-             },

-         )

- 

-         data = {

-             "name": "api1",

-             "description": "Mighty mighty description",

-             "avatar_email": True,

-         }

- 

-         # invalid avatar_email - boolean

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"avatar_email": ["avatar_email must be an email"]},

-             },

-         )

- 

-         data = {

-             "name": "api1",

-             "description": "Mighty mighty description",

-             "avatar_email": "mighty@email.com",

-         }

- 

-         # valid avatar_email

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(data, {"message": 'Project "api1" created'})

- 

-         data = {

-             "name": "test_42",

-             "description": "Just another small test project",

-         }

- 

-         # Valid request

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

- 

      @patch.dict(

          "pagure.config.config",

          {
@@ -2194,43 +2044,20 @@ 

              data = json.loads(output.get_data(as_text=True))

              self.assertDictEqual(data, {"message": 'Project "test" created'})

  

-     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})

-     def test_api_new_project_private(self):

-         """ Test the api_new_project method of the flask api to create

-         a private project. """

- 

+     def test_api_fork_project(self):

+         """ Test the api_fork_project method of the flask api. """

          tests.create_projects(self.session)

-         tests.create_projects_git(os.path.join(self.path, "tickets"))

+         for folder in ["docs", "tickets", "requests", "repos"]:

+             tests.create_projects_git(

+                 os.path.join(self.path, folder), bare=True

+             )

          tests.create_tokens(self.session)

          tests.create_tokens_acl(self.session)

  

-         headers = {"Authorization": "token aaabbbcccddd"}

- 

-         data = {

-             "name": "test",

-             "description": "Just a small test project",

-             "private": True,

-         }

- 

-         # Valid request

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data, {"message": 'Project "pingou/test" created'}

-         )

- 

-     def test_api_new_project_user_token(self):

-         """ Test the api_new_project method of the flask api. """

-         tests.create_projects(self.session)

-         tests.create_projects_git(os.path.join(self.path, "tickets"))

-         tests.create_tokens(self.session, project_id=None)

-         tests.create_tokens_acl(self.session)

- 

          headers = {"Authorization": "token foo_token"}

  

          # Invalid token

-         output = self.app.post("/api/0/new", headers=headers)

+         output = self.app.post("/api/0/fork", headers=headers)

          self.assertEqual(output.status_code, 401)

          data = json.loads(output.get_data(as_text=True))

          self.assertEqual(
@@ -2240,12 +2067,12 @@ 

          self.assertEqual(

              pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]

          )

-         self.assertEqual(data["errors"], "Missing ACLs: create_project")

+         self.assertEqual(data["errors"], "Missing ACLs: fork_project")

  

          headers = {"Authorization": "token aaabbbcccddd"}

  

          # No input

-         output = self.app.post("/api/0/new", headers=headers)

+         output = self.app.post("/api/0/fork", headers=headers)

          self.assertEqual(output.status_code, 400)

          data = json.loads(output.get_data(as_text=True))

          self.assertDictEqual(
@@ -2253,17 +2080,14 @@ 

              {

                  "error": "Invalid or incomplete input submitted",

                  "error_code": "EINVALIDREQ",

-                 "errors": {

-                     "name": ["This field is required."],

-                     "description": ["This field is required."],

-                 },

+                 "errors": {"repo": ["This field is required."]},

              },

          )

  

          data = {"name": "test"}

  

          # Incomplete request

-         output = self.app.post("/api/0/new", data=data, headers=headers)

+         output = self.app.post("/api/0/fork", data=data, headers=headers)

          self.assertEqual(output.status_code, 400)

          data = json.loads(output.get_data(as_text=True))

          self.assertDictEqual(
@@ -2271,206 +2095,49 @@ 

              {

                  "error": "Invalid or incomplete input submitted",

                  "error_code": "EINVALIDREQ",

-                 "errors": {"description": ["This field is required."]},

+                 "errors": {"repo": ["This field is required."]},

              },

          )

  

-         data = {"name": "test", "description": "Just a small test project"}

+         data = {"repo": "test"}

  

-         # Valid request but repo already exists

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         # Valid request

+         output = self.app.post("/api/0/fork/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data, {"message": 'Repo "test" cloned to "pingou/test"'}

+         )

+ 

+         data = {"repo": "test"}

+ 

+         # project already forked

+         output = self.app.post("/api/0/fork/", data=data, headers=headers)

          self.assertEqual(output.status_code, 400)

          data = json.loads(output.get_data(as_text=True))

          self.assertDictEqual(

              data,

              {

-                 "error": 'It is not possible to create the repo "test"',

+                 "error": 'Repo "forks/pingou/test" already exists',

                  "error_code": "ENOCODE",

              },

          )

  

-         data = {

-             "name": "test_42",

-             "description": "Just another small test project",

-         }

- 

-         # Valid request

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

- 

-         # Project with a namespace

-         pagure.config.config["ALLOWED_PREFIX"] = ["rpms"]

-         data = {

-             "name": "test_42",

-             "namespace": "pingou",

-             "description": "Just another small test project",

-         }

+         data = {"repo": "test", "username": "pingou"}

  

-         # Invalid namespace

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         # Fork already exists

+         output = self.app.post("/api/0/fork/", data=data, headers=headers)

          self.assertEqual(output.status_code, 400)

          data = json.loads(output.get_data(as_text=True))

          self.assertDictEqual(

              data,

              {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"namespace": ["Not a valid choice"]},

+                 "error": 'Repo "forks/pingou/test" already exists',

+                 "error_code": "ENOCODE",

              },

          )

  

-         data = {

-             "name": "test_42",

-             "namespace": "rpms",

-             "description": "Just another small test project",

-         }

- 

-         # All good

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data, {"message": 'Project "rpms/test_42" created'}

-         )

- 

-     @patch.dict("pagure.config.config", {"USER_NAMESPACE": True})

-     def test_api_new_project_user_ns(self):

-         """ Test the api_new_project method of the flask api. """

-         tests.create_projects(self.session)

-         tests.create_projects_git(os.path.join(self.path, "tickets"))

-         tests.create_tokens(self.session)

-         tests.create_tokens_acl(self.session)

- 

-         headers = {"Authorization": "token aaabbbcccddd"}

- 

-         # Create a project with the user namespace feature on

-         data = {

-             "name": "testproject",

-             "description": "Just another small test project",

-         }

- 

-         # Valid request

-         output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data, {"message": 'Project "pingou/testproject" created'}

-         )

- 

-         # Create a project with a namespace and the user namespace feature on

-         data = {

-             "name": "testproject2",

-             "namespace": "testns",

-             "description": "Just another small test project",

-         }

- 

-         # Valid request

-         with patch.dict(

-             "pagure.config.config", {"ALLOWED_PREFIX": ["testns"]}

-         ):

-             output = self.app.post("/api/0/new/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data, {"message": 'Project "testns/testproject2" created'}

-         )

- 

-     def test_api_fork_project(self):

-         """ Test the api_fork_project method of the flask api. """

-         tests.create_projects(self.session)

-         for folder in ["docs", "tickets", "requests", "repos"]:

-             tests.create_projects_git(

-                 os.path.join(self.path, folder), bare=True

-             )

-         tests.create_tokens(self.session)

-         tests.create_tokens_acl(self.session)

- 

-         headers = {"Authorization": "token foo_token"}

- 

-         # Invalid token

-         output = self.app.post("/api/0/fork", headers=headers)

-         self.assertEqual(output.status_code, 401)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertEqual(

-             sorted(data.keys()), ["error", "error_code", "errors"]

-         )

-         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])

-         self.assertEqual(

-             pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]

-         )

-         self.assertEqual(data["errors"], "Missing ACLs: fork_project")

- 

-         headers = {"Authorization": "token aaabbbcccddd"}

- 

-         # No input

-         output = self.app.post("/api/0/fork", headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"repo": ["This field is required."]},

-             },

-         )

- 

-         data = {"name": "test"}

- 

-         # Incomplete request

-         output = self.app.post("/api/0/fork", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": "Invalid or incomplete input submitted",

-                 "error_code": "EINVALIDREQ",

-                 "errors": {"repo": ["This field is required."]},

-             },

-         )

- 

-         data = {"repo": "test"}

- 

-         # Valid request

-         output = self.app.post("/api/0/fork/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 200)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data, {"message": 'Repo "test" cloned to "pingou/test"'}

-         )

- 

-         data = {"repo": "test"}

- 

-         # project already forked

-         output = self.app.post("/api/0/fork/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": 'Repo "forks/pingou/test" already exists',

-                 "error_code": "ENOCODE",

-             },

-         )

- 

-         data = {"repo": "test", "username": "pingou"}

- 

-         # Fork already exists

-         output = self.app.post("/api/0/fork/", data=data, headers=headers)

-         self.assertEqual(output.status_code, 400)

-         data = json.loads(output.get_data(as_text=True))

-         self.assertDictEqual(

-             data,

-             {

-                 "error": 'Repo "forks/pingou/test" already exists',

-                 "error_code": "ENOCODE",

-             },

-         )

- 

-         data = {"repo": "test", "namespace": "pingou"}

+         data = {"repo": "test", "namespace": "pingou"}

  

          # Repo does not exists

          output = self.app.post("/api/0/fork/", data=data, headers=headers)
@@ -4691,5 +4358,425 @@ 

          )

  

  

+ class PagureFlaskApiProjectCreateProjectTests(tests.Modeltests):

+     """ Tests for the flask API of pagure for git branches

+     """

+ 

+     maxDiff = None

+ 

+     def setUp(self):

+         """ Set up the environnment, ran before every tests. """

+         super(PagureFlaskApiProjectCreateProjectTests, self).setUp()

+ 

+         tests.create_projects(self.session)

+         tests.create_projects_git(os.path.join(self.path, "tickets"))

+         tests.create_tokens(self.session)

+         tests.create_tokens(self.session, suffix="_user", project_id=None)

+         tests.create_tokens_acl(self.session)

+         tests.create_tokens_acl(self.session, token_id="aaabbbcccddd_user")

+ 

+     def test_api_new_project_invalid_token(self):

+ 

+         headers = {"Authorization": "token foo_token"}

+ 

+         # Invalid token

+         output = self.app.post("/api/0/new", headers=headers)

+         self.assertEqual(output.status_code, 401)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertEqual(

+             sorted(data.keys()), ["error", "error_code", "errors"]

+         )

+         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])

+         self.assertEqual(

+             pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]

+         )

+         self.assertEqual(data["errors"], "Missing ACLs: create_project")

+ 

+     def test_api_new_project_no_input(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         # No input

+         output = self.app.post("/api/0/new", headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {

+                     "name": ["This field is required."],

+                     "description": ["This field is required."],

+                 },

+             },

+         )

+ 

+     def test_api_new_project_incomplete_request(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"name": "test"}

+ 

+         # Incomplete request

+         output = self.app.post("/api/0/new", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {"description": ["This field is required."]},

+             },

+         )

+ 

+     def test_api_new_project_existing_repo(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {"name": "test", "description": "Just a small test project"}

+ 

+         # Valid request but repo already exists

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": 'It is not possible to create the repo "test"',

+                 "error_code": "ENOCODE",

+             },

+         )

+ 

+     def test_api_new_project_invalid_avatar_email_int(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "api1",

+             "description": "Mighty mighty description",

+             "avatar_email": 123,

+         }

+ 

+         # invalid avatar_email - number

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {"avatar_email": ["avatar_email must be an email"]},

+             },

+         )

+ 

+     def test_api_new_project_invalid_avatar_email_list(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "api1",

+             "description": "Mighty mighty description",

+             "avatar_email": [1, 2, 3],

+         }

+ 

+         # invalid avatar_email - list

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {"avatar_email": ["avatar_email must be an email"]},

+             },

+         )

+ 

+     def test_api_new_project_invalid_avatar_email_bool(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "api1",

+             "description": "Mighty mighty description",

+             "avatar_email": True,

+         }

+ 

+         # invalid avatar_email - boolean

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {"avatar_email": ["avatar_email must be an email"]},

+             },

+         )

+ 

+     def test_api_new_project_with_avatar(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "api1",

+             "description": "Mighty mighty description",

+             "avatar_email": "mighty@email.com",

+         }

+ 

+         # valid avatar_email

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": 'Project "api1" created'})

+ 

+     def test_api_new_project(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "test_42",

+             "description": "Just another small test project",

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

+ 

+     def test_api_new_project_mirrored_from(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "test_42",

+             "description": "Just another small test project",

+             "mirrored_from": "https://pagure.io/pagure/pagure.git",

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

+ 

+         project = pagure.lib.query.get_authorized_project(

+             self.session, "test_42"

+         )

+         self.assertEqual(

+             project.mirrored_from, "https://pagure.io/pagure/pagure.git"

+         )

+ 

+     def test_api_new_project_readme(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "test_42",

+             "description": "Just another small test project",

+             "create_readme": "true",

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

+ 

+         project = pagure.lib.query.get_authorized_project(

+             self.session, "test_42"

+         )

+         repo = pygit2.Repository(project.repopath("main"))

+         self.assertEqual(repo.listall_branches(), ["master"])

+ 

+     def test_api_new_project_readme_default_branch(self):

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+         data = {

+             "name": "test_42",

+             "description": "Just another small test project",

+             "create_readme": "true",

+             "default_branch": "main",

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

+ 

+         project = pagure.lib.query.get_authorized_project(

+             self.session, "test_42"

+         )

+         repo = pygit2.Repository(project.repopath("main"))

+         self.assertEqual(repo.listall_branches(), ["main"])

+ 

+     @patch.dict("pagure.config.config", {"PRIVATE_PROJECTS": True})

+     def test_api_new_project_private(self):

+         """ Test the api_new_project method of the flask api to create

+         a private project. """

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         data = {

+             "name": "test",

+             "description": "Just a small test project",

+             "private": True,

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data, {"message": 'Project "pingou/test" created'}

+         )

+ 

+     def test_api_new_project_user_token(self):

+         """ Test the api_new_project method of the flask api. """

+ 

+         headers = {"Authorization": "token foo_token_user"}

+ 

+         # Invalid token

+         output = self.app.post("/api/0/new", headers=headers)

+         self.assertEqual(output.status_code, 401)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertEqual(

+             sorted(data.keys()), ["error", "error_code", "errors"]

+         )

+         self.assertEqual(pagure.api.APIERROR.EINVALIDTOK.value, data["error"])

+         self.assertEqual(

+             pagure.api.APIERROR.EINVALIDTOK.name, data["error_code"]

+         )

+         self.assertEqual(data["errors"], "Missing ACLs: create_project")

+ 

+         headers = {"Authorization": "token aaabbbcccddd_user"}

+ 

+         # No input

+         output = self.app.post("/api/0/new", headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {

+                     "name": ["This field is required."],

+                     "description": ["This field is required."],

+                 },

+             },

+         )

+ 

+         data = {"name": "test"}

+ 

+         # Incomplete request

+         output = self.app.post("/api/0/new", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {"description": ["This field is required."]},

+             },

+         )

+ 

+         data = {"name": "test", "description": "Just a small test project"}

+ 

+         # Valid request but repo already exists

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": 'It is not possible to create the repo "test"',

+                 "error_code": "ENOCODE",

+             },

+         )

+ 

+         data = {

+             "name": "test_42",

+             "description": "Just another small test project",

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(data, {"message": 'Project "test_42" created'})

+ 

+         # Project with a namespace

+         pagure.config.config["ALLOWED_PREFIX"] = ["rpms"]

+         data = {

+             "name": "test_42",

+             "namespace": "pingou",

+             "description": "Just another small test project",

+         }

+ 

+         # Invalid namespace

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 400)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data,

+             {

+                 "error": "Invalid or incomplete input submitted",

+                 "error_code": "EINVALIDREQ",

+                 "errors": {"namespace": ["Not a valid choice"]},

+             },

+         )

+ 

+         data = {

+             "name": "test_42",

+             "namespace": "rpms",

+             "description": "Just another small test project",

+         }

+ 

+         # All good

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data, {"message": 'Project "rpms/test_42" created'}

+         )

+ 

+     @patch.dict("pagure.config.config", {"USER_NAMESPACE": True})

+     def test_api_new_project_user_ns(self):

+         """ Test the api_new_project method of the flask api. """

+ 

+         headers = {"Authorization": "token aaabbbcccddd"}

+ 

+         # Create a project with the user namespace feature on

+         data = {

+             "name": "testproject",

+             "description": "Just another small test project",

+         }

+ 

+         # Valid request

+         output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data, {"message": 'Project "pingou/testproject" created'}

+         )

+ 

+         # Create a project with a namespace and the user namespace feature on

+         data = {

+             "name": "testproject2",

+             "namespace": "testns",

+             "description": "Just another small test project",

+         }

+ 

+         # Valid request

+         with patch.dict(

+             "pagure.config.config", {"ALLOWED_PREFIX": ["testns"]}

+         ):

+             output = self.app.post("/api/0/new/", data=data, headers=headers)

+         self.assertEqual(output.status_code, 200)

+         data = json.loads(output.get_data(as_text=True))

+         self.assertDictEqual(

+             data, {"message": 'Project "testns/testproject2" created'}

+         )

+ 

+ 

  if __name__ == "__main__":

      unittest.main(verbosity=2)

@@ -658,35 +658,6 @@ 

                  )

              )

  

-     @patch.dict("pagure.config.config", {"CASE_SENSITIVE": True})

-     def test_new_project_case_sensitive(self):

-         tests.create_projects(self.session)

-         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)

- 

-         output = self.app.get("/test")

-         self.assertEqual(output.status_code, 200)

- 

-         output = self.app.get("/TEST")

-         self.assertEqual(output.status_code, 404)

- 

-         user = tests.FakeUser()

-         user.username = "foo"

-         with tests.user_set(self.app.application, user):

-             output = self.app.get("/new/")

-             self.assertEqual(output.status_code, 200)

- 

-             csrf_token = self.get_csrf(output=output)

-             data = {

-                 "description": "TEST",

-                 "name": "TEST",

-                 "csrf_token": csrf_token,

-                 "create_readme": True,

-             }

-             self.app.post("/new/", data=data, follow_redirects=True)

- 

-             output = self.app.get("/TEST")

-             self.assertEqual(output.status_code, 200)

- 

      @patch("pagure.ui.app.admin_session_timedout")

      def test_user_settings(self, ast):

          """ Test the user_settings endpoint. """
@@ -2298,6 +2269,70 @@ 

              )

          )

  

+     @patch.dict("pagure.config.config", {"CASE_SENSITIVE": True})

+     def test_new_project_case_sensitive(self):

+         tests.create_projects(self.session)

+         tests.create_projects_git(os.path.join(self.path, "repos"), bare=True)

+ 

+         output = self.app.get("/test")

+         self.assertEqual(output.status_code, 200)

+ 

+         output = self.app.get("/TEST")

+         self.assertEqual(output.status_code, 404)

+ 

+         user = tests.FakeUser()

+         user.username = "foo"

+         with tests.user_set(self.app.application, user):

+             output = self.app.get("/new/")

+             self.assertEqual(output.status_code, 200)

+ 

+             csrf_token = self.get_csrf(output=output)

+             data = {

+                 "description": "TEST",

+                 "name": "TEST",

+                 "csrf_token": csrf_token,

+                 "create_readme": True,

+             }

+             self.app.post("/new/", data=data, follow_redirects=True)

+             self.assertEqual(output.status_code, 200)

+ 

+             output = self.app.get("/TEST")

+             self.assertEqual(output.status_code, 200)

+ 

+     def test_new_project_readme(self):

+         # Before

+         projects = pagure.lib.query.search_projects(self.session)

+         self.assertEqual(len(projects), 0)

+ 

+         user = tests.FakeUser(username="foo")

+         with tests.user_set(self.app.application, user):

+             csrf_token = self.get_csrf()

+ 

+             data = {

+                 "description": "testproject",

+                 "name": "testproject",

+                 "csrf_token": csrf_token,

+                 "create_readme": True,

+             }

+             output = self.app.post("/new/", data=data, follow_redirects=True)

+             self.assertEqual(output.status_code, 200)

+             output_text = output.get_data(as_text=True)

+             self.assertIn(

+                 "<title>Overview - testproject - Pagure</title>", output_text

+             )

+             self.assertIn(

+                 '<a href="/testproject"><strong>testproject</strong></a>',

+                 output_text,

+             )

+ 

+         # After

+         projects = pagure.lib.query.search_projects(self.session)

+         self.assertEqual(len(projects), 1)

+ 

+         project = pagure.lib.query._get_project(self.session, "testproject")

+         repo = pygit2.Repository(project.repopath("main"))

+         self.assertEqual(repo.listall_branches(), ["master"])

+ 

      @patch.dict("pagure.config.config", {"ENABLE_UI_NEW_PROJECTS": False})

      def test_new_project_when_turned_off_in_the_ui(self):

          """ Test the new_project endpoint when new project creation is
@@ -2426,6 +2461,45 @@ 

          projects = pagure.lib.query.search_projects(self.session)

          self.assertEqual(len(projects), 1)

  

+         repo = pygit2.Repository(projects[0].repopath("main"))

+         self.assertEqual(repo.listall_branches(), [])

+ 

+     def test_new_project_with_default_branch(self):

+         """ Test the new_project endpoint when new project contains a plus sign.

+         """

+         # Before

+         projects = pagure.lib.query.search_projects(self.session)

+         self.assertEqual(len(projects), 0)

+ 

+         user = tests.FakeUser(username="foo")

+         with tests.user_set(self.app.application, user):

+             csrf_token = self.get_csrf()

+ 

+             data = {

+                 "description": "Project #1.",

+                 "name": "project_main",

+                 "csrf_token": csrf_token,

+                 "default_branch": "main",

+                 "create_readme": True,

+             }

+             output = self.app.post("/new/", data=data, follow_redirects=True)

+             self.assertEqual(output.status_code, 200)

+             output_text = output.get_data(as_text=True)

+             self.assertIn(

+                 "<title>Overview - project_main - Pagure</title>", output_text

+             )

+             self.assertIn(

+                 '<a href="/project_main"><strong>project_main</strong></a>',

+                 output_text,

+             )

+ 

+         # After

+         projects = pagure.lib.query.search_projects(self.session)

+         self.assertEqual(len(projects), 1)

+ 

+         repo = pygit2.Repository(projects[0].repopath("main"))

+         self.assertEqual(repo.listall_branches(), ["main"])

+ 

      def test_new_project_when_turned_off(self):

          """ Test the new_project endpoint when new project creation is

          not allowed in the pagure instance. """

@@ -112,11 +112,8 @@ 

          output = self.app.get("/test")

          self.assertEqual(output.status_code, 200)

          output_text = output.get_data(as_text=True)

-         self.assertIn('<section class="no-readme">', output_text)

-         self.assertIn(

-             "The test project's README file is empty or unavailable.",

-             output_text,

-         )

+         self.assertIn("<title>Overview - test - Pagure</title>", output_text)

+         self.assertIn("<p>This repo is brand new!</p>", output_text)

          self.assertEqual(

              output_text.count('<a class="dropdown-item" href="/test/branch/'),

              0,

@@ -2067,7 +2067,7 @@ 

          output = self.app.get("/test")

          self.assertEqual(output.status_code, 200)

          output_text = output.get_data(as_text=True)

-         self.assertNotIn("<p>This repo is brand new!</p>", output_text)

+         self.assertIn("<p>This repo is brand new!</p>", output_text)

          self.assertNotIn("Forked from", output_text)

          self.assertIn("<title>Overview - test - Pagure</title>", output_text)

          self.assertEqual(output_text.count('<span class="commitid">'), 0)

no initial comment

This still need a little work on the API side so it's also supported there, but the UI part can be tested :)

@pingou Could we also get an instance-wide default or forced name setting? For example, @midipix enforces main as the default branch name for projects on dev.midipix.org, but that's done by him running a script after a new project is created to force the branches to change.

Well, with this he won't have to run a script anymore :)

rebased onto 35f86f1624f6c8c3e29fc7b4eabb47432ae068de

4 years ago

3 new commits added

  • Allow setting the default git branch when creating projects via the API
  • Allow creating mirrored project from the API
  • Move all the tests related to creating new project via the API to their own class
4 years ago

rebased onto 57859c0

4 years ago

4 new commits added

  • Allow setting the default git branch when creating projects via the API
  • Allow creating mirrored project from the API
  • Move all the tests related to creating new project via the API to their own class
  • Add the possibility to set the default branch at project creation
4 years ago

Thanks for the review!

Pull-Request has been merged by pingou

4 years ago