#4786 Add a collaborator level to projects.
Merged 3 years ago by pingou. Opened 4 years ago by pingou.

@@ -0,0 +1,30 @@ 

+ """Add branch info to projects_groups

+ 

+ Revision ID: 2b39a728a38f

+ Revises: 318a4793b360

+ Create Date: 2020-03-26 21:50:45.899760

+ 

+ """

+ 

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ # revision identifiers, used by Alembic.

+ revision = '2b39a728a38f'

+ down_revision = '318a4793b360'

+ 

+ 

+ def upgrade():

+     ''' Add the column branches to the table projects_groups.

+     '''

+     op.add_column(

+         'projects_groups',

+         sa.Column('branches', sa.Text, nullable=True)

+     )

+ 

+ 

+ def downgrade():

+     ''' Drop the column branches from the table projects_groups.

+     '''

+     op.drop_column('projects_groups', 'branches')

@@ -0,0 +1,30 @@ 

+ """Add branch info to user_projects

+ 

+ Revision ID: 318a4793b360

+ Revises: 060b20d6d6e6

+ Create Date: 2020-03-26 21:49:17.632967

+ 

+ """

+ 

+ from alembic import op

+ import sqlalchemy as sa

+ 

+ 

+ # revision identifiers, used by Alembic.

+ revision = '318a4793b360'

+ down_revision = '060b20d6d6e6'

+ 

+ 

+ def upgrade():

+     ''' Add the column branches to the table user_projects.

+     '''

+     op.add_column(

+         'user_projects',

+         sa.Column('branches', sa.Text, nullable=True)

+     )

+ 

+ 

+ def downgrade():

+     ''' Drop the column branches from the table user_projects.

+     '''

+     op.drop_column('user_projects', 'branches')

file modified
+5
@@ -395,6 +395,11 @@ 

          flask.g.repo_obj = pygit2.Repository(flask.g.reponame)

          flask.g.repo_admin = pagure.utils.is_repo_admin(flask.g.repo)

          flask.g.repo_committer = pagure.utils.is_repo_committer(flask.g.repo)

+         if flask.g.authenticated and not flask.g.repo_committer:

+             flask.g.repo_committer = flask.g.fas_user.username in [

+                 u.user.username for u in flask.g.repo.collaborators

+             ]

+ 

          flask.g.repo_user = pagure.utils.is_repo_user(flask.g.repo)

          flask.g.branches = sorted(flask.g.repo_obj.listall_branches())

  

file modified
+8
@@ -632,6 +632,10 @@ 

          'Access Level <span class="error">*</span>',

          [wtforms.validators.DataRequired()],

      )

+     branches = wtforms.StringField(

+         'Git branches <span class="error">*</span>',

+         [wtforms.validators.Optional()],

+     )

  

  

  class AddUserToGroupForm(PagureForm):
@@ -666,6 +670,10 @@ 

          'Access Level <span class="error">*</span>',

          [wtforms.validators.DataRequired()],

      )

+     branches = wtforms.StringField(

+         'Git branches <span class="error">*</span>',

+         [wtforms.validators.Optional()],

+     )

  

  

  class ConfirmationForm(PagureForm):

file modified
+4 -3
@@ -126,7 +126,7 @@ 

  @PV.route("/ssh/checkaccess/", methods=["POST"])

  @internal_access_only

  def check_ssh_access():

-     """ Determines whether a user has any access to the requested repo. """

+     """ Determines whether a user has read access to the requested repo. """

      gitdir = flask.request.form["gitdir"]

      remoteuser = flask.request.form["username"]

      _auth_log.info(
@@ -192,8 +192,9 @@ 

          )

          return flask.jsonify({"access": False})

  

-     _log.info("Access granted to %s on: %s" % (remoteuser, project.fullname))

- 

+     _log.info(

+         "Read access granted to %s on: %s" % (remoteuser, project.fullname)

+     )

      return flask.jsonify(

          {

              "access": True,

file modified
+4 -2
@@ -27,7 +27,7 @@ 

  import pagure.lib.query

  from pagure.config import config as pagure_config

  from pagure.lib import model

- from pagure.utils import is_repo_committer, lookup_deploykey

+ from pagure.utils import is_repo_collaborator, lookup_deploykey

  

  

  # logging.config.dictConfig(pagure_config.get('LOGGING') or {'version': 1})
@@ -901,7 +901,9 @@ 

              return False

  

          # Determine whether the current user is allowed to push

-         is_committer = is_repo_committer(project, username, session)

+         is_committer = is_repo_collaborator(

+             project, refname, username, session

+         )

          deploykey = lookup_deploykey(project, username)

          if deploykey is not None:

              self.info("Deploykey used. Push access: %s" % deploykey.pushaccess)

file modified
+48 -6
@@ -142,7 +142,7 @@ 

              session.rollback()

              _log.debug("ACL %s could not be added", acl)

  

-     for access in ["ticket", "commit", "admin"]:

+     for access in ["ticket", "collaborator", "commit", "admin"]:

          access_obj = AccessLevels(access=access)

          session.add(access_obj)

          try:
@@ -443,6 +443,13 @@ 

          viewonly=True,

      )

  

+     collaborators = relation(

+         "ProjectUser",

+         primaryjoin="and_(projects.c.id==user_projects.c.project_id,\

+                     user_projects.c.access=='collaborator')",

+         viewonly=True,

+     )

+ 

      groups = relation(

          "PagureGroup",

          secondary="projects_groups",
@@ -479,6 +486,13 @@ 

          viewonly=True,

      )

  

+     collaborator_groups = relation(

+         "ProjectGroup",

+         primaryjoin="and_(projects.c.id==projects_groups.c.project_id,\

+                     projects_groups.c.access=='collaborator')",

+         viewonly=True,

+     )

