gitlabform 6.2.0__tar.gz → 6.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (276) hide show
  1. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/_releases.yml +7 -19
  2. gitlabform-6.2.1/.python-version +1 -0
  3. {gitlabform-6.2.0 → gitlabform-6.2.1}/PKG-INFO +1 -1
  4. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/changelog.md +18 -0
  5. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/files_processor.py +3 -5
  6. gitlabform-6.2.1/gitlabform/processors/project/merge_requests_approval_rules.py +38 -0
  7. {gitlabform-6.2.0 → gitlabform-6.2.1}/pyproject.toml +8 -8
  8. {gitlabform-6.2.0 → gitlabform-6.2.1}/tbump.toml +2 -1
  9. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_merge_request_approval_rules.py +53 -0
  10. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_files.py +95 -0
  11. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_remote_mirrors.py +3 -3
  12. gitlabform-6.2.1/tests/unit/processors/test_merge_requests_approval_rules_processor.py +120 -0
  13. {gitlabform-6.2.0 → gitlabform-6.2.1}/uv.lock +159 -159
  14. gitlabform-6.2.0/.python-version +0 -1
  15. gitlabform-6.2.0/gitlabform/processors/project/merge_requests_approval_rules.py +0 -17
  16. {gitlabform-6.2.0 → gitlabform-6.2.1}/.coveragerc +0 -0
  17. {gitlabform-6.2.0 → gitlabform-6.2.1}/.dockerignore +0 -0
  18. {gitlabform-6.2.0 → gitlabform-6.2.1}/.git-blame-ignore-revs +0 -0
  19. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/CODEOWNERS +0 -0
  20. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/ISSUE_TEMPLATE/question-or-other-issue.md +0 -0
  23. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/actions/setup-uv-local/README.md +0 -0
  24. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/actions/setup-uv-local/action.yml +0 -0
  25. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/dependabot.yml +0 -0
  26. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/_main.yml +0 -0
  27. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/build.yml +0 -0
  28. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/codecov-upload.yml +0 -0
  29. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/pr-ci-workflow.yml +0 -0
  30. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/prs-entrypoint-forks.yml +0 -0
  31. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/prs-entrypoint-main.yml +0 -0
  32. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/static-analysis.yml +0 -0
  33. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/tests-acceptance.yml +0 -0
  34. {gitlabform-6.2.0 → gitlabform-6.2.1}/.github/workflows/tests-unit.yml +0 -0
  35. {gitlabform-6.2.0 → gitlabform-6.2.1}/.gitignore +0 -0
  36. {gitlabform-6.2.0 → gitlabform-6.2.1}/.lgtm.yml +0 -0
  37. {gitlabform-6.2.0 → gitlabform-6.2.1}/.mypy.ini +0 -0
  38. {gitlabform-6.2.0 → gitlabform-6.2.1}/.overrides/gitlabform-logo-favicon.png +0 -0
  39. {gitlabform-6.2.0 → gitlabform-6.2.1}/.overrides/gitlabform-logo-favicon.svg +0 -0
  40. {gitlabform-6.2.0 → gitlabform-6.2.1}/CONTRIBUTING.md +0 -0
  41. {gitlabform-6.2.0 → gitlabform-6.2.1}/Dockerfile +0 -0
  42. {gitlabform-6.2.0 → gitlabform-6.2.1}/LICENSE +0 -0
  43. {gitlabform-6.2.0 → gitlabform-6.2.1}/README.md +0 -0
  44. {gitlabform-6.2.0 → gitlabform-6.2.1}/codecov.yml +0 -0
  45. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/__init__.py +0 -0
  46. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/common.py +0 -0
  47. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/docker.py +0 -0
  48. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/docs.py +0 -0
  49. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/env.py +0 -0
  50. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/gitlab/await-healthy.sh +0 -0
  51. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/gitlab/gitlab.rb +0 -0
  52. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/gitlab/healthcheck-and-setup.sh +0 -0
  53. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/gitlab/run_gitlab_in_docker.sh +0 -0
  54. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/gitlab/tests.Dockerfile +0 -0
  55. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/infra.py +0 -0
  56. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/main.py +0 -0
  57. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/package.py +0 -0
  58. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/qa.py +0 -0
  59. {gitlabform-6.2.0 → gitlabform-6.2.1}/dev/release.py +0 -0
  60. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/automation.md +0 -0
  61. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/contrib/coding_guidelines.md +0 -0
  62. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/contrib/implementation_design.md +0 -0
  63. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/contrib/index.md +0 -0
  64. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/contrib/local_development.md +0 -0
  65. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/contrib/releases.md +0 -0
  66. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/contrib/workflows.md +0 -0
  67. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/features.md +0 -0
  68. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/Opencast-logo.png +0 -0
  69. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/egnyte-logo.svg +0 -0
  70. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/elastic-path-logo.svg +0 -0
  71. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/gitlabform-logo-square.png +0 -0
  72. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/gitlabform-logo.png +0 -0
  73. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/gitlabform-logo.svg +0 -0
  74. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/group-ldap-links-provider.png +0 -0
  75. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/liquidlight-logo.svg +0 -0
  76. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/roche-logo.png +0 -0
  77. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/images/stroeer-online-marketing-logo.svg +0 -0
  78. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/index.md +0 -0
  79. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/installation.md +0 -0
  80. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/archive_unarchive.md +0 -0
  81. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/avatar.md +0 -0
  82. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/badges.md +0 -0
  83. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/ci_cd_variables.md +0 -0
  84. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/deploy_keys.md +0 -0
  85. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/files.md +0 -0
  86. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/group_ldap_links.md +0 -0
  87. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/group_saml_links.md +0 -0
  88. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/index.md +0 -0
  89. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/integrations.md +0 -0
  90. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/job_token_scope.md +0 -0
  91. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/labels.md +0 -0
  92. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/members.md +0 -0
  93. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/merge_requests.md +0 -0
  94. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/pipeline_schedules.md +0 -0
  95. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/project_security_settings.md +0 -0
  96. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/project_transfer.md +0 -0
  97. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/protected_branches.md +0 -0
  98. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/protected_environments.md +0 -0
  99. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/push_mirrors.md +0 -0
  100. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/push_rules.md +0 -0
  101. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/resource_groups.md +0 -0
  102. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/settings.md +0 -0
  103. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/tags_protection.md +0 -0
  104. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/reference/webhooks.md +0 -0
  105. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/requirements.md +0 -0
  106. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/running.md +0 -0
  107. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/similar_apps.md +0 -0
  108. {gitlabform-6.2.0 → gitlabform-6.2.1}/docs/upgrade.md +0 -0
  109. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlab-config/config.yml +0 -0
  110. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlab-config/gitlab-config.md +0 -0
  111. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/__init__.py +0 -0
  112. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/configuration/__init__.py +0 -0
  113. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/configuration/common.py +0 -0
  114. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/configuration/core.py +0 -0
  115. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/configuration/groups.py +0 -0
  116. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/configuration/projects.py +0 -0
  117. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/configuration/transform.py +0 -0
  118. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/constants.py +0 -0
  119. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/__init__.py +0 -0
  120. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/commits.py +0 -0
  121. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/core.py +0 -0
  122. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/group_badges.py +0 -0
  123. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/group_ldap_links.py +0 -0
  124. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/groups.py +0 -0
  125. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/merge_requests.py +0 -0
  126. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/pipelines.py +0 -0
  127. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/project_badges.py +0 -0
  128. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/project_deploy_keys.py +0 -0
  129. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
  130. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/project_protected_environments.py +0 -0
  131. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/projects.py +0 -0
  132. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/python_gitlab.py +0 -0
  133. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/gitlab/variables.py +0 -0
  134. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/lists/__init__.py +0 -0
  135. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/lists/filter.py +0 -0
  136. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/lists/groups.py +0 -0
  137. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/lists/projects.py +0 -0
  138. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/output.py +0 -0
  139. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/__init__.py +0 -0
  140. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/abstract_processor.py +0 -0
  141. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/application/__init__.py +0 -0
  142. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/application/application_settings_processor.py +0 -0
  143. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/defining_keys.py +0 -0
  144. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/__init__.py +0 -0
  145. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_badges_processor.py +0 -0
  146. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_hooks_processor.py +0 -0
  147. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_labels_processor.py +0 -0
  148. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
  149. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_members_processor.py +0 -0
  150. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_protected_branches_processor.py +0 -0
  151. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
  152. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_saml_links_processor.py +0 -0
  153. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_settings_processor.py +0 -0
  154. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/group/group_variables_processor.py +0 -0
  155. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/multiple_entities_processor.py +0 -0
  156. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/__init__.py +0 -0
  157. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/badges_processor.py +0 -0
  158. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/branches_processor.py +0 -0
  159. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
  160. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/hooks_processor.py +0 -0
  161. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/integrations_processor.py +0 -0
  162. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
  163. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/members_processor.py +0 -0
  164. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
  165. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/project_labels_processor.py +0 -0
  166. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/project_processor.py +0 -0
  167. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
  168. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/project_security_settings.py +0 -0
  169. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/project_settings_processor.py +0 -0
  170. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/project_variables_processor.py +0 -0
  171. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/remote_mirrors_processor.py +0 -0
  172. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/resource_groups_processor.py +0 -0
  173. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/schedules_processor.py +0 -0
  174. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/project/tags_processor.py +0 -0
  175. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/shared/__init__.py +0 -0
  176. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
  177. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/util/__init__.py +0 -0
  178. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/util/branch_protection.py +0 -0
  179. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/util/decorators.py +0 -0
  180. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/util/difference_logger.py +0 -0
  181. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/util/labels_processor.py +0 -0
  182. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/processors/util/variables_processor.py +0 -0
  183. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/run.py +0 -0
  184. {gitlabform-6.2.0 → gitlabform-6.2.1}/gitlabform/util.py +0 -0
  185. {gitlabform-6.2.0 → gitlabform-6.2.1}/mkdocs.yml +0 -0
  186. {gitlabform-6.2.0 → gitlabform-6.2.1}/prek.toml +0 -0
  187. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/__init__.py +0 -0
  188. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/__init__.py +0 -0
  189. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/conftest.py +0 -0
  190. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_branches.py +0 -0
  191. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_branches_users_case_insensitive.py +0 -0
  192. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_group_ldap_links.py +0 -0
  193. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_group_protected_branches.py +0 -0
  194. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_group_push_rules.py +0 -0
  195. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_group_saml_links.py +0 -0
  196. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_group_settings.py +0 -0
  197. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_group_variables.py +0 -0
  198. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_merge_request_approvals_settings.py +0 -0
  199. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_protected_environments.py +0 -0
  200. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_push_rules.py +0 -0
  201. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/premium/test_tags.py +0 -0
  202. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/__init__.py +0 -0
  203. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_application_settings.py +0 -0
  204. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_archive_project.py +0 -0
  205. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_badges.py +0 -0
  206. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_branches.py +0 -0
  207. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_deploy_keys.py +0 -0
  208. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_deploy_keys_all_projects.py +0 -0
  209. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_files_templates.py +0 -0
  210. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_avatar.py +0 -0
  211. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_badges.py +0 -0
  212. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_hooks.py +0 -0
  213. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_labels.py +0 -0
  214. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_members_case_insensitive.py +0 -0
  215. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_members_groups.py +0 -0
  216. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_members_users.py +0 -0
  217. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_settings.py +0 -0
  218. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_group_variables.py +0 -0
  219. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_hooks.py +0 -0
  220. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_integrations.py +0 -0
  221. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_job_token_scope.py +0 -0
  222. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_members.py +0 -0
  223. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_members_add_group.py +0 -0
  224. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_members_enforce.py +0 -0
  225. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_project_avatar.py +0 -0
  226. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_project_group_members_case_insensitive.py +0 -0
  227. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_project_labels.py +0 -0
  228. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_project_members_case_insensitve.py +0 -0
  229. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_project_settings.py +0 -0
  230. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_project_variables.py +0 -0
  231. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_resource_groups.py +0 -0
  232. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_running.py +0 -0
  233. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_scheduled_for_deletion_project.py +0 -0
  234. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_schedules.py +0 -0
  235. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_tags.py +0 -0
  236. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_token_from_config.py +0 -0
  237. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_transfer_project.py +0 -0
  238. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/standard/test_wildcard_patterns.py +0 -0
  239. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/ultimate/test_group_members.py +0 -0
  240. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/ultimate/test_group_settings.py +0 -0
  241. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/ultimate/test_members.py +0 -0
  242. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/ultimate/test_project_security_settings.py +0 -0
  243. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/acceptance/ultimate/test_project_settings.py +0 -0
  244. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/__init__.py +0 -0
  245. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/__init__.py +0 -0
  246. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_case_sensitivity.py +0 -0
  247. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_inheritance_break_projects_and_groups.py +0 -0
  248. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_inheritance_break_subgroups.py +0 -0
  249. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_inheritance_break_validation.py +0 -0
  250. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_projects_and_groups.py +0 -0
  251. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_skip_groups_skip_projects.py +0 -0
  252. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_subgroups.py +0 -0
  253. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_wildcard_patterns.py +0 -0
  254. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/test_yaml_version.py +0 -0
  255. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/transform/test_access_level_transformer.py +0 -0
  256. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/transform/test_group_transformer.py +0 -0
  257. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/transform/test_implicit_name_transformer.py +0 -0
  258. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/transform/test_user_and_group_transformers.py +0 -0
  259. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/configuration/transform/test_user_transformer.py +0 -0
  260. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/gitlab/test_core.py +0 -0
  261. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/gitlab/test_python_gitlab.py +0 -0
  262. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/__init__.py +0 -0
  263. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_abstract_processor.py +0 -0
  264. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_abstract_processors.py +0 -0
  265. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_branches_processor.py +0 -0
  266. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_difference_logger.py +0 -0
  267. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_files_processor.py +0 -0
  268. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_group_members_processor.py +0 -0
  269. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_group_protected_branches_processor.py +0 -0
  270. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_group_saml_links_processor.py +0 -0
  271. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/processors/test_schedules_processor_extended_cron_pattern.py +0 -0
  272. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/test_access_levels.py +0 -0
  273. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/test_non_empty_configs_provider.py +0 -0
  274. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/test_parse_args.py +0 -0
  275. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/test_projects_provider.py +0 -0
  276. {gitlabform-6.2.0 → gitlabform-6.2.1}/tests/unit/test_utils.py +0 -0
