gitlabform 5.0.0rc1__tar.gz → 5.0.2__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 (86) hide show
  1. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/PKG-INFO +11 -11
  2. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/README.md +1 -1
  3. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/configuration/core.py +1 -1
  4. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/core.py +6 -3
  5. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/branches_processor.py +45 -17
  6. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform.egg-info/PKG-INFO +11 -11
  7. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform.egg-info/SOURCES.txt +0 -1
  8. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform.egg-info/requires.txt +8 -8
  9. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/pyproject.toml +11 -11
  10. gitlabform-5.0.0rc1/gitlabform/processors/single_entity_processor.py +0 -59
  11. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/LICENSE +0 -0
  12. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/__init__.py +0 -0
  13. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/configuration/__init__.py +0 -0
  14. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/configuration/common.py +0 -0
  15. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/configuration/groups.py +0 -0
  16. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/configuration/projects.py +0 -0
  17. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/configuration/transform.py +0 -0
  18. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/constants.py +0 -0
  19. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/__init__.py +0 -0
  20. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/commits.py +0 -0
  21. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/group_badges.py +0 -0
  22. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/group_ldap_links.py +0 -0
  23. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/groups.py +0 -0
  24. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/merge_requests.py +0 -0
  25. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/pipelines.py +0 -0
  26. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/project_badges.py +0 -0
  27. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/project_deploy_keys.py +0 -0
  28. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/project_merge_requests_approvals.py +0 -0
  29. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/project_protected_environments.py +0 -0
  30. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/projects.py +0 -0
  31. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/python_gitlab.py +0 -0
  32. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/gitlab/variables.py +0 -0
  33. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/lists/__init__.py +0 -0
  34. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/lists/filter.py +0 -0
  35. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/lists/groups.py +0 -0
  36. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/lists/projects.py +0 -0
  37. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/output.py +0 -0
  38. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/__init__.py +0 -0
  39. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/abstract_processor.py +0 -0
  40. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/application/__init__.py +0 -0
  41. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/application/application_settings_processor.py +0 -0
  42. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/defining_keys.py +0 -0
  43. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/__init__.py +0 -0
  44. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_badges_processor.py +0 -0
  45. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_hooks_processor.py +0 -0
  46. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_labels_processor.py +0 -0
  47. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_ldap_links_processor.py +0 -0
  48. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_members_processor.py +0 -0
  49. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_push_rules_processor.py +0 -0
  50. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_saml_links_processor.py +0 -0
  51. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_settings_processor.py +0 -0
  52. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/group/group_variables_processor.py +0 -0
  53. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/multiple_entities_processor.py +0 -0
  54. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/__init__.py +0 -0
  55. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/badges_processor.py +0 -0
  56. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/deploy_keys_processor.py +0 -0
  57. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/files_processor.py +0 -0
  58. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/hooks_processor.py +0 -0
  59. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/integrations_processor.py +0 -0
  60. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/job_token_scope_processor.py +0 -0
  61. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/members_processor.py +0 -0
  62. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/merge_requests_approval_rules.py +0 -0
  63. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/merge_requests_approvals.py +0 -0
  64. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/project_labels_processor.py +0 -0
  65. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/project_processor.py +0 -0
  66. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/project_push_rules_processor.py +0 -0
  67. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/project_security_settings.py +0 -0
  68. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/project_settings_processor.py +0 -0
  69. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/project_variables_processor.py +0 -0
  70. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/remote_mirrors_processor.py +0 -0
  71. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/resource_groups_processor.py +0 -0
  72. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/schedules_processor.py +0 -0
  73. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/project/tags_processor.py +0 -0
  74. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/shared/__init__.py +0 -0
  75. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/shared/protected_environments_processor.py +0 -0
  76. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/util/__init__.py +0 -0
  77. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/util/decorators.py +0 -0
  78. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/util/difference_logger.py +0 -0
  79. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/util/labels_processor.py +0 -0
  80. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/processors/util/variables_processor.py +0 -0
  81. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/run.py +0 -0
  82. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform/util.py +0 -0
  83. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform.egg-info/dependency_links.txt +0 -0
  84. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform.egg-info/entry_points.txt +0 -0
  85. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/gitlabform.egg-info/top_level.txt +0 -0
  86. {gitlabform-5.0.0rc1 → gitlabform-5.0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlabform
3
- Version: 5.0.0rc1
3
+ Version: 5.0.2
4
4
  Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
5
5
  Author: Greg Dubicki and Contributors
6
6
  Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
@@ -17,10 +17,10 @@ Classifier: Operating System :: POSIX :: Linux
17
17
  Classifier: Operating System :: MacOS
18
18
  Classifier: Operating System :: Microsoft :: Windows
19
19
  Classifier: Topic :: Software Development :: Version Control :: Git
20
- Requires-Python: >=3.14.0
20
+ Requires-Python: >=3.12.0
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: certifi==2026.1.4
23
+ Requires-Dist: certifi==2026.2.25
24
24
  Requires-Dist: cli-ui==0.19.0
25
25
  Requires-Dist: ez-yaml==1.2.0
26
26
  Requires-Dist: Jinja2==3.1.6
@@ -28,23 +28,23 @@ Requires-Dist: luddite==1.0.4
28
28
  Requires-Dist: MarkupSafe==3.0.3
29
29
  Requires-Dist: mergedeep==1.3.4
30
30
  Requires-Dist: packaging==26.0
31
- Requires-Dist: python-gitlab==8.0.0
32
- Requires-Dist: python-gitlab[graphql]==8.0.0
31
+ Requires-Dist: python-gitlab==8.1.0
32
+ Requires-Dist: python-gitlab[graphql]==8.1.0
33
33
  Requires-Dist: requests==2.32.5
34
34
  Requires-Dist: ruamel.yaml==0.17.21
35
35
  Requires-Dist: yamlpath==3.8.2
36
36
  Provides-Extra: test
37
- Requires-Dist: coverage==7.13.3; extra == "test"
38
- Requires-Dist: cryptography==46.0.4; extra == "test"
39
- Requires-Dist: deepdiff==8.6.1; extra == "test"
37
+ Requires-Dist: coverage==7.13.5; extra == "test"
38
+ Requires-Dist: cryptography==46.0.5; extra == "test"
39
+ Requires-Dist: deepdiff==8.6.2; extra == "test"
40
40
  Requires-Dist: mypy==1.19.1; extra == "test"
41
41
  Requires-Dist: mypy-extensions==1.1.0; extra == "test"
42
42
  Requires-Dist: pre-commit==2.21.0; extra == "test"
43
43
  Requires-Dist: pytest==9.0.2; extra == "test"
44
- Requires-Dist: pytest-cov==7.0.0; extra == "test"
44
+ Requires-Dist: pytest-cov==7.1.0; extra == "test"
45
45
  Requires-Dist: pytest-rerunfailures==16.1; extra == "test"
46
46
  Requires-Dist: types-requests==2.32.4.20260107; extra == "test"
47
- Requires-Dist: types-setuptools==81.0.0.20260209; extra == "test"
47
+ Requires-Dist: types-setuptools==82.0.0.20260210; extra == "test"
48
48
  Requires-Dist: xkcdpass==1.30.0; extra == "test"
49
49
  Provides-Extra: docs
50
50
  Requires-Dist: mkdocs; extra == "docs"
@@ -56,7 +56,7 @@ Dynamic: license-file
56
56
  [![Downloads](https://static.pepy.tech/badge/gitlabform/month)](https://pepy.tech/project/gitlabform)
57
57
  [![code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
58
58
  [![codecov](https://codecov.io/gh/gitlabform/gitlabform/branch/main/graph/badge.svg?token=NOMttkpB2A)](https://codecov.io/gh/gitlabform/gitlabform)
59
- [![gitlabform](https://snyk.io/advisor/python/gitlabform/badge.svg)](https://snyk.io/advisor/python/gitlabform)
59
+ [![snyk](https://snyk.io/test/github/gitlabform/gitlabform/badge.svg)](https://security.snyk.io/package/pip/gitlabform)
60
60
 
61
61
  <img src="https://raw.githubusercontent.com/gitlabform/gitlabform/main/docs/images/gitlabform-logo.png" width="600px" alt="logo">
62
62
 
@@ -3,7 +3,7 @@
3
3
  [![Downloads](https://static.pepy.tech/badge/gitlabform/month)](https://pepy.tech/project/gitlabform)
4
4
  [![code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
5
5
  [![codecov](https://codecov.io/gh/gitlabform/gitlabform/branch/main/graph/badge.svg?token=NOMttkpB2A)](https://codecov.io/gh/gitlabform/gitlabform)
6
- [![gitlabform](https://snyk.io/advisor/python/gitlabform/badge.svg)](https://snyk.io/advisor/python/gitlabform)
6
+ [![snyk](https://snyk.io/test/github/gitlabform/gitlabform/badge.svg)](https://security.snyk.io/package/pip/gitlabform)
7
7
 
8
8
  <img src="https://raw.githubusercontent.com/gitlabform/gitlabform/main/docs/images/gitlabform-logo.png" width="600px" alt="logo">
9
9
 
@@ -66,7 +66,7 @@ class ConfigurationCore(ABC):
66
66
  # we are NOT checking for the existence of non-empty 'projects_and_groups' key here
67
67
  # as it would break using GitLabForm as a library
68
68
 
69
- except FileNotFoundError, OSError:
69
+ except (FileNotFoundError, OSError):
70
70
  raise ConfigFileNotFoundException(config_path)
71
71
 
72
72
  except Exception as e:
@@ -72,9 +72,12 @@ class GitLabCore:
72
72
  )
73
73
 
74
74
  try:
75
- version = self._make_requests_to_api("version")
76
- verbose(f"Connected to GitLab version: {version['version']} ({version['revision']})")
77
- self.version = version["version"]
75
+ version_response = self._make_requests_to_api("version")
76
+ verbose(
77
+ f"Connected to GitLab version: {version_response['version']} ({version_response['revision']}), Enterprise Edition: {version_response['enterprise']}"
78
+ )
79
+ self.version = version_response["version"]
80
+ self.enterprise = version_response["enterprise"]
78
81
 
79
82
  current_user = self._make_requests_to_api("user")
80
83
  if current_user.get("is_admin", False):
@@ -1,14 +1,15 @@
1
- from typing import Optional
1
+ from typing import Optional, Any
2
+
2
3
  from cli_ui import info, warning, error, fatal, debug as verbose
3
4
  from gitlab import (
4
5
  GitlabGetError,
5
6
  GitlabDeleteError,
6
- GitlabCreateError,
7
+ GitlabOperationError,
7
8
  )
8
9
  from gitlab.v4.objects import Project, ProjectProtectedBranch
9
10
 
10
11
  from gitlabform.constants import EXIT_INVALID_INPUT, EXIT_PROCESSING_ERROR
11
- from gitlabform.gitlab import GitLab, AccessLevel
12
+ from gitlabform.gitlab import GitLab
12
13
  from gitlabform.processors.abstract_processor import AbstractProcessor
13
14
 
14
15
 
@@ -80,10 +81,13 @@ class BranchesProcessor(AbstractProcessor):
80
81
  return
81
82
 
82
83
  # https://docs.gitlab.com/api/protected_branches/#update-a-protected-branch was only introduced after 15.6
83
- # for user's on older versions of Gitlab we need to unprotect and then reprotect the branch to apply the
84
+ # for user's on older versions of Gitlab or Community Edition (https://gitlab.com/rluna-gitlab/gitlab-ce/-/work_items/37)
85
+ # We need to unprotect and then reprotect the branch to apply the
84
86
  # defined configuration
85
- if self.gitlab.is_version_less_than("15.6.0"):
86
- self.process_branch_config_gitlab_15_6_0_or_older(branch_config, branch_name, project, protected_branch)
87
+ if self.gitlab.is_version_less_than("15.6.0") or (self.gitlab.enterprise == False):
88
+ self.process_branch_config_gitlab_under_15_6_0_or_ce(
89
+ branch_config, branch_name, project, protected_branch
90
+ )
87
91
  return
88
92
 
89
93
  # For later Gitlab versions we dynamically generate the data to send to the update endpoint based on the
@@ -116,7 +120,7 @@ class BranchesProcessor(AbstractProcessor):
116
120
  verbose("Creating data to update merge_access_levels as necessary")
117
121
  merge_access_items_patch_data = self.build_patch_request_data(
118
122
  transformed_access_levels=transformed_branch_config.get("merge_access_levels"),
119
- existing_records=tuple(protected_branch.merge_access_levels),
123
+ existing_records=tuple(self._get_list_attribute(protected_branch, "merge_access_levels")),
120
124
  )
121
125
  if len(merge_access_items_patch_data) > 0:
122
126
  protected_branch_api_patch_data["allowed_to_merge"] = merge_access_items_patch_data
@@ -124,16 +128,18 @@ class BranchesProcessor(AbstractProcessor):
124
128
  verbose("Creating data to update push_access_levels as necessary")
125
129
  push_access_items_patch_data = self.build_patch_request_data(
126
130
  transformed_access_levels=transformed_branch_config.get("push_access_levels"),
127
- existing_records=tuple(protected_branch.push_access_levels),
131
+ existing_records=tuple(self._get_list_attribute(protected_branch, "push_access_levels")),
128
132
  )
129
133
  if len(push_access_items_patch_data) > 0:
130
134
  protected_branch_api_patch_data["allowed_to_push"] = push_access_items_patch_data
131
135
 
132
136
  verbose("Creating data to update unprotect_access_levels as necessary")
137
+
133
138
  unprotect_access_items_patch_data = self.build_patch_request_data(
134
139
  transformed_access_levels=transformed_branch_config.get("unprotect_access_levels"),
135
- existing_records=tuple(protected_branch.unprotect_access_levels),
140
+ existing_records=tuple(self._get_list_attribute(protected_branch, "unprotect_access_levels")),
136
141
  )
142
+
137
143
  if len(unprotect_access_items_patch_data) > 0:
138
144
  protected_branch_api_patch_data["allowed_to_unprotect"] = unprotect_access_items_patch_data
139
145
 
@@ -146,9 +152,9 @@ class BranchesProcessor(AbstractProcessor):
146
152
  info(f"Removing branch protection for {branch_name}")
147
153
  self.unprotect_branch(protected_branch)
148
154
 
149
- def process_branch_config_gitlab_15_6_0_or_older(self, branch_config, branch_name, project, protected_branch):
155
+ def process_branch_config_gitlab_under_15_6_0_or_ce(self, branch_config, branch_name, project, protected_branch):
150
156
  """
151
- Processes the branches configuration for gitlab version 15.6.0 or older,
157
+ Processes the branches configuration for gitlab version <=15.6.0 or Community Edition,
152
158
  first checking if the branch needs to be updated, if it does, then the branch will be unprotected prior to being
153
159
  reprotected with the YAML configuration.
154
160
  """
@@ -186,7 +192,7 @@ class BranchesProcessor(AbstractProcessor):
186
192
  project.protectedbranches.update(branch_name, branch_config)
187
193
  else:
188
194
  project.protectedbranches.create({"name": branch_name, **branch_config})
189
- except GitlabCreateError as e:
195
+ except GitlabOperationError as e:
190
196
  message = f"Protecting branch '{branch_name}' failed! Error '{e.error_message}"
191
197
 
192
198
  if self.strict:
@@ -374,15 +380,21 @@ class BranchesProcessor(AbstractProcessor):
374
380
  matching_item_to_be_created = None
375
381
 
376
382
  for item in patch_data:
377
- if (
383
+ # We prioritize user_id and group_id matches over access_level.
384
+ # This avoids ambiguity because a user/group record from GitLab also contains an access_level.
385
+ # If we matched by access_level first, we might incorrectly pair a specific user's rule
386
+ # with a generic role-based rule from the config.
387
+ if existing_records_user_id is not None and item.get("user_id") == existing_records_user_id:
388
+ matching_item_to_be_created = item
389
+ break
390
+ elif existing_records_group_id is not None and item.get("group_id") == existing_records_group_id:
391
+ matching_item_to_be_created = item
392
+ break
393
+ elif (
378
394
  existing_records_access_level is not None
379
395
  and item.get("access_level") == existing_records_access_level
380
396
  ):
381
397
  matching_item_to_be_created = item
382
- elif existing_records_user_id is not None and item.get("user_id") == existing_records_user_id:
383
- matching_item_to_be_created = item
384
- elif existing_records_group_id is not None and item.get("group_id") == existing_records_group_id:
385
- matching_item_to_be_created = item
386
398
 
387
399
  if matching_item_to_be_created is not None:
388
400
  # If we found an existing item matching one that has been configured, remove the item
@@ -436,3 +448,19 @@ class BranchesProcessor(AbstractProcessor):
436
448
  https://docs.gitlab.com/user/project/repository/branches/protected/#use-wildcard-rules
437
449
  """
438
450
  return "*" in branch
451
+
452
+ @staticmethod
453
+ def _get_list_attribute(protected_branch: ProjectProtectedBranch, attribute_name: str) -> list[Any]:
454
+ """
455
+ Gets list attribute such as unprotect_access_levels, merge_access_levels, push_access_levels, etc.
456
+ Uses the python-gitlab attributes raw dict rather than direct parameter to gracefully handle when an attribute
457
+ is not present in the API response.
458
+ For example in CE: unprotect_access_levels is not returned on the protected_branch, so trying to access directly
459
+ throws a runtime-exception
460
+ """
461
+ existing_list_value: list[Any] = []
462
+ # Get from the "attributes" as this is the raw dict
463
+ existing_attr = protected_branch.attributes.get(attribute_name)
464
+ if existing_attr is not None:
465
+ existing_list_value = existing_attr
466
+ return existing_list_value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitlabform
3
- Version: 5.0.0rc1
3
+ Version: 5.0.2
4
4
  Summary: 🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML
5
5
  Author: Greg Dubicki and Contributors
6
6
  Project-URL: Homepage, https://gitlabform.github.io/gitlabform/
@@ -17,10 +17,10 @@ Classifier: Operating System :: POSIX :: Linux
17
17
  Classifier: Operating System :: MacOS
18
18
  Classifier: Operating System :: Microsoft :: Windows
19
19
  Classifier: Topic :: Software Development :: Version Control :: Git
20
- Requires-Python: >=3.14.0
20
+ Requires-Python: >=3.12.0
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
- Requires-Dist: certifi==2026.1.4
23
+ Requires-Dist: certifi==2026.2.25
24
24
  Requires-Dist: cli-ui==0.19.0
25
25
  Requires-Dist: ez-yaml==1.2.0
26
26
  Requires-Dist: Jinja2==3.1.6
@@ -28,23 +28,23 @@ Requires-Dist: luddite==1.0.4
28
28
  Requires-Dist: MarkupSafe==3.0.3
29
29
  Requires-Dist: mergedeep==1.3.4
30
30
  Requires-Dist: packaging==26.0
31
- Requires-Dist: python-gitlab==8.0.0
32
- Requires-Dist: python-gitlab[graphql]==8.0.0
31
+ Requires-Dist: python-gitlab==8.1.0
32
+ Requires-Dist: python-gitlab[graphql]==8.1.0
33
33
  Requires-Dist: requests==2.32.5
34
34
  Requires-Dist: ruamel.yaml==0.17.21
35
35
  Requires-Dist: yamlpath==3.8.2
36
36
  Provides-Extra: test
37
- Requires-Dist: coverage==7.13.3; extra == "test"
38
- Requires-Dist: cryptography==46.0.4; extra == "test"
39
- Requires-Dist: deepdiff==8.6.1; extra == "test"
37
+ Requires-Dist: coverage==7.13.5; extra == "test"
38
+ Requires-Dist: cryptography==46.0.5; extra == "test"
39
+ Requires-Dist: deepdiff==8.6.2; extra == "test"
40
40
  Requires-Dist: mypy==1.19.1; extra == "test"
41
41
  Requires-Dist: mypy-extensions==1.1.0; extra == "test"
42
42
  Requires-Dist: pre-commit==2.21.0; extra == "test"
43
43
  Requires-Dist: pytest==9.0.2; extra == "test"
44
- Requires-Dist: pytest-cov==7.0.0; extra == "test"
44
+ Requires-Dist: pytest-cov==7.1.0; extra == "test"
45
45
  Requires-Dist: pytest-rerunfailures==16.1; extra == "test"
46
46
  Requires-Dist: types-requests==2.32.4.20260107; extra == "test"
47
- Requires-Dist: types-setuptools==81.0.0.20260209; extra == "test"
47
+ Requires-Dist: types-setuptools==82.0.0.20260210; extra == "test"
48
48
  Requires-Dist: xkcdpass==1.30.0; extra == "test"
49
49
  Provides-Extra: docs
50
50
  Requires-Dist: mkdocs; extra == "docs"
@@ -56,7 +56,7 @@ Dynamic: license-file
56
56
  [![Downloads](https://static.pepy.tech/badge/gitlabform/month)](https://pepy.tech/project/gitlabform)
57
57
  [![code style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
58
58
  [![codecov](https://codecov.io/gh/gitlabform/gitlabform/branch/main/graph/badge.svg?token=NOMttkpB2A)](https://codecov.io/gh/gitlabform/gitlabform)
59
- [![gitlabform](https://snyk.io/advisor/python/gitlabform/badge.svg)](https://snyk.io/advisor/python/gitlabform)
59
+ [![snyk](https://snyk.io/test/github/gitlabform/gitlabform/badge.svg)](https://security.snyk.io/package/pip/gitlabform)
60
60
 
61
61
  <img src="https://raw.githubusercontent.com/gitlabform/gitlabform/main/docs/images/gitlabform-logo.png" width="600px" alt="logo">
62
62
 
@@ -41,7 +41,6 @@ gitlabform/processors/__init__.py
41
41
  gitlabform/processors/abstract_processor.py
42
42
  gitlabform/processors/defining_keys.py
43
43
  gitlabform/processors/multiple_entities_processor.py
44
- gitlabform/processors/single_entity_processor.py
45
44
  gitlabform/processors/application/__init__.py
46
45
  gitlabform/processors/application/application_settings_processor.py
47
46
  gitlabform/processors/group/__init__.py
@@ -1,4 +1,4 @@
1
- certifi==2026.1.4
1
+ certifi==2026.2.25
2
2
  cli-ui==0.19.0
3
3
  ez-yaml==1.2.0
4
4
  Jinja2==3.1.6
@@ -6,8 +6,8 @@ luddite==1.0.4
6
6
  MarkupSafe==3.0.3
7
7
  mergedeep==1.3.4
8
8
  packaging==26.0
9
- python-gitlab==8.0.0
10
- python-gitlab[graphql]==8.0.0
9
+ python-gitlab==8.1.0
10
+ python-gitlab[graphql]==8.1.0
11
11
  requests==2.32.5
12
12
  ruamel.yaml==0.17.21
13
13
  yamlpath==3.8.2
@@ -17,15 +17,15 @@ mkdocs
17
17
  mkdocs-material
18
18
 
19
19
  [test]
20
- coverage==7.13.3
21
- cryptography==46.0.4
22
- deepdiff==8.6.1
20
+ coverage==7.13.5
21
+ cryptography==46.0.5
22
+ deepdiff==8.6.2
23
23
  mypy==1.19.1
24
24
  mypy-extensions==1.1.0
25
25
  pre-commit==2.21.0
26
26
  pytest==9.0.2
27
- pytest-cov==7.0.0
27
+ pytest-cov==7.1.0
28
28
  pytest-rerunfailures==16.1
29
29
  types-requests==2.32.4.20260107
30
- types-setuptools==81.0.0.20260209
30
+ types-setuptools==82.0.0.20260210
31
31
  xkcdpass==1.30.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitlabform"
7
- version = "5.0.0-rc.1"
7
+ version = "5.0.2"
8
8
  authors = [{ name = "Greg Dubicki and Contributors" }]
9
9
  description = "🏗 Specialized configuration as a code tool for GitLab projects, groups and more using hierarchical configuration written in YAML"
10
10
  keywords = ["cli", "yaml", "gitlab", "configuration-as-code"]
@@ -21,9 +21,9 @@ classifiers = [
21
21
  "Operating System :: Microsoft :: Windows",
22
22
  "Topic :: Software Development :: Version Control :: Git",
23
23
  ]
24
- requires-python = ">=3.14.0"
24
+ requires-python = ">=3.12.0"
25
25
  dependencies = [
26
- "certifi==2026.1.4",
26
+ "certifi==2026.2.25",
27
27
  "cli-ui==0.19.0",
28
28
  "ez-yaml==1.2.0",
29
29
  "Jinja2==3.1.6",
@@ -31,8 +31,8 @@ dependencies = [
31
31
  "MarkupSafe==3.0.3",
32
32
  "mergedeep==1.3.4",
33
33
  "packaging==26.0",
34
- "python-gitlab==8.0.0",
35
- "python-gitlab[graphql]==8.0.0",
34
+ "python-gitlab==8.1.0",
35
+ "python-gitlab[graphql]==8.1.0",
36
36
  "requests==2.32.5",
37
37
  "ruamel.yaml==0.17.21",
38
38
  "yamlpath==3.8.2",
@@ -50,17 +50,17 @@ content-type = "text/markdown"
50
50
 
51
51
  [project.optional-dependencies]
52
52
  test = [
53
- "coverage==7.13.3",
54
- "cryptography==46.0.4",
55
- "deepdiff==8.6.1",
53
+ "coverage==7.13.5",
54
+ "cryptography==46.0.5",
55
+ "deepdiff==8.6.2",
56
56
  "mypy==1.19.1",
57
57
  "mypy-extensions==1.1.0",
58
58
  "pre-commit==2.21.0",
59
59
  "pytest==9.0.2",
60
- "pytest-cov==7.0.0",
60
+ "pytest-cov==7.1.0",
61
61
  "pytest-rerunfailures==16.1",
62
62
  "types-requests==2.32.4.20260107",
63
- "types-setuptools==81.0.0.20260209",
63
+ "types-setuptools==82.0.0.20260210",
64
64
  "xkcdpass==1.30.0",
65
65
  ]
66
66
  docs = ["mkdocs", "mkdocs-material"]
@@ -74,9 +74,9 @@ exclude = ["tests*"]
74
74
 
75
75
  [tool.pytest.ini_options]
76
76
  filterwarnings = ["ignore::DeprecationWarning"]
77
-
78
77
  markers = [
79
78
  "requires_license: marks tests which require GitLab paid (Premium/Ultimate) license '-m \"not requires_license\"')",
79
+ "ce: marks tests which need to be run against GitLab CE image as well as the standard EE image"
80
80
  ]
81
81
 
82
82
  #uncomment below for debug logging ALWAYS, even if the tests are passing
@@ -1,59 +0,0 @@
1
- from logging import debug
2
- from cli_ui import debug as verbose
3
-
4
- import abc
5
- from typing import Callable, Optional
6
-
7
- from gitlabform.gitlab import GitLab
8
- from gitlabform.processors.abstract_processor import AbstractProcessor
9
- from gitlabform.processors.util.difference_logger import DifferenceLogger
10
-
11
-
12
- def noop():
13
- pass
14
-
15
-
16
- class SingleEntityProcessor(AbstractProcessor, metaclass=abc.ABCMeta):
17
- def __init__(
18
- self,
19
- configuration_name: str,
20
- gitlab: GitLab,
21
- get_method_name: str,
22
- edit_method_name: str,
23
- add_method_name: Optional[str] = None,
24
- ):
25
- super().__init__(configuration_name, gitlab)
26
- self.get_method: Callable = getattr(self.gitlab, get_method_name)
27
- self.edit_method: Callable = getattr(self.gitlab, edit_method_name)
28
- if add_method_name:
29
- self.add_method: Callable = getattr(self.gitlab, add_method_name)
30
- else:
31
- self.add_method = noop
32
-
33
- def _process_configuration(self, project_or_group: str, configuration: dict):
34
- entity_config = configuration[self.configuration_name]
35
-
36
- entity_in_gitlab = self.get_method(project_or_group)
37
- debug(f"{self.configuration_name} BEFORE: ^^^")
38
-
39
- if entity_in_gitlab:
40
- if self._needs_update(entity_in_gitlab, entity_config):
41
- verbose(f"Editing {self.configuration_name} in {project_or_group}")
42
- self.edit_method(project_or_group, entity_config)
43
- debug(f"{self.configuration_name} AFTER: ^^^")
44
- else:
45
- verbose(f"{self.configuration_name} in {project_or_group} doesn't need an update.")
46
- else:
47
- verbose(f"Adding {self.configuration_name} in {project_or_group}")
48
- self.add_method(project_or_group, entity_config)
49
- debug(f"{self.configuration_name} AFTER: ^^^")
50
-
51
- def _print_diff(self, project_or_project_and_group: str, entity_config, diff_only_changed: bool):
52
- entity_in_gitlab = self.get_method(project_or_project_and_group)
53
-
54
- DifferenceLogger.log_diff(
55
- f"{self.configuration_name} changes",
56
- entity_in_gitlab,
57
- entity_config,
58
- only_changed=diff_only_changed,
59
- )
File without changes
File without changes