+ 

      def __repr__(self):

          return (

              "Project(%s, name:%s, namespace:%s, url:%s, is_fork:%s, "
@@ -905,7 +919,7 @@ 

          :type combine: boolean

          """

  

-         if access not in ["admin", "commit", "ticket"]:

+         if access not in ["admin", "commit", "collaborator", "ticket"]:

              raise pagure.exceptions.AccessLevelNotFound(

                  "The access level does not exist"

              )
@@ -915,6 +929,8 @@ 

                  return self.admins

              elif access == "commit":

                  return self.committers

+             elif access == "collaborator":

+                 return [u.user for u in self.collaborators]

              elif access == "ticket":

                  return self.users

          else:
@@ -924,11 +940,20 @@ 

                  committers = set(self.committers)

                  admins = set(self.admins)

                  return list(committers - admins)

-             elif access == "ticket":

+             elif access == "collaborator":

+                 admins = set(self.admins)

                  committers = set(self.committers)

+                 return list(

+                     set([u.user for u in self.collaborators])

+                     - committers

+                     - admins

+                 )

+             elif access == "ticket":

                  admins = set(self.admins)

+                 committers = set(self.committers)

+                 collaborators = set([u.user for u in self.collaborators])

                  users = set(self.users)

-                 return list(users - committers - admins)

+                 return list(users - collaborators - committers - admins)

  

      def get_project_groups(self, access, combine=True):

          """ Returns the list of groups of the project according
@@ -951,7 +976,7 @@ 

          :type combine: boolean

          """

  

-         if access not in ["admin", "commit", "ticket"]:

+         if access not in ["admin", "commit", "collaborator", "ticket"]:

              raise pagure.exceptions.AccessLevelNotFound(

                  "The access level does not exist"

              )
@@ -961,6 +986,8 @@ 

                  return self.admin_groups

              elif access == "commit":

                  return self.committer_groups

+             elif access == "collaborator":

+                 return self.collaborator_groups

              elif access == "ticket":

                  return self.groups

          else:
@@ -970,11 +997,18 @@ 

                  committers = set(self.committer_groups)

                  admins = set(self.admin_groups)

                  return list(committers - admins)

+             elif access == "collaborator":

+                 committers = set(self.committer_groups)

+                 admins = set(self.admin_groups)

+                 return list(

+                     set(self.collaborator_groups) - committers - admins

+                 )

              elif access == "ticket":

                  committers = set(self.committer_groups)

                  admins = set(self.admin_groups)

+                 collaborators = set(self.collaborator_groups)

                  groups = set(self.groups)

-                 return list(groups - committers - admins)

+                 return list(groups - collaborators - committers - admins)

  

      @property

      def access_users(self):
@@ -989,6 +1023,9 @@ 

                  self.get_project_users(access="commit", combine=False),

                  key=lambda u: u.user,

              ),

+             "collaborator": sorted(

+                 self.get_project_users(access="collaborator", combine=False)

+             ),

              "ticket": sorted(

                  self.get_project_users(access="ticket", combine=False),

                  key=lambda u: u.user,
@@ -1028,6 +1065,9 @@ 

                  self.get_project_groups(access="commit", combine=False),

                  key=lambda x: x.group_name,

              ),

+             "collaborator": sorted(

+                 self.get_project_groups(access="collaborator", combine=False)

+             ),

              "ticket": sorted(

                  self.get_project_groups(access="ticket", combine=False),

                  key=lambda x: x.group_name,
@@ -1171,6 +1211,7 @@ 

          ),

          nullable=False,

      )

+     branches = sa.Column(sa.Text, nullable=True,)

  

      project = relation(

          "Project",
@@ -2707,6 +2748,7 @@ 

          ),

          nullable=False,

      )

+     branches = sa.Column(sa.Text, nullable=True,)

  

      project = relation(

          "Project",

file modified
+39 -6
@@ -1098,7 +1098,13 @@ 

  

  

  def add_user_to_project(

-     session, project, new_user, user, access="admin", required_groups=None

+     session,

+     project,

+     new_user,

+     user,

+     access="admin",

+     branches=None,

+     required_groups=None,

  ):

      """ Add a specified user to a specified project with a specified access

      """
@@ -1127,15 +1133,20 @@ 

      )

      users.add(project.user.user)

  

-     if new_user in users:

+     if new_user in users and access != "collaborator":

          raise pagure.exceptions.PagureException(

              "This user is already listed on this project with the same access"

          )

  

+     # Reset the branches to None if the user isn't a collaborator

+     if access != "collaborator":

+         branches = None

+ 

      # user has some access on project, so update to new access

      if new_user_obj in project.users:

          access_obj = get_obj_access(session, project, new_user_obj)

          access_obj.access = access

+         access_obj.branches = branches

          project.date_modified = datetime.datetime.utcnow()

          update_read_only_mode(session, project, read_only=True)

          session.add(access_obj)
@@ -1149,6 +1160,7 @@ 

                  project=project.to_json(public=True),

                  new_user=new_user_obj.username,

                  new_access=access,

+                 new_branches=branches,

                  agent=user_obj.username,

              ),

          )
@@ -1156,7 +1168,10 @@ 

          return "User access updated"

  

      project_user = model.ProjectUser(

-         project_id=project.id, user_id=new_user_obj.id, access=access

+         project_id=project.id,

+         user_id=new_user_obj.id,

+         access=access,

+         branches=branches,

      )

      project.date_modified = datetime.datetime.utcnow()

      session.add(project_user)
@@ -1173,6 +1188,7 @@ 

              project=project.to_json(public=True),

              new_user=new_user_obj.username,

              access=access,

+             branches=branches,

              agent=user_obj.username,

          ),

      )
@@ -1186,6 +1202,7 @@ 

      new_group,

      user,

      access="admin",

+     branches=None,

      create=False,

      is_admin=False,

  ):
@@ -1228,15 +1245,20 @@ 

          ]

      )

  

-     if new_group in groups:

+     if new_group in groups and access != "collaborator":

          raise pagure.exceptions.PagureException(

              "This group already has this access on this project"

          )

  

+     # Reset the branches to None if the group isn't a collaborator

+     if access != "collaborator":

+         branches = None

+ 

      # the group already has some access, update to new access

      if group_obj in project.groups:

          access_obj = get_obj_access(session, project, group_obj)

          access_obj.access = access

+         access_obj.branches = branches

          session.add(access_obj)

          project.date_modified = datetime.datetime.utcnow()

          update_read_only_mode(session, project, read_only=True)
@@ -1250,6 +1272,7 @@ 

                  project=project.to_json(public=True),

                  new_group=group_obj.group_name,

                  new_access=access,

+                 new_branches=branches,

                  agent=user,

              ),

          )
@@ -1257,7 +1280,10 @@ 

          return "Group access updated"

  

      project_group = model.ProjectGroup(

-         project_id=project.id, group_id=group_obj.id, access=access

+         project_id=project.id,

+         group_id=group_obj.id,

+         access=access,

+         branches=branches,

      )

      session.add(project_group)

      # Make sure we won't have SQLAlchemy error before we continue
@@ -1274,6 +1300,7 @@ 

              project=project.to_json(public=True),

              new_group=group_obj.group_name,

              access=access,

+             branches=branches,

              agent=user,

          ),

      )
@@ -2380,6 +2407,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectUser.access == "admin",

                      model.ProjectUser.access == "commit",

+                     model.ProjectUser.access == "collaborator",

                  ),

              )

          )
@@ -2394,6 +2422,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2409,6 +2438,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2455,6 +2485,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectUser.access == "admin",

                      model.ProjectUser.access == "commit",

+                     model.ProjectUser.access == "collaborator",

                  ),

              )

          )
@@ -2470,6 +2501,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2486,6 +2518,7 @@ 

                  sqlalchemy.or_(

                      model.ProjectGroup.access == "admin",

                      model.ProjectGroup.access == "commit",

+                     model.ProjectGroup.access == "collaborator",

                  ),

              )

          )
@@ -2591,7 +2624,7 @@ 

      projects = session.query(sqlalchemy.distinct(model.Project.id))

  

      if acls is None:

-         acls = ["main admin", "admin", "commit", "ticket"]

+         acls = ["main admin", "admin", "collaborator", "commit", "ticket"]

  

      if username is not None:

  

@@ -0,0 +1,34 @@ 

+ <p class="center"> <strong>Access Levels</strong> </p>

+ <p class="justify">

+ <strong>Ticket</strong>: A user or a group with this level of access can only edit metadata

+   of an issue. This includes changing the status of an issue, adding/removing

+   tags from them, adding/removing assignees and every other option which can

+   be accessed when you click "Edit Metadata" button in an issue page. However,

+   this user can not "create" a new tag or "delete" an existing tag because,

+   that would involve access to settings page of the project which this user

+   won't have. It also won't be able to "delete" the issue because, it falls

+   outside of "Edit Metadata".

+ </p>

+ <p class="justify">

+ <strong>Collaborator</strong>: A user or a group with this level of access can do everything what

+   a user/group with ticket access can do + it can commit to some branches in the project.

+   These branches are defined here using their name or a pattern and needs to be comma separated. <br />

+   Some examples:

+     <ul>

+         <li>master,features/*</li>

+         <li>el*</li>

+         <li>master,f*</li>

+     </ul>

+ </p>

+ <p class="justify">

+ <strong>Commit</strong>: A user or a group with this level of access can do everything what

+   a user/group with ticket access can do + it can do everything on the project

+   which doesn't include access to settings page. It can "Edit Metadata" of an issue

+   just like a user with ticket access would do, can merge a pull request, can push

+   to the main repository directly, delete an issue, cancel a pull request etc.

+ </p>

+ <p class="justify">

+ <strong>Admin</strong>: The user/group with this access has access to everything on the project.

+   All the "users" of the project that have been added till now are having this access.

+   They can change the settings of the project, add/remove users/groups on the project.

+ </p>

@@ -33,6 +33,8 @@ 

              <option value="{{ access }}" id="{{ access }}"> {{ access }} </option>

            {% endfor %}

          </select>

+         <input class="form-control" name="branches" id="branches" class="hidden"

+           placeholder="A comma separated list of branches" value=""/>

        </fieldset>

  

        <p class="buttons indent">
@@ -41,29 +43,7 @@ 

          {{ form.csrf_token }}

        </p>

      </form>

-     <p class="center"> <strong>Access Levels</strong> </p>

-     <p class="justify">

-     <strong>Ticket</strong>: A user or a group with this level of access can only edit metadata

-       of an issue. This includes changing the status of an issue, adding/removing

-       tags from them, adding/removing assignees and every other option which can

-       be accessed when you click "Edit Metadata" button in an issue page. However,

-       this user can not "create" a new tag or "delete" an existing tag because,

-       that would involve access to settings page of the project which this user

-       won't have. It also won't be able to "delete" the issue because, it falls

-       outside of "Edit Metadata".

-     </p>

-     <p class="justify">

-     <strong>Commit</strong>: A user or a group with this level of access can do everything what

-       a user/group with ticket access can do + it can do everything on the project

-       which doesn't include access to settings page. It can "Edit Metadata" of an issue

-       just like a user with ticket access would do, can merge a pull request, can push

-       to the main repository directly, delete an issue, cancel a pull request etc.

-     </p>

-     <p class="justify">

-     <strong>Admin</strong>: The user/group with this access has access to everything on the project.

-       All the "users" of the project that have been added till now are having this access.

-       They can change the settings of the project, add/remove users/groups on the project.

-     </p>

+     {% include '_access_levels_descriptions.html' %}

      </div>

    </div>

  </div>
@@ -90,7 +70,10 @@ 

    );

  }

  

+ 

  $( document ).ready(function() {

+   $("#branches").hide();

+ 

    var group_to_update = "{{ group_to_update }}";

    if (!group_to_update || group_to_update === "None") {

      $('#group').selectize({
@@ -114,9 +97,24 @@ 

      if (group_access !== "None") {

        $("#" + "{{ group_access.access }}").attr("selected", "selected");

      }

+     $("#branches").val("{{ group_access.branches or ''}}");

      $("#card-topic").html("<strong>Update group access in {{repo.name}}</strong>");

      $("#add_update_button").attr("value", "Update");

-   }

+   };

+ 

+   if ($("#access").val() == "collaborator") {

+     $("#branches").show();

+   };

+ 

+   $("#access").on("change", function() {

+     var _acc = $("#access");

+     if (_acc.val() == "collaborator") {

+         $("#branches").show();

+     } else {

+         $("#branches").hide();

+     }

+   });

+ 

  });

  </script>

  {% endblock %}

file modified
+21 -23
@@ -31,6 +31,9 @@ 

              <option value="{{ access }}" id="{{ access }}"> {{ access }} </option>

            {% endfor %}

          </select>

+ 

+         <input class="form-control" name="branches" id="branches" class="hidden"

+           placeholder="A comma separated list of branches" value=""/>

        </fieldset>

  

        <p class="buttons indent">
@@ -39,29 +42,7 @@ 

          {{ form.csrf_token }}

        </p>

      </form>

-     <p class="center"> <strong>Access Levels</strong> </p>

-     <p class="justify">

-     <strong>Ticket</strong>: A user or a group with this level of access can only edit metadata

-       of an issue. This includes changing the status of an issue, adding/removing

-       tags from them, adding/removing assignees and every other option which can

-       be accessed when you click "Edit Metadata" button in an issue page. However,

-       this user can not "create" a new tag or "delete" an existing tag because,

-       that would involve access to settings page of the project which this user

-       won't have. It also won't be able to "delete" the issue because, it falls

-       outside of "Edit Metadata".

-     </p>

-     <p class="justify">

-     <strong>Commit</strong>: A user or a group with this level of access can do everything what

-       a user/group with ticket access can do + it can do everything on the project

-       which doesn't include access to settings page. It can "Edit Metadata" of an issue

-       just like a user with ticket access would do, can merge a pull request, can push

-       to the main repository directly, delete an issue, cancel a pull request etc.

-     </p>

-     <p class="justify">

-     <strong>Admin</strong>: The user/group with this access has access to everything on the project.

-       All the "users" of the project that have been added till now are having this access.

-       They can change the settings of the project, add/remove users/groups on the project.

-     </p>

+     {% include '_access_levels_descriptions.html' %}

      </div>

    </div>

  </div>
@@ -75,6 +56,8 @@ 

  

  <script type="text/javascript" nonce="{{ g.nonce }}">

  $( document ).ready(function() {

+   $("#branches").hide();

+ 

    var user_to_update = "{{ user_to_update }}";

    if (!user_to_update || user_to_update === "None") {

      $('#user').selectize({
@@ -99,13 +82,28 @@ 

      $("#user").attr("value", user_to_update);

      $("#user").attr("readonly", true);

      var user_access = "{{ user_access }}";

+     console.log("{{ user_access.branches }}");

      if (user_access !== "None") {

        $("#" + "{{ user_access.access }}").attr("selected", "selected");

      }

+     $("#branches").val("{{ user_access.branches or ''}}");

      $("#card-topic").html("<strong>Update user access in {{repo.name}}</strong>");

      $("#add_update_button").attr("value", "Update");

    }

  

+   if ($("#access").val() == "collaborator") {

+     $("#branches").show();

+   };

+ 

+   $("#access").on("change", function() {

+     var _acc = $("#access");

+     if (_acc.val() == "collaborator") {

+         $("#branches").show();

+     } else {

+         $("#branches").hide();

+     }

+   });

+ 

  });

  </script>

  {% endblock %}

file modified
+2
@@ -2081,6 +2081,7 @@ 

                  new_user=form.user.data,

                  user=flask.g.fas_user.username,

                  access=form.access.data,

+                 branches=form.branches.data,

                  required_groups=pagure_config.get("REQUIRED_GROUPS"),

              )

              flask.g.session.commit()
@@ -2241,6 +2242,7 @@ 

                  new_group=form.group.data,

                  user=flask.g.fas_user.username,

                  access=form.access.data,

+                 branches=form.branches.data,

                  create=pagure_config.get("ENABLE_GROUP_MNGT", False),

                  is_admin=pagure.utils.is_admin(),

              )

file modified
+59 -1
@@ -1,7 +1,7 @@ 

  # -*- coding: utf-8 -*-

  

  """

-  (c) 2017 - Copyright Red Hat Inc

+  (c) 2017-2020 - Copyright Red Hat Inc

  

   Authors:

     Pierre-Yves Chibon <pingou@pingoured.fr>
@@ -11,6 +11,7 @@ 

  from __future__ import unicode_literals, absolute_import

  

  import datetime

+ import fnmatch

  import logging

  import logging.config

  import os
@@ -269,6 +270,63 @@ 

      return False

  

  

+ def is_repo_collaborator(repo_obj, refname, username=None, session=None):

+     """ Return whether the user has commit on the specified branch of the

+     provided repo. """

+     committer = is_repo_committer(repo_obj, username=username, session=session)

+     if committer:

+         _log.debug("User is a committer")

+         return committer

+ 

+     import pagure.lib.query

+ 

+     if username is None:

+         if not authenticated():

+             return False

+         if is_admin():

+             return True

+         username = flask.g.fas_user.username

+         usergroups = set(flask.g.fas_user.groups)

+ 

+     if not session:

+         session = flask.g.session

+     try:

+         user = pagure.lib.query.get_user(session, username)

+         usergroups = set(user.groups)

+     except pagure.exceptions.PagureException:

+         return False

+ 

+     # If they are in the list of committers -> maybe

+     for user in repo_obj.collaborators:

+         if user.user.username == username:

+             # if branch is None when the user tries to read,

+             # so we'll allow that

+             if refname is None:

+                 return True

+             # If the branch is specified: the user is trying to write, we'll

+             # check if they are allowed to

+             for pattern in user.branches.split(","):

+                 pattern = "refs/heads/{}".format(pattern.strip())

+                 if fnmatch.fnmatch(refname, pattern):

+                     return True

+ 

+     # If they are in a group that has commit access -> maybe

+     for project_group in repo_obj.collaborator_groups:

+         if project_group.group.group_name in usergroups:

+             # if branch is None when the user tries to read,

+             # so we'll allow that

+             if refname is None:

+                 return True

+             # If the branch is specified: the user is trying to write, we'll

+             # check if they are allowed to

+             for pattern in project_group.branches.split(","):

+                 pattern = "refs/heads/{}".format(pattern.strip())

+                 if fnmatch.fnmatch(refname, pattern):

+                     return True

+ 

+     return False

+ 

+ 

  def is_repo_user(repo_obj, username=None):

      """ Return whether the user has some access in the provided repo. """

      if username:

file modified
+187
@@ -317,6 +317,193 @@ 

              output = pagure.utils.is_repo_committer(repo)

              self.assertFalse(output)

  

+     def test_is_repo_collaborator_logged_out(self):

+         """ Test is_repo_committer in pagure when there is no logged in user.

+         """

+         repo = pagure.lib.query._get_project(self.session, "test")

+         with self.app.application.app_context():

+             output = pagure.utils.is_repo_collaborator(repo, "master")

+         self.assertFalse(output)

+ 

+     def test_is_repo_collaborator_logged_in(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="pingou")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertTrue(output)

+ 

+     def test_is_repo_collaborator_invalid_username(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="invalid")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+     @mock.patch.dict("pagure.config.config", {"PAGURE_ADMIN_USERS": ["foo"]})

+     def test_is_repo_collaborator_admin_user(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertTrue(output)

+ 

+     def test_is_repo_collaborator_not_in_project(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+     def test_is_repo_collaborator_in_project(self):

+         """ Test is_repo_collaborator in pagure with the appropriate user logged

+         in. """

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         # Add user foo to project test

+         msg = pagure.lib.query.add_user_to_project(

+             self.session,

+             project=repo,

+             new_user="foo",

+             user="pingou",

+             access="collaborator",

+             branches="epel*",

+         )

+         self.session.commit()

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             # Collaborator trying to read the project

+             output = pagure.utils.is_repo_collaborator(repo, None)

+             self.assertTrue(output)

+ 

+             # Collaborator trying to write to the project

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+             output = pagure.utils.is_repo_collaborator(repo, "refs/heads/epel")

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8"

+             )

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8-sig-foobar"

+             )

+             self.assertTrue(output)

+ 

+     def test_is_repo_collaborator_logged_in_in_group(self):

+         """ Test is_repo_committer in pagure with the appropriate user logged

+         in. """

+         # Create group

+         msg = pagure.lib.query.add_group(

+             self.session,

+             group_name="packager",

+             display_name="packager",

+             description="The Fedora packager groups",

+             group_type="user",

+             user="pingou",

+             is_admin=False,

+             blacklist=[],

+         )

+         self.session.commit()

+         self.assertEqual(msg, "User `pingou` added to the group `packager`.")

+ 

+         # Add user to group

+         group = pagure.lib.query.search_groups(

+             self.session, group_name="packager"

+         )

+         msg = pagure.lib.query.add_user_to_group(

+             self.session,

+             username="foo",

+             group=group,

+             user="pingou",

+             is_admin=True,

+         )

+         self.session.commit()

+         self.assertEqual(msg, "User `foo` added to the group `packager`.")

+ 

+         # Add group packager to project test

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

+         msg = pagure.lib.query.add_group_to_project(

+             self.session,

+             project=project,

+             new_group="packager",

+             user="pingou",

+             access="collaborator",

+             branches="epel*",

+         )

+         self.session.commit()

+         self.assertEqual(msg, "Group added")

+ 

+         repo = pagure.lib.query._get_project(self.session, "test")

+ 

+         g = munch.Munch()

+         g.fas_user = tests.FakeUser(username="foo")

+         g.authenticated = True

+         g.session = self.session

+         with mock.patch("flask.g", g):

+             # Collaborator in the group trying to read the project

+             output = pagure.utils.is_repo_collaborator(repo, None)

+             self.assertTrue(output)

+ 

+             # Collaborator in the group trying to write to the project

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/master"

+             )