@@ -64,17 +64,6 @@ jobs:
64
64
  MANUAL_UPSTREAM_RUN_ID: ${{ inputs.override_run_id }}
65
65
  run: uv run release gh-workflow-check
66
66
 
67
- # TODO: Remove this job when all the release jobs have been migrated
68
- # to consume build artifacts from Main workflow instead of rebuilding.
69
- build-release-artifacts:
70
- name: Build release artifacts
71
- if: ${{ needs.prep-release.outputs.is_valid_release == 'true' }}
72
- needs:
73
- - prep-release
74
- uses: ./.github/workflows/build.yml
75
- with:
76
- BRANCH_REF: ${{ needs.prep-release.outputs.semver_tag }}
77
-
78
67
  publish-docs:
79
68
  name: Publish Docs
80
69
  if: ${{ needs.prep-release.outputs.is_valid_release == 'true' }}
@@ -120,7 +109,6 @@ jobs:
120
109
  if: ${{ needs.prep-release.outputs.is_valid_release == 'true' }}
121
110
  needs:
122
111
  - prep-release
123
- - build-release-artifacts
124
112
  runs-on: ubuntu-latest
125
113
  environment:
126
114
  name: pypi
@@ -140,12 +128,13 @@ jobs:
140
128
  - name: Setup Python ${{ env.UV_PYTHON }} and install dependencies