+             self.assertFalse(output)

+ 

+             output = pagure.utils.is_repo_collaborator(repo, "refs/heads/epel")

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8"

+             )

+             self.assertTrue(output)

+ 

+             output = pagure.utils.is_repo_collaborator(

+                 repo, "refs/heads/epel8-sig-foobar"

+             )

+             self.assertTrue(output)

+ 

  

  if __name__ == "__main__":

      unittest.main(verbosity=2)

@@ -422,11 +422,13 @@ 

                      "project": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -460,11 +462,13 @@ 

                      "repo_from": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -763,9 +767,15 @@ 

              "initial_comment": None,

              "last_updated": "1431414800",

              "project": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -797,9 +807,15 @@ 

              },

              "remote_git": None,

              "repo_from": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -921,9 +937,15 @@ 

              "initial_comment": None,

              "last_updated": "1431414800",

              "project": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -955,9 +977,15 @@ 

              },

              "remote_git": None,

              "repo_from": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -3036,9 +3064,15 @@ 

                  "initial_comment": "Nothing much, the changes speak for themselves",

                  "last_updated": "1516348115",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3070,9 +3104,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3173,9 +3213,15 @@ 

                  "initial_comment": None,

                  "last_updated": "1516348115",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3207,9 +3253,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -3532,9 +3584,15 @@ 

              "initial_comment": "Nothing much, the changes speak for themselves",

              "last_updated": "1516348115",

              "project": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],