141
129
  run: |
142
130
  uv sync --frozen --no-dev --group release
143
- # TODO: In a separate PR, consume the build artifact (package-dist) uploaded
144
- # in build-release-artifacts instead of rebuilding and verifying the package here.
145
- - name: Build and verify the package
146
- run: |
147
- uv run --no-sync package build
148
- uv run --no-sync package verify
131
+ - name: Download built package
132
+ uses: actions/download-artifact@v8
133
+ with:
134
+ run-id: ${{ needs.prep-release.outputs.target_run_id }}
135
+ github-token: ${{ secrets.GITHUB_TOKEN }}
136
+ name: package-dist
137
+ path: dist
149
138
  - name: Publish to PyPI
150
139
  run: |
151
140
  uv run --no-sync release pypi
@@ -155,7 +144,6 @@ jobs:
155
144
  if: ${{ needs.prep-release.outputs.is_valid_release == 'true' }}
156
145
  needs:
157
146
  - prep-release
158
- - build-release-artifacts
159
147
  - publish-to-pypi
160
148
  runs-on: ubuntu-latest
161
149
  steps:
@@ -0,0 +1 @@
1
+ 3.14.6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlabform
3
- Version: 6.2.0
3
+ Version: 6.2.1
4
4
  Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
5
5
  Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
6
6
  Project-URL: Repository, https://github.com/gitlabform/gitlabform.git