@@ -3566,9 +3624,15 @@ 

              },

              "remote_git": None,

              "repo_from": {

-                 "access_groups": {"admin": [], "commit": [], "ticket": []},

+                 "access_groups": {

+                     "admin": [],

+                     "collaborator": [],

+                     "commit": [],

+                     "ticket": [],

+                 },

                  "access_users": {

                      "admin": [],

+                     "collaborator": [],

                      "commit": [],

                      "owner": ["pingou"],

                      "ticket": [],

@@ -256,9 +256,15 @@ 

                  "initial_comment": "Edited initial comment",

                  "last_updated": "1551276261",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -290,9 +296,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -310,11 +322,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -412,9 +426,15 @@ 

                  "initial_comment": "",

                  "last_updated": "1551276261",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -446,9 +466,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -466,11 +492,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -588,9 +616,15 @@ 

                  "fixes #2 \n\nThanks",

                  "last_updated": "1551276261",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -622,9 +656,15 @@ 

                  },

                  "remote_git": None,

                  "repo_from": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -642,11 +682,13 @@ 

                      "parent": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -290,11 +290,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -368,11 +370,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -438,11 +442,13 @@ 

                  {

                      "access_groups": {

                          "admin": ["some_group"],

+                         "collaborator": [],

                          "commit": [],

                          "ticket": [],

                      },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -36,6 +36,8 @@ 

  class PagureFlaskApiProjecttests(tests.Modeltests):

      """ Tests for the flask API of pagure for issue """

  

+     maxDiff = None

+ 

      def setUp(self):

          super(PagureFlaskApiProjecttests, self).setUp()

          self.gga_patcher = patch(
@@ -225,9 +227,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -441,9 +449,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -502,9 +516,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -535,9 +555,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -568,9 +594,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -629,9 +661,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -662,9 +700,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -695,9 +739,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -752,9 +802,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -809,9 +865,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -882,9 +944,15 @@ 

          data["date_created"] = "1436527638"

          data["date_modified"] = "1436527638"

          expected_data = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -972,11 +1040,13 @@ 

          expected_data = {

              "access_groups": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": ["some_group"],

                  "ticket": [],

              },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -1036,9 +1106,15 @@ 

          data["date_created"] = "1436527638"

          data["date_modified"] = "1436527638"

          expected_data = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -1101,9 +1177,15 @@ 

              },

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1134,9 +1216,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1167,9 +1255,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1238,9 +1332,15 @@ 

              "pagination": {"next": None, "page": 2, "pages": 2, "per_page": 2},

              "projects": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1396,9 +1496,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],
@@ -1451,9 +1557,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": ["pingou"],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],
@@ -1516,9 +1628,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": ["pingou"],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],
@@ -1573,9 +1691,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],
@@ -1632,9 +1756,15 @@ 

          data["date_created"] = "1496338274"

          data["date_modified"] = "1496338274"

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["foo"],

                  "ticket": [],
@@ -2986,7 +3116,8 @@ 

  

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

      def test_api_modify_acls_no_project(self):
@@ -3126,9 +3257,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": ["foo"],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -3190,9 +3327,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": ["baz"]},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": ["baz"],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -3231,7 +3374,8 @@ 

  

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

          data = {"user_type": "user", "name": "foo"}
@@ -3280,7 +3424,12 @@ 

          user_foo = pagure.lib.query.search_user(self.session, username="foo")

          self.assertEquals(

              project.access_users,

-             {"admin": [], "commit": [user_foo], "ticket": []},

+             {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [user_foo],

+                 "ticket": [],

+             },

          )

  

          # Create an API token for `foo` for the project `test`
@@ -3309,9 +3458,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -3348,7 +3503,8 @@ 

          self.session = pagure.lib.query.create_session(self.dbpath)

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

      def test_api_modify_acls_remove_someone_else_acl(self):
@@ -3362,7 +3518,12 @@ 

          user_foo = pagure.lib.query.search_user(self.session, username="foo")

          self.assertEquals(

              project.access_users,

-             {"admin": [], "commit": [user_foo], "ticket": []},

+             {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [user_foo],

+                 "ticket": [],

+             },

          )

  

          headers = {"Authorization": "token aaabbbcccddd"}