@@ -6,6 +6,24 @@ For details on how to migrate between major versions, please refer to the [upgra
6
6
 
7
7
  ---
8
8
 
9
+ ## 6.2.1
10
+
11
+ ### Bug Fixes
12
+
13
+ * catch GitlabCreateError and GitlabDeleteError when modifying files on protected branches [#1361](https://github.com/gitlabform/gitlabform/pull/1361). ([haijeploeg](https://github.com/haijeploeg))
14
+ * fix merge_request_approval_rules to stop redundant edit calls on unchanged rules [#1350](https://github.com/gitlabform/gitlabform/pull/1350). ([rickbrouwer](https://github.com/rickbrouwer))
15
+ * use add instead of delete in commit message for new files to reflect what actually happened in commit history [#1363](https://github.com/gitlabform/gitlabform/pull/1363). ([rickbrouwer](https://github.com/rickbrouwer))
16
+
17
+ ### Build
18
+
19
+ * consume package artifact from main workflow in pypi release [#1327](https://github.com/gitlabform/gitlabform/pull/1327). ([amimas](https://github.com/amimas))
20
+
21
+ ### Dependencies
22
+
23
+ * Update various dependencies to newer versions.
24
+
25
+ Thanks to all the contributors of this release!
26
+
9
27
  ## 6.2.0
10
28
 
11
29
  ### Features
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from logging import debug, info, warning, critical
7
7
 
8
8
  from jinja2 import Environment, FileSystemLoader
9
- from gitlab import GitlabGetError, GitlabUpdateError, GitlabListError
9
+ from gitlab import GitlabCreateError, GitlabDeleteError, GitlabGetError, GitlabListError, GitlabUpdateError
10
10
  from gitlab.v4.objects import Project, ProjectFile, ProjectBranch
11
11
  from gitlab.base import RESTObject
12
12
 
@@ -235,7 +235,7 @@ class FilesProcessor(AbstractProcessor):
235
235
  new_content,
236
236
  )
237
237
 
238
- except GitlabUpdateError as e:
238
+ except (GitlabUpdateError, GitlabCreateError, GitlabDeleteError) as e:
239
239
  if (
240
240
  e.response_code == 400 or e.response_code == 403
241
241
  ) and "You are not allowed to push into this branch" in e.error_message:
@@ -302,9 +302,7 @@ class FilesProcessor(AbstractProcessor):
302
302
  "file_path": file_to_operate_on,
303
303
  "branch": branch.name,
304
304
  "content": new_content,
305
- "commit_message": self.get_commit_message_for_file_change(
306
- "delete", file_to_operate_on, configuration
307
- ),
305
+ "commit_message": self.get_commit_message_for_file_change("add", file_to_operate_on, configuration),
308
306
  }
309
307
  )
310
308
  elif operation == "delete" and isinstance(file_to_operate_on, ProjectFile):
@@ -0,0 +1,38 @@
1
+ from gitlabform.gitlab import GitLab
2
+ from gitlabform.processors.defining_keys import Key, And
3
+ from gitlabform.processors.multiple_entities_processor import MultipleEntitiesProcessor
4
+
5
+
6
+ class MergeRequestsApprovalRules(MultipleEntitiesProcessor):
7
+ def __init__(self, gitlab: GitLab):
8
+ super().__init__(
9
+ "merge_requests_approval_rules",
10
+ gitlab,
11
+ list_method_name="get_approval_rules",
12
+ add_method_name="add_approval_rule",
13
+ edit_method_name="edit_approval_rule",
14
+ delete_method_name="delete_approval_rule",
15
+ defining=Key("name"),
16
+ required_to_create_or_update=And(Key("name"), Key("approvals_required")),
17
+ )
18
+
19
+ def _needs_update(self, entity_in_gitlab: dict, entity_in_configuration: dict) -> bool:
20
+ # GitLab returns users/groups as lists of objects and protected_branches as list
21
+ # of objects with a "name" field, while the config (post-transform) has user_ids /
22
+ # group_ids as int lists and protected_branches as list of names. Without
23
+ # normalization the base _needs_update always triggers an update, even when nothing
24
+ # changed. We normalize both sides unconditionally so keys line up regardless of
25
+ # whether GitLab omitted an empty list or the user omitted the field in config.
26
+ gitlab_norm = dict(entity_in_gitlab)
27
+ gitlab_norm["user_ids"] = sorted(u["id"] for u in gitlab_norm.pop("users", []))
28
+ gitlab_norm["group_ids"] = sorted(g["id"] for g in gitlab_norm.pop("groups", []))
29
+ gitlab_norm["protected_branches"] = sorted(b["name"] for b in gitlab_norm.get("protected_branches", []))
30
+
31
+ # edit_approval_rule treats missing user_ids/group_ids/protected_branches
32
+ # in config as "clear them", so mirror that here to keep the comparison honest.
33
+ config_norm = dict(entity_in_configuration)
34
+ config_norm["user_ids"] = sorted(config_norm.get("user_ids", []))
35
+ config_norm["group_ids"] = sorted(config_norm.get("group_ids", []))
36
+ config_norm["protected_branches"] = sorted(config_norm.get("protected_branches", []))
37
+
38
+ return super()._needs_update(gitlab_norm, config_norm)
@@ -10,7 +10,7 @@ packages = ["gitlabform"]
10
10
 
11
11
  [project]
12
12
  name = "gitlabform"
13
- version = "6.2.0"
13
+ version = "6.2.1"
14
14
  requires-python = ">=3.12.0"
15
15
  authors = [{ name = "Greg Dubicki and Contributors" }]
16
16
  description = "🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML"
@@ -53,12 +53,12 @@ dev = [
53
53
  { include-group = "lint" },
54
54
  { include-group = "docs" },
55
55
  { include-group = "release" },
56
- "commitizen==4.16.4",
57
- "prek==0.4.9",
56
+ "commitizen==4.16.5",
57
+ "prek==0.4.10",
58
58
  ]
59
59
  # Minimum set of dependencies required to execute the test suite.
60
60
  test = [
61
- "coverage==7.15.0",
61
+ "coverage==7.15.2",
62
62
  "cryptography==49.0.0",
63
63
  "pytest==9.1.1",
64
64
  "pytest-cov==7.1.0",
@@ -69,17 +69,17 @@ test = [
69
69
  lint = [
70
70
  "bandit==1.9.4",
71
71
  "black==26.5.1",
72
- "codespell==2.4.2",
73
- "mypy==2.2.0",
72
+ "codespell==2.4.3",
73
+ "mypy==2.3.0",
74
74
  "mypy-extensions==1.1.0",
75
- "ruff==0.15.21",
75
+ "ruff==0.15.22",
76
76
  "types-requests==2.33.0.20260712",
77
77
  ]
78
78
  # Documentation generation stack.
79
79
  docs = [
80
80
  "markdown==3.10.2",
81
81
  "mkdocs==1.6.1",
82
- "mkdocs-material==9.7.6",
82
+ "mkdocs-material==9.7.7",
83
83
  "mkdocs-material-extensions==1.3.1",
84
84
  "pygments==2.20.0",
85
85
  "pymdown-extensions==11.0.1",
@@ -2,7 +2,7 @@
2
2
  github_url = "https://github.com/gitlabform/gitlabform/"
3
3
 
4
4
  [version]
5
- current = "6.2.0"
5
+ current = "6.2.1"
6
6
 
7
7
  # Example of a semver regexp.
8
8
  # Make sure this matches current_version before
@@ -25,6 +25,7 @@ tag_template = "v{new_version}"
25
25
  # tbump.toml location.
26
26
  [[file]]
27
27
  src = "pyproject.toml"
28
+ search = 'version = "{current_version}"'
28
29
 
29
30
  # You can specify a list of commands to
30
31
  # run after the files have been patched
@@ -1,3 +1,5 @@
1
+ import logging
2
+
1
3
  import pytest
2
4
 
3
5
  from gitlabform.gitlab import AccessLevel
@@ -546,3 +548,54 @@ class TestMergeRequestApprovers:
546
548
  coverage_check_approval_rule_details = mr_approval_rules_under_this_project[1]
547
549
  assert coverage_check_approval_rule_details.name == "Coverage-Check"
548
550
  assert coverage_check_approval_rule_details.approvals_required == 0
551
+
552
+ def test__no_churn_when_config_unchanged(
553
+ self,
554
+ project_for_function,
555
+ group_with_one_owner_and_two_developers,
556
+ make_user,
557
+ branch_for_function,
558
+ caplog,
559
+ ):
560
+ user1 = make_user(AccessLevel.DEVELOPER, add_to_project=False)
561
+ project_for_function.members.create({"user_id": user1.id, "access_level": AccessLevel.DEVELOPER.value})
562
+
563
+ config = f"""
564
+ projects_and_groups:
565
+ {project_for_function.path_with_namespace}:
566
+ branches:
567
+ {branch_for_function}:
568
+ protected: true
569
+ push_access_level: no access
570
+ merge_access_level: developer
571
+ unprotect_access_level: maintainer
572
+ merge_requests_approval_rules:
573
+ standard:
574
+ approvals_required: 1
575
+ name: "Regular approvers"
576
+ users:
577
+ - {user1.username}
578
+ security:
579
+ approvals_required: 2
580
+ name: "Extra Security Team approval for selected branches"
581
+ users:
582
+ - {user1.username}
583
+ groups:
584
+ - {group_with_one_owner_and_two_developers.full_path}
585
+ protected_branches:
586
+ - {branch_for_function}
587
+ """
588
+
589
+ run_gitlabform(config, project_for_function)
590
+ assert len(project_for_function.approvalrules.list()) >= 2
591
+
592
+ caplog.clear()
593
+ with caplog.at_level(logging.INFO):
594
+ run_gitlabform(config, project_for_function)
595
+
596
+ assert (
597
+ "Editing standard of merge_requests_approval_rules" not in caplog.text
598
+ ), f"Expected no edit for unchanged 'standard' rule, got: {caplog.text}"
599
+ assert (
600
+ "Editing security of merge_requests_approval_rules" not in caplog.text
601
+ ), f"Expected no edit for unchanged 'security' rule, got: {caplog.text}"
@@ -177,6 +177,101 @@ class TestFiles:
177
177
  break
178
178
  assert is_protected == True
179
179
 
180
+ def test__add_new_file_strongly_protected_branch(self, project, no_access_branch, is_enterprise_edition):
181
+ """
182
+ Creating a NEW file (as opposed to modifying an existing one) goes through
183
+ python-gitlab's files.create(), which raises GitlabCreateError instead of
184
+ GitlabUpdateError. This test validates that the temporary unprotect/re-protect
185
+ fallback also works in that case.
186
+ """
187
+ protected_branch = project.protectedbranches.get(no_access_branch.name)
188
+ push_access_level = protected_branch.push_access_levels[0]["access_level"]
189
+ merge_access_level = protected_branch.merge_access_levels[0]["access_level"]
190
+
191
+ # Unprotect access level is not available in GitLab CE.
192
+ unprotect_access_level_config = ""
193
+ if is_enterprise_edition:
194
+ unprotect_access_level = protected_branch.unprotect_access_levels[0]["access_level"]
195
+ unprotect_access_level_config = f"unprotect_access_level: {unprotect_access_level}"
196
+
197
+ add_file_to_protected_branch = f"""
198
+ projects_and_groups:
199
+ {project.path_with_namespace}:
200
+ branches:
201
+ {no_access_branch.name}:
202
+ protected: true
203
+ push_access_level: {push_access_level}
204
+ merge_access_level: {merge_access_level}
205
+ {unprotect_access_level_config}
206
+ files:
207
+ "newly-created-file.txt":
208
+ overwrite: true
209
+ branches:
210
+ - {no_access_branch.name}
211
+ content: "Content of a new file on a strongly protected branch"
212
+ """
213
+
214
+ run_gitlabform(add_file_to_protected_branch, project)
215
+
216
+ project_file = project.files.get(ref=no_access_branch.name, file_path="newly-created-file.txt")
217
+ assert project_file.decode().decode("utf-8") == "Content of a new file on a strongly protected branch"
218
+
219
+ branch = project.branches.get(no_access_branch.name)
220
+ assert branch.commit["message"] == "Automated add made by gitlabform"
221
+
222
+ # the file must not exist on main
223
+ with pytest.raises(GitlabGetError):
224
+ project.files.get(ref="main", file_path="newly-created-file.txt")
225
+
226
+ # check that the branch protection was restored
227
+ protected_branches = project.protectedbranches.list()
228
+ assert any(pb.name == no_access_branch.name for pb in protected_branches)
229
+
230
+ def test__delete_file_strongly_protected_branch(self, project, no_access_branch, is_enterprise_edition):
231
+ """
232
+ Deleting a file goes through python-gitlab's file.delete(), which raises
233
+ GitlabDeleteError instead of GitlabUpdateError. This test validates that
234
+ the temporary unprotect/re-protect fallback also works in that case.
235
+ """
236
+ protected_branch = project.protectedbranches.get(no_access_branch.name)
237
+ push_access_level = protected_branch.push_access_levels[0]["access_level"]
238
+ merge_access_level = protected_branch.merge_access_levels[0]["access_level"]
239
+
240
+ # Unprotect access level is not available in GitLab CE.
241
+ unprotect_access_level_config = ""
242
+ if is_enterprise_edition:
243
+ unprotect_access_level = protected_branch.unprotect_access_levels[0]["access_level"]
244
+ unprotect_access_level_config = f"unprotect_access_level: {unprotect_access_level}"
245
+
246
+ delete_file_on_protected_branch = f"""
247
+ projects_and_groups:
248
+ {project.path_with_namespace}:
249
+ branches:
250
+ {no_access_branch.name}:
251
+ protected: true
252
+ push_access_level: {push_access_level}
253
+ merge_access_level: {merge_access_level}
254
+ {unprotect_access_level_config}
255
+ files:
256
+ "README.md":
257
+ branches:
258
+ - {no_access_branch.name}
259
+ delete: true
260
+ """
261
+
262
+ run_gitlabform(delete_file_on_protected_branch, project)
263
+
264
+ with pytest.raises(GitlabGetError):
265
+ project.files.get(ref=no_access_branch.name, file_path="README.md")
266
+
267
+ # the file must still exist on main
268
+ project_file = project.files.get(ref="main", file_path="README.md")
269
+ assert project_file.decode().decode("utf-8") == DEFAULT_README
270
+
271
+ # check that the branch protection was restored
272
+ protected_branches = project.protectedbranches.list()
273
+ assert any(pb.name == no_access_branch.name for pb in protected_branches)
274
+
180
275
  def test__delete_file_protected_branch(self, project, branch):
181
276
  set_file_specific_branch = f"""
182
277
  projects_and_groups:
@@ -754,7 +754,7 @@ class TestRemoteMirrorsProcessor:
754
754
 
755
755
  def test_remote_mirrors_print_details(
756
756
  self,
757
- project: Project,
757
+ project_for_function: Project,
758
758
  mirror_urls: Tuple[str, str, str],
759
759
  root_username,
760
760
  root_access_token,
@@ -764,7 +764,7 @@ class TestRemoteMirrorsProcessor:
764
764
  Validates that when print_details is true, the full mirror object
765
765
  details are retrieved and printed to the info logs.
766
766
  """
767
- target_path = project.path_with_namespace
767
+ target_path = project_for_function.path_with_namespace
768
768
  first_url, second_url, _ = mirror_urls
769
769
 
770
770
  # Normalize URLs as they will appear in the masked logs
@@ -773,7 +773,7 @@ class TestRemoteMirrorsProcessor:
773
773
 
774
774
  # 1. Setup: Ensure at least one mirror exists manually that is NOT in the config
775
775
  # This tests that the report shows the "Final State" of GitLab, not just the config.
776
- project.remote_mirrors.create({"url": second_url, "enabled": True, "auth_method": "password"})
776
+ project_for_function.remote_mirrors.create({"url": second_url, "enabled": True, "auth_method": "password"})
777
777
 
778
778
  # 2. Run GitLabForm with global print_details: true
779
779
  # Config only contains the first mirror
@@ -0,0 +1,120 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ from gitlabform.processors.project.merge_requests_approval_rules import MergeRequestsApprovalRules
4
+
5
+
6
+ class TestMergeRequestsApprovalRulesProcessor:
7
+ def setup_method(self):
8
+ self.gitlab = MagicMock()
9
+ with patch("gitlabform.processors.abstract_processor.GitlabWrapper"):
10
+ self.processor = MergeRequestsApprovalRules(self.gitlab)
11
+
12
+ @staticmethod
13
+ def _gitlab_rule(
14
+ name="standard",
15
+ approvals_required=1,
16
+ users=None,
17
+ groups=None,
18
+ protected_branches=None,
19
+ applies_to_all_protected_branches=False,
20
+ rule_type="regular",
21
+ ):
22
+ return {
23
+ "id": 1,
24
+ "name": name,
25
+ "rule_type": rule_type,
26
+ "report_type": None,
27
+ "eligible_approvers": [],
28
+ "approvals_required": approvals_required,
29
+ "users": users or [],
30
+ "groups": groups or [],
31
+ "contains_hidden_groups": False,
32
+ "protected_branches": protected_branches or [],
33
+ "applies_to_all_protected_branches": applies_to_all_protected_branches,
34
+ }
35
+
36
+ def test_no_update_when_users_match(self):
37
+ gitlab_rule = self._gitlab_rule(
38
+ users=[{"id": 5, "username": "alice"}, {"id": 7, "username": "bob"}],
39
+ )
40
+ config = {"name": "standard", "approvals_required": 1, "user_ids": [7, 5]}
41
+
42
+ assert self.processor._needs_update(gitlab_rule, config) is False
43
+
44
+ def test_no_update_when_groups_match(self):
45
+ gitlab_rule = self._gitlab_rule(groups=[{"id": 3, "name": "sec"}])
46
+ config = {"name": "standard", "approvals_required": 1, "group_ids": [3]}
47
+
48
+ assert self.processor._needs_update(gitlab_rule, config) is False
49
+
50
+ def test_no_update_when_protected_branches_match(self):
51
+ gitlab_rule = self._gitlab_rule(
52
+ protected_branches=[
53
+ {"id": 1, "name": "main"},
54
+ {"id": 2, "name": "release"},
55
+ ],
56
+ )
57
+ config = {
58
+ "name": "standard",
59
+ "approvals_required": 1,
60
+ "protected_branches": ["release", "main"],
61
+ }
62
+
63
+ assert self.processor._needs_update(gitlab_rule, config) is False
64
+
65
+ def test_no_update_when_config_omits_user_ids_and_gitlab_has_no_users(self):
66
+ gitlab_rule = self._gitlab_rule()
67
+ config = {"name": "standard", "approvals_required": 1}
68
+
69
+ assert self.processor._needs_update(gitlab_rule, config) is False
70
+
71
+ def test_no_update_when_gitlab_omits_users_groups_protected_branches_keys(self):
72
+ gitlab_rule = {
73
+ "id": 1,
74
+ "name": "standard",
75
+ "rule_type": "regular",
76
+ "approvals_required": 1,
77
+ }
78
+ config = {"name": "standard", "approvals_required": 1}
79
+
80
+ assert self.processor._needs_update(gitlab_rule, config) is False
81
+
82
+ def test_update_when_user_ids_differ(self):
83
+ gitlab_rule = self._gitlab_rule(users=[{"id": 5, "username": "alice"}])
84
+ config = {"name": "standard", "approvals_required": 1, "user_ids": [9]}
85
+
86
+ assert self.processor._needs_update(gitlab_rule, config) is True
87
+
88
+ def test_update_when_config_clears_users_but_gitlab_has_users(self):
89
+ gitlab_rule = self._gitlab_rule(users=[{"id": 5, "username": "alice"}])
90
+ config = {"name": "standard", "approvals_required": 1}
91
+
92
+ assert self.processor._needs_update(gitlab_rule, config) is True
93
+
94
+ def test_update_when_protected_branches_differ(self):
95
+ gitlab_rule = self._gitlab_rule(
96
+ protected_branches=[{"id": 1, "name": "main"}],
97
+ )
98
+ config = {
99
+ "name": "standard",
100
+ "approvals_required": 1,
101
+ "protected_branches": ["release"],
102
+ }
103
+
104
+ assert self.processor._needs_update(gitlab_rule, config) is True
105
+
106
+ def test_update_when_approvals_required_changes(self):
107
+ gitlab_rule = self._gitlab_rule(approvals_required=1)
108
+ config = {"name": "standard", "approvals_required": 2}
109
+
110
+ assert self.processor._needs_update(gitlab_rule, config) is True
111
+
112
+ def test_no_update_for_any_approver_rule(self):
113
+ gitlab_rule = self._gitlab_rule(rule_type="any_approver", approvals_required=0)
114
+ config = {
115
+ "name": "standard",
116
+ "approvals_required": 0,
117
+ "rule_type": "any_approver",
118
+ }
119
+
120
+ assert self.processor._needs_update(gitlab_rule, config) is False