@@ -3376,9 +3537,15 @@ 

          data["date_modified"] = "1510742566"

  

          expected_output = {

-             "access_groups": {"admin": [], "commit": [], "ticket": []},

+             "access_groups": {

+                 "admin": [],

+                 "collaborator": [],

+                 "commit": [],

+                 "ticket": [],

+             },

              "access_users": {

                  "admin": [],

+                 "collaborator": [],

                  "commit": [],

                  "owner": ["pingou"],

                  "ticket": [],
@@ -3415,7 +3582,8 @@ 

          self.session = pagure.lib.query.create_session(self.dbpath)

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

  
@@ -3434,7 +3602,8 @@ 

  

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

          self.assertEquals(

-             project.access_users, {"admin": [], "commit": [], "ticket": []}

+             project.access_users,

+             {"admin": [], "collaborator": [], "commit": [], "ticket": []},

          )

  

      def test_api_get_project_options_wrong_project(self):

@@ -152,9 +152,15 @@ 

              {

                  "message": "Project deleted",

                  "project": {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],

@@ -1396,11 +1396,13 @@ 

                          {

                              "access_groups": {

                                  "admin": [],

+                                 "collaborator": [],

                                  "commit": [],

                                  "ticket": [],

                              },

                              "access_users": {

                                  "admin": [],

+                                 "collaborator": [],

                                  "commit": [],

                                  "owner": ["pingou"],

                                  "ticket": [],
@@ -1454,11 +1456,13 @@ 

                          {

                              "access_groups": {

                                  "admin": [],

+                                 "collaborator": [],

                                  "commit": [],

                                  "ticket": [],

                              },

                              "access_users": {

                                  "admin": [],

+                                 "collaborator": [],

                                  "commit": [],

                                  "owner": ["pingou"],

                                  "ticket": [],
@@ -1588,11 +1592,13 @@ 

                              "project": {

                                  "access_groups": {

                                      "admin": [],

+                                     "collaborator": [],

                                      "commit": [],

                                      "ticket": [],

                                  },

                                  "access_users": {

                                      "admin": [],

+                                     "collaborator": [],

                                      "commit": [],

                                      "owner": ["pingou"],

                                      "ticket": [],
@@ -1621,11 +1627,13 @@ 

                              "repo_from": {

                                  "access_groups": {

                                      "admin": [],

+                                     "collaborator": [],

                                      "commit": [],

                                      "ticket": [],

                                  },

                                  "access_users": {

                                      "admin": [],

+                                     "collaborator": [],

                                      "commit": [],

                                      "owner": ["pingou"],

                                      "ticket": [],
@@ -1719,11 +1727,13 @@ 

                      "project": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],
@@ -1752,11 +1762,13 @@ 

                      "repo_from": {

                          "access_groups": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "ticket": [],

                          },

                          "access_users": {

                              "admin": [],

+                             "collaborator": [],

                              "commit": [],

                              "owner": ["pingou"],

                              "ticket": [],

@@ -139,9 +139,15 @@ 

              },

              "repos": [

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -172,9 +178,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -205,9 +217,15 @@ 

                      },

                  },

                  {

-                     "access_groups": {"admin": [], "commit": [], "ticket": []},

+                     "access_groups": {

+                         "admin": [],

+                         "collaborator": [],

+                         "commit": [],

+                         "ticket": [],

+                     },

                      "access_users": {

                          "admin": [],

+                         "collaborator": [],

                          "commit": [],

                          "owner": ["pingou"],

                          "ticket": [],
@@ -1759,11 +1777,13 @@ 

                          "project": {

                              "access_groups": {

                                  "admin": [],

+                                 "collaborator": [],

                                  "commit": [],

                                  "ticket": [],

                              },

                              "access_users": {

                                  "admin": [],

+                                 "collaborator": [],

                                  "commit": [],

                                  "owner": ["pingou"],

                                  "ticket": [],

file modified
+3 -1
@@ -4413,7 +4413,9 @@ 

          """ Test the get_access_levels method in pagure.lib """

  

          acls = pagure.lib.query.get_access_levels(self.session)

-         self.assertEqual(sorted(["admin", "commit", "ticket"]), sorted(acls))

+         self.assertEqual(

+             sorted(["admin", "collaborator", "commit", "ticket"]), sorted(acls)

+         )

  

      def test_get_project_users(self):

          """ Test the get_project_users method when combine is True

file modified
+5 -1
@@ -1683,7 +1683,7 @@ 

  index 0000000..60f7480

  --- /dev/null

  +++ b/456

- @@ -0,0 +1,152 @@

+ @@ -0,0 +1,156 @@

  +{

  +    "assignee": null,

  +    "branch": "master",
@@ -1701,11 +1701,13 @@ 

  +    "project": {

  +        "access_groups": {

  +            "admin": [],

+ +            "collaborator": [],

  +            "commit": [],

  +            "ticket": []

  +        },

  +        "access_users": {

  +            "admin": [],

+ +            "collaborator": [],

  +            "commit": [],

  +            "owner": [

  +                "pingou"
@@ -1762,11 +1764,13 @@ 

  +    "repo_from": {

  +        "access_groups": {

  +            "admin": [],

+ +            "collaborator": [],

  +            "commit": [],

  +            "ticket": []

  +        },

  +        "access_users": {

  +            "admin": [],

+ +            "collaborator": [],

  +            "commit": [],

  +            "owner": [

  +                "pingou"

@@ -63,6 +63,17 @@ 

          self.session.add(push_dkey)

          self.session.commit()

  

+         # Allow the user foo to commit to project test on epel* branches

+         msg = pagure.lib.query.add_user_to_project(

+             self.session,

+             project=project,

+             new_user="foo",

+             user="pingou",

+             access="collaborator",

+             branches="epel*",

+         )

+         self.session.commit()

+ 

      def create_fork(self):

          # Create fork

          headers = {"Authorization": "token aaabbbcccddd"}
@@ -82,6 +93,7 @@ 

              "project_pr_only": False,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": ["Internal push allowed"],

              "expected_result": True,
@@ -93,6 +105,7 @@ 

              "project_pr_only": False,

              "global_pr_only": True,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": ["Pull request required"],

              "expected_result": False,
@@ -104,6 +117,7 @@ 

              "project_pr_only": False,

              "global_pr_only": True,

              "project": {"name": "acltest", "user": "pingou"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": ["Has commit access: False"],

              "expected_result": False,
@@ -115,6 +129,7 @@ 

              "project_pr_only": True,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": ["Pull request required"],

              "expected_result": False,
@@ -126,6 +141,7 @@ 

              "project_pr_only": True,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "ticket",

              "expected_messages": ["Has commit access: False"],

              "expected_result": False,
@@ -137,6 +153,7 @@ 

              "project_pr_only": False,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": [

                  "Deploykey used. Push access: False",
@@ -151,6 +168,7 @@ 

              "project_pr_only": False,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": [

                  "Deploykey used. Push access: True",
@@ -165,6 +183,7 @@ 

              "project_pr_only": False,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

              "repotype": "main",

              "expected_messages": ["Has commit access: False"],

              "expected_result": False,
@@ -176,6 +195,43 @@ 

              "project_pr_only": False,

              "global_pr_only": False,

              "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

+             "repotype": "main",

+             "expected_messages": ["Has commit access: True"],

+             "expected_result": True,

+         },

+         # Contributor invalid branch

+         {

+             "internal": False,

+             "username": "foo",

+             "project_pr_only": False,

+             "global_pr_only": False,

+             "project": {"name": "acltest"},

+             "ref": "refs/heads/master",

+             "repotype": "main",

+             "expected_messages": ["Has commit access: False"],

+             "expected_result": False,

+         },

+         # Contributor valid branch epel-foo

+         {

+             "internal": False,

+             "username": "foo",

+             "project_pr_only": False,

+             "global_pr_only": False,

+             "project": {"name": "acltest"},

+             "ref": "refs/heads/epel-foo",

+             "repotype": "main",

+             "expected_messages": ["Has commit access: True"],

+             "expected_result": True,

+         },

+         # Contributor valid branch epel

+         {

+             "internal": False,

+             "username": "foo",

+             "project_pr_only": False,

+             "global_pr_only": False,

+             "project": {"name": "acltest"},

+             "ref": "refs/heads/epel",

              "repotype": "main",

              "expected_messages": ["Has commit access: True"],

              "expected_result": True,
@@ -210,7 +266,7 @@ 

                  session=self.session,

                  project=project,

                  username=case["username"],

-                 refname="refs/heads/master",

+                 refname=case["ref"],

                  pull_request=None,

                  repotype=case["repotype"],

                  is_internal=case["internal"],

The collaborators are collaborators that are only granted limited access
to the project (for example, one or a few branches in the repository).
They are also provided ticket access.
They are not granted full commit on the entire project.

Signed-off-by: Pierre-Yves Chibon pingou@pingoured.fr

This is really not ready for review/merging but people can already start to have a look at it and I'd like to see what breaks in the CI :)

rebased onto c36322d36104821782e39538dfef382915dd66c5

4 years ago

Shouldn't this be collaborator? Especially throughout the rest of the file?

rebased onto c70565aa9d23d0f04a7346c780e6b09932e6becb

4 years ago

How does this affect PR workflow? Can someone merge a PR that targets their branch but not see the merge button if it doesn't?

How does this affect PR workflow? Can someone merge a PR that targets their branch but not see the merge button if it doesn't?

To be figured out

How does this affect PR workflow? Can someone merge a PR that targets their branch but not see the merge button if it doesn't?

If desired we could use the new is_collaborator util method to leverage that on the merge button logic

Is it possible to add collaborators without branches defined?

We could want that in conjuction with https://pagure.io/pagure/issue/4470 , that way we could allow to specify a group of users without commit access but with voting capabilities

rebased onto a02f94af8895115a03c06c0d4ff7e2c893e943ab

3 years ago

pretty please pagure-ci rebuild

3 years ago

Blech...

05:38:02  Failed tests:
05:38:02  FAILED test: py3-test_pagure_exclude_group_index
05:38:02  FAILED test: py3-test_pagure_flask_api_fork
05:38:02  FAILED test: py3-test_pagure_flask_api_fork_update
05:38:02  FAILED test: py3-test_pagure_flask_api_group
05:38:02  FAILED test: py3-test_pagure_flask_api_project
05:38:02  FAILED test: py3-test_pagure_flask_api_ui_private_repo
05:38:02  FAILED test: py3-test_pagure_flask_api_user
05:38:02  FAILED test: py3-test_pagure_flask_ui_app_browse
05:38:02  FAILED test: py3-test_pagure_flask_ui_repo
05:38:02  FAILED test: py3-test_pagure_lib
05:38:02  FAILED test: py3-test_pagure_lib_git
05:38:02  FAILED test: py3-test_pagure_lib_git_auth
05:38:02  FAILED test: py3-test_pagure_lib_git_auth_paguregitauth
05:38:02  FAILED test: py3-test_style

rebased onto d11d8a654ef9f4b32e0abeb11dceed461ee56553

3 years ago

rebased onto 70e47fac575828a4ba9aeb32e0a25701e5864d48

3 years ago

rebased onto e43d4cd6dbbe2f64a95db1f2e8ece5c46dea1abd

3 years ago

rebased onto 6c6c52a28d47008b1a6db89c44fc47eb86d0f844

3 years ago

:sob:

15:44:01  Failed tests:
15:44:01  FAILED test: py3-test_pagure_flask_api_fork_assign
15:44:01  FAILED test: py3-test_pagure_flask_api_fork_update
15:44:01  FAILED test: py3-test_pagure_flask_internal
15:44:01  FAILED test: py3-test_pagure_flask_ui_app
15:44:01  FAILED test: py3-test_pagure_flask_ui_archives
15:44:01  FAILED test: py3-test_pagure_flask_ui_fork
15:44:01  FAILED test: py3-test_pagure_flask_ui_issue_pr_link
15:44:01  FAILED test: py3-test_pagure_flask_ui_issues
15:44:01  FAILED test: py3-test_pagure_flask_ui_issues_acl_checks
15:44:01  FAILED test: py3-test_pagure_flask_ui_issues_open_access
15:44:01  FAILED test: py3-test_pagure_flask_ui_issues_private
15:44:01  FAILED test: py3-test_pagure_flask_ui_login
15:44:01  FAILED test: py3-test_pagure_flask_ui_no_master_branch
15:44:01  FAILED test: py3-test_pagure_flask_ui_old_commit
15:44:01  FAILED test: py3-test_pagure_flask_ui_plugins
15:44:01  FAILED test: py3-test_pagure_flask_ui_pr_edit
15:44:01  FAILED test: py3-test_pagure_flask_ui_pr_no_sources
15:44:01  FAILED test: py3-test_pagure_flask_ui_remote_pr
15:44:01  FAILED test: py3-test_pagure_flask_ui_repo
15:44:01  FAILED test: py3-test_pagure_flask_ui_repo_flag_commit
15:44:01  FAILED test: py3-test_pagure_flask_ui_repo_slash_name
15:44:01  FAILED test: py3-test_pagure_flask_ui_repo_view_blame
15:44:01  FAILED test: py3-test_pagure_flask_ui_repo_view_file
15:44:01  FAILED test: py3-test_pagure_flask_ui_repo_view_history
15:44:01  FAILED test: py3-test_pagure_flask_ui_roadmap
15:44:01  FAILED test: py3-test_pagure_flask_ui_slash_branch_name
15:44:01  FAILED test: py3-test_pagure_flask_ui_star_project
15:44:01  FAILED test: py3-test_pagure_merge_pr_no_fork
15:44:01  FAILED test: py3-test_style

rebased onto 8168720bea6fa8786d2e2c9d806c9e44faf100b1

3 years ago

rebased onto 5ade6fa8ec05b7cd6f54a377db18207d20e7ece1

3 years ago

rebased onto 94c1ea83b6c483b7b0ead14d98e54d87c5f2f08d

3 years ago

rebased onto 418f5d292dcdbf52ca6f8b6e319fc6196bd5a55f

3 years ago

rebased onto 1e13ecfd0a3cd6e83eabbdc9867058e49f58b672

3 years ago

rebased onto d259837f58aa1e2ee5cb389f7447395ce215d431

3 years ago

rebased onto c09e7973d92513f2beb9cefc053edc0bfc26dbaf

3 years ago

Looks like only the blacken style test failed...

At this point, it looks like this just needs to be rebased and merged, right?

rebased onto d76fd4010da3c4ebf4e399da6595b3a17d62416e

3 years ago

rebased onto a3e419a432c91d5e399d3e8c307a781ba806d723

3 years ago

Note that pagure/ui/fork.py line 1185 and 1187 only check for is_committer. This would imply that if you're also asking Pagure to delete the branch in the source repo but you're not a full committer, but would have permissions to delete this specific branch, that would still be denied.
This is fine with me (fail closed), but just figured I'd note it.

I would strongly suggest to add tests to test_pagure_lib_git_auth.py for these cases, both positive and negative (with different branches).

The code itself looks reasonable to me, just those two general notes.
I think the ui/fork.py one can definitely be in a follow-up, and while I'd strongly recommend getting tests for the AuthHelper, the current implementation looks reasonable.

Note that pagure/ui/fork.py line 1185 and 1187 only check for is_committer. This would imply that if you're also asking Pagure to delete the branch in the source repo but you're not a full committer, but would have permissions to delete this specific branch, that would still be denied.
This is fine with me (fail closed), but just figured I'd note it.

Yeah, the whole logic around PR needs to be think through and adjusted. It will likely be in a subsequent PR (in the spirit of doing small incremental changes).

I would strongly suggest to add tests to test_pagure_lib_git_auth.py for these cases, both positive and negative (with different branches).

Will do :)

rebased onto 4f28c744277cece8a3c72f3bcbc3220fe69298d8

3 years ago

rebased onto 40c5b7a9c6b07df3c08abb2fe57846da3e23cfff

3 years ago

rebased onto 5c435e3959e7923c9297e34cb9f4315883fe8b23

3 years ago

1 new commit added

  • Add a collaborator level to projects.
3 years ago

rebased onto 585ecfd6c844b538d22e4f0272ca1fcb5b9e6234

3 years ago

rebased onto 67f81f1

3 years ago

Thanks for the reviews folks! Let's get this in :)

Pull-Request has been merged by pingou

3 years ago
Metadata
Flags
jenkins
success (100%)
Build #3607 successful (commit: 67f81f18)
3 years ago
jenkins
success (100%)
Build #3601 successful (commit: 585ecfd6)
3 years ago
jenkins
success (100%)
Build #3600 successful (commit: 585ecfd6)
3 years ago
jenkins
success (100%)
Build #3598 successful (commit: 5c435e39)
3 years ago
jenkins
failure
Build #3597 failed (commit: 40c5b7a9)
3 years ago
jenkins
success (100%)
Build #3596 successful (commit: 4f28c744)
3 years ago
jenkins
success (100%)
Build #3585 successful (commit: a3e419a4)
3 years ago
jenkins
failure
Build #3582 failed (commit: d76fd401)
3 years ago
jenkins
success (100%)
Build #3565 successful (commit: c09e7973)
3 years ago
jenkins
failure
Build #3564 failed (commit: c09e7973)
3 years ago
jenkins
success (100%)
Build #3558 successful (commit: 1e13ecfd)
3 years ago
jenkins
success (100%)
Build #3548 successful (commit: 418f5d29)
3 years ago
jenkins
success (100%)
Build #3393 successful (commit: 94c1ea83)
3 years ago
jenkins
success (100%)
Build #3392 successful (commit: 5ade6fa8)
3 years ago
jenkins
failure
Build #3391 failed (commit: 8168720b)
3 years ago
jenkins
failure
Build #3387 failed (commit: 6c6c52a2)
3 years ago
jenkins
failure
Build #3384 failed (commit: e43d4cd6)
3 years ago
jenkins
failure
Build #3381 failed (commit: 70e47fac)
3 years ago
jenkins
failure
Build #3382 failed (commit: 70e47fac)
3 years ago
jenkins
failure
Build #3374 failed (commit: a02f94af)
3 years ago
jenkins
failure
Build #3371 failed (commit: a02f94af)
3 years ago
jenkins
failure
Build #3240 failed (commit: c70565aa)
4 years ago
jenkins
failure
Build #3239 failed (commit: c36322d3)
4 years ago
jenkins
failure
Build #3238 failed (commit: c36322d3)
4 years ago