gitlabber 1.2.6__tar.gz → 1.2.8__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 (54) hide show
  1. {gitlabber-1.2.6/gitlabber.egg-info → gitlabber-1.2.8}/PKG-INFO +27 -11
  2. {gitlabber-1.2.6 → gitlabber-1.2.8}/README.rst +8 -4
  3. gitlabber-1.2.8/gitlabber/__init__.py +2 -0
  4. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/cli.py +2 -9
  5. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/gitlab_tree.py +27 -16
  6. {gitlabber-1.2.6 → gitlabber-1.2.8/gitlabber.egg-info}/PKG-INFO +27 -11
  7. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber.egg-info/SOURCES.txt +2 -13
  8. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber.egg-info/requires.txt +6 -0
  9. gitlabber-1.2.8/pyproject.toml +79 -0
  10. gitlabber-1.2.8/setup.cfg +10 -0
  11. gitlabber-1.2.8/setup.py +3 -0
  12. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/gitlab_test_utils.py +16 -5
  13. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_e2e.py +16 -6
  14. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_gitlab_tree.py +73 -1
  15. gitlabber-1.2.6/gitlabber/__init__.py +0 -1
  16. gitlabber-1.2.6/setup.cfg +0 -24
  17. gitlabber-1.2.6/setup.py +0 -89
  18. gitlabber-1.2.6/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  19. gitlabber-1.2.6/tests/__pycache__/gitlab_test_utils.cpython-311.pyc +0 -0
  20. gitlabber-1.2.6/tests/__pycache__/io_test_util.cpython-311.pyc +0 -0
  21. gitlabber-1.2.6/tests/__pycache__/test_archive.cpython-311-pytest-8.2.2.pyc +0 -0
  22. gitlabber-1.2.6/tests/__pycache__/test_cli.cpython-311-pytest-8.2.2.pyc +0 -0
  23. gitlabber-1.2.6/tests/__pycache__/test_e2e.cpython-311-pytest-8.2.2.pyc +0 -0
  24. gitlabber-1.2.6/tests/__pycache__/test_format.cpython-311-pytest-8.2.2.pyc +0 -0
  25. gitlabber-1.2.6/tests/__pycache__/test_git.cpython-311-pytest-8.2.2.pyc +0 -0
  26. gitlabber-1.2.6/tests/__pycache__/test_gitlab_tree.cpython-311-pytest-8.2.2.pyc +0 -0
  27. gitlabber-1.2.6/tests/__pycache__/test_integration.cpython-311-pytest-8.2.2.pyc +0 -0
  28. gitlabber-1.2.6/tests/__pycache__/test_method.cpython-311-pytest-8.2.2.pyc +0 -0
  29. gitlabber-1.2.6/tests/__pycache__/test_naming.cpython-311-pytest-8.2.2.pyc +0 -0
  30. {gitlabber-1.2.6 → gitlabber-1.2.8}/LICENSE.txt +0 -0
  31. {gitlabber-1.2.6 → gitlabber-1.2.8}/MANIFEST.in +0 -0
  32. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/__main__.py +0 -0
  33. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/archive.py +0 -0
  34. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/format.py +0 -0
  35. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/git.py +0 -0
  36. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/method.py +0 -0
  37. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/naming.py +0 -0
  38. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber/progress.py +0 -0
  39. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber.egg-info/dependency_links.txt +0 -0
  40. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber.egg-info/entry_points.txt +0 -0
  41. {gitlabber-1.2.6 → gitlabber-1.2.8}/gitlabber.egg-info/top_level.txt +0 -0
  42. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/__init__.py +0 -0
  43. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/io_test_util.py +0 -0
  44. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test-input.yaml +0 -0
  45. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test-output.json +0 -0
  46. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test-output.tree +0 -0
  47. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test-output.yaml +0 -0
  48. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_archive.py +0 -0
  49. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_cli.py +0 -0
  50. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_format.py +0 -0
  51. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_git.py +0 -0
  52. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_integration.py +0 -0
  53. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_method.py +0 -0
  54. {gitlabber-1.2.6 → gitlabber-1.2.8}/tests/test_naming.py +0 -0
@@ -1,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: gitlabber
3
- Version: 1.2.6
3
+ Version: 1.2.8
4
4
  Summary: A Gitlab clone/pull utility for backing up or cloning Gitlab groups
5
- Home-page: https://github.com/ezbz/gitlabber
6
- Download-URL: https://github.com/ezbz/gitlabber/archive/main.zip
7
- Author: Erez Mazor
8
- Author-email: erezmazor@gmail.com
5
+ Author-email: Erez Mazor <erezmazor@gmail.com>
9
6
  License: MIT
7
+ Project-URL: Homepage, https://github.com/ezbz/gitlabber
8
+ Project-URL: Download, https://github.com/ezbz/gitlabber/archive/main.zip
10
9
  Keywords: gitlab,python,cli
11
10
  Classifier: Development Status :: 4 - Beta
12
11
  Classifier: Intended Audience :: Developers
@@ -15,14 +14,27 @@ Classifier: License :: OSI Approved :: MIT License
15
14
  Classifier: Natural Language :: English
16
15
  Classifier: Operating System :: OS Independent
17
16
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
17
  Classifier: Programming Language :: Python :: 3.9
20
18
  Classifier: Programming Language :: Python :: 3.10
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
23
22
  Requires-Python: >=3
24
23
  Description-Content-Type: text/x-rst
25
24
  License-File: LICENSE.txt
25
+ Requires-Dist: docopt
26
+ Requires-Dist: anytree
27
+ Requires-Dist: globre
28
+ Requires-Dist: pyyaml
29
+ Requires-Dist: tqdm
30
+ Requires-Dist: GitPython
31
+ Requires-Dist: python-gitlab
32
+ Provides-Extra: test
33
+ Requires-Dist: coverage; extra == "test"
34
+ Requires-Dist: pytest; extra == "test"
35
+ Requires-Dist: pytest-cov; extra == "test"
36
+ Requires-Dist: pytest-integration; extra == "test"
37
+ Dynamic: license-file
26
38
 
27
39
  .. image:: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml/badge.svg?branch=master
28
40
  :target: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml
@@ -40,7 +52,7 @@ License-File: LICENSE.txt
40
52
  :target: https://pypi.python.org/pypi/gitlabber/
41
53
 
42
54
  .. image:: https://readthedocs.org/projects/gitlabber/badge/?version=latest&style=plastic
43
- :target: https://gitlabber.readthedocs.io/en/latest/README.html
55
+ :target: https://app.readthedocs.org/projects/gitlabber/
44
56
 
45
57
 
46
58
  Gitlabber
@@ -104,7 +116,7 @@ Usage
104
116
 
105
117
  * To see how to use glob patterns and regex to filter tree nodes, see the `globre project page <https://pypi.org/project/globre/#details>`_.
106
118
 
107
- * Include/Exclude patterns do not work at the API level but work on the results returned from the API, for large Gitlab installations this can take a lot of time, if you need to reduce the amound of API calls for such projects use the ``--group-search`` parameter to search only for the top level groups the interest you using the `Gitlab Group Search API <https://docs.gitlab.com/ee/api/groups.html#search-for-group>` which allows you to do a partial like query for a Group's path or name
119
+ * Include/Exclude patterns do not work at the API level but work on the results returned from the API, for large Gitlab installations this can take a lot of time, if you need to reduce the amound of API calls for such projects use the ``--group-search`` parameter to search only for the top level groups the interest you using the `Gitlab Group Search API <https://docs.gitlab.com/ee/api/groups.html#search-for-group>`_ which allows you to do a partial like query for a Group's path or name
108
120
 
109
121
  * Cloning vs Pulling: when running Gitlabber consecutively with the same parameters, it will scan the local tree structure; if the project directory exists and is a valid git repository (has .git folder in it) Gitlabber will perform a git pull in the directory, otherwise the project directory will be created and the GitLab project will be cloned into it.
110
122
 
@@ -145,7 +157,6 @@ Usage
145
157
  comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
146
158
  -r, --recursive clone/pull git submodules recursively
147
159
  -F, --use-fetch clone/fetch git repository (mirrored repositories)
148
- -d, --dont-checkout don't checkout pulled git repository
149
160
  -s, --include-shared include shared projects in the results
150
161
  -g term, --group-search term
151
162
  only include groups matching the search term, filtering done at the API level (useful for large projects, see: https://docs.gitlab.com/ee/api/groups.html#search-for-group works with partial names of path or name)
@@ -174,7 +185,7 @@ Usage
174
185
  clone projects that start with a case insensitive 'w' using a regular expression:
175
186
  gitlabber -i '/{[w].*}' .
176
187
 
177
- clone a user's personal projects to username-personal-projects
188
+ clone the personal projects to username-personal-projects
178
189
  gitlabber -U .
179
190
 
180
191
  perform a shallow clone of the git repositories
@@ -201,3 +212,8 @@ Known Limitations
201
212
  * Renaming, moving and deleting projects: Gitlabber doesn't maintain local tree state (projects and groups). For that reason is does not rename move or delete local projects when they are modified on the server. When projects are moved or renamed, Gitlabber will clone them again under their new name or location. When deleted, Gitlabber will not delete the local project.
202
213
  * Folder naming strategy: Consecutively running Gitlabber with different values for the ``-n`` parameter will produce undesirable results. Use the same value as previous runs, or simply don't change it from the default (project name).
203
214
  * If you're going to clone a large number of projects, observe rate limits `for gitlab.com <https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits/>`_, and `for on-premise installations <https://docs.gitlab.com/ee/security/rate_limits.html>`_.
215
+
216
+
217
+ Links
218
+ -----
219
+ * Gitlabber `tutorial <https://medium.com/@natskvi/clone-all-your-gitlab-groups-repos-before-starting-to-code-dd559ec5c8d6>`_ by nsk
@@ -14,7 +14,7 @@
14
14
  :target: https://pypi.python.org/pypi/gitlabber/
15
15
 
16
16
  .. image:: https://readthedocs.org/projects/gitlabber/badge/?version=latest&style=plastic
17
- :target: https://gitlabber.readthedocs.io/en/latest/README.html
17
+ :target: https://app.readthedocs.org/projects/gitlabber/
18
18
 
19
19
 
20
20
  Gitlabber
@@ -78,7 +78,7 @@ Usage
78
78
 
79
79
  * To see how to use glob patterns and regex to filter tree nodes, see the `globre project page <https://pypi.org/project/globre/#details>`_.
80
80
 
81
- * Include/Exclude patterns do not work at the API level but work on the results returned from the API, for large Gitlab installations this can take a lot of time, if you need to reduce the amound of API calls for such projects use the ``--group-search`` parameter to search only for the top level groups the interest you using the `Gitlab Group Search API <https://docs.gitlab.com/ee/api/groups.html#search-for-group>` which allows you to do a partial like query for a Group's path or name
81
+ * Include/Exclude patterns do not work at the API level but work on the results returned from the API, for large Gitlab installations this can take a lot of time, if you need to reduce the amound of API calls for such projects use the ``--group-search`` parameter to search only for the top level groups the interest you using the `Gitlab Group Search API <https://docs.gitlab.com/ee/api/groups.html#search-for-group>`_ which allows you to do a partial like query for a Group's path or name
82
82
 
83
83
  * Cloning vs Pulling: when running Gitlabber consecutively with the same parameters, it will scan the local tree structure; if the project directory exists and is a valid git repository (has .git folder in it) Gitlabber will perform a git pull in the directory, otherwise the project directory will be created and the GitLab project will be cloned into it.
84
84
 
@@ -119,7 +119,6 @@ Usage
119
119
  comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
120
120
  -r, --recursive clone/pull git submodules recursively
121
121
  -F, --use-fetch clone/fetch git repository (mirrored repositories)
122
- -d, --dont-checkout don't checkout pulled git repository
123
122
  -s, --include-shared include shared projects in the results
124
123
  -g term, --group-search term
125
124
  only include groups matching the search term, filtering done at the API level (useful for large projects, see: https://docs.gitlab.com/ee/api/groups.html#search-for-group works with partial names of path or name)
@@ -148,7 +147,7 @@ Usage
148
147
  clone projects that start with a case insensitive 'w' using a regular expression:
149
148
  gitlabber -i '/{[w].*}' .
150
149
 
151
- clone a user's personal projects to username-personal-projects
150
+ clone the personal projects to username-personal-projects
152
151
  gitlabber -U .
153
152
 
154
153
  perform a shallow clone of the git repositories
@@ -175,3 +174,8 @@ Known Limitations
175
174
  * Renaming, moving and deleting projects: Gitlabber doesn't maintain local tree state (projects and groups). For that reason is does not rename move or delete local projects when they are modified on the server. When projects are moved or renamed, Gitlabber will clone them again under their new name or location. When deleted, Gitlabber will not delete the local project.
176
175
  * Folder naming strategy: Consecutively running Gitlabber with different values for the ``-n`` parameter will produce undesirable results. Use the same value as previous runs, or simply don't change it from the default (project name).
177
176
  * If you're going to clone a large number of projects, observe rate limits `for gitlab.com <https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits/>`_, and `for on-premise installations <https://docs.gitlab.com/ee/security/rate_limits.html>`_.
177
+
178
+
179
+ Links
180
+ -----
181
+ * Gitlabber `tutorial <https://medium.com/@natskvi/clone-all-your-gitlab-groups-repos-before-starting-to-code-dd559ec5c8d6>`_ by nsk
@@ -0,0 +1,2 @@
1
+ """ Gitlabber """
2
+ __version__ = '1.2.8'
@@ -1,4 +1,3 @@
1
-
2
1
  import os
3
2
  import sys
4
3
  import logging
@@ -73,7 +72,7 @@ def config_logging(args):
73
72
 
74
73
 
75
74
  def parse_args(argv=None):
76
- example_text = '''examples:
75
+ example_text = r'''examples:
77
76
 
78
77
  clone an entire gitlab tree using a url and a token:
79
78
  gitlabber -t <personal access token> -u <gitlab url>
@@ -93,7 +92,7 @@ def parse_args(argv=None):
93
92
  clone projects that start with a case insensitive 'w' using a regular expression:
94
93
  gitlabber -i '/{[w].*}' .
95
94
 
96
- clone a user's personal projects to username-personal-projects
95
+ clone the user personal projects to username-personal-projects
97
96
  gitlabber -U .
98
97
 
99
98
  perform a shallow clone of the git repositories
@@ -200,12 +199,6 @@ def parse_args(argv=None):
200
199
  action='store_true',
201
200
  default=False,
202
201
  help='clone/fetch git repository (mirrored repositories)')
203
- parser.add_argument(
204
- '-d',
205
- '--dont-checkout',
206
- action='store_true',
207
- default=False,
208
- help="don't checkout pulled git repository")
209
202
  parser.add_argument(
210
203
  '-s',
211
204
  '--include-shared',
@@ -119,26 +119,37 @@ class GitlabTree:
119
119
  projects = group.projects.list(archived=self.archived, with_shared=self.include_shared, get_all=True)
120
120
  self.progress.update_progress_length(len(projects))
121
121
  self.add_projects(parent, projects)
122
+
123
+ if self.include_shared and hasattr(group, 'shared_projects'):
124
+ shared_projects = group.shared_projects.list(get_all=True)
125
+ self.progress.update_progress_length(len(shared_projects))
126
+ self.add_projects(parent, shared_projects)
122
127
  except GitlabListError as error:
123
128
  log.error(f"Error getting projects on {group.name} id: [{group.id}] error message: [{error.error_message}]")
124
129
 
125
130
  def get_subgroups(self, group, parent):
126
- subgroups = group.subgroups.list(as_list=False, get_all=True)
127
- self.progress.update_progress_length(len(subgroups))
128
- for subgroup_def in subgroups:
129
- try:
130
- subgroup = self.gitlab.groups.get(subgroup_def.id)
131
- subgroup_id = subgroup.name if self.naming == FolderNaming.NAME else subgroup.path
132
- node = self.make_node("subgroup", subgroup_id, parent, url=subgroup.web_url)
133
- self.progress.show_progress(node.name, 'group')
134
- self.get_subgroups(subgroup, node)
135
- self.get_projects(subgroup, node)
136
- except GitlabListError as error:
137
- if error.response_code == 404:
138
- log.error(f"{error.response_code} error while listing subgroup with name: {group.name} [id: {group.id}]. Check your permissions as you may not have access to it. Message: {error.error_message}")
139
- continue
140
- else:
141
- raise error
131
+ try:
132
+ subgroups = group.subgroups.list(as_list=False, get_all=True)
133
+ self.progress.update_progress_length(len(subgroups))
134
+ for subgroup_def in subgroups:
135
+ try:
136
+ subgroup = self.gitlab.groups.get(subgroup_def.id)
137
+ subgroup_id = subgroup.name if self.naming == FolderNaming.NAME else subgroup.path
138
+ node = self.make_node("subgroup", subgroup_id, parent, url=subgroup.web_url)
139
+ self.progress.show_progress(node.name, 'group')
140
+ self.get_subgroups(subgroup, node)
141
+ self.get_projects(subgroup, node)
142
+ except GitlabGetError as error:
143
+ if error.response_code == 404:
144
+ log.error(f"{error.response_code} error while getting subgroup with name: {group.name} [id: {group.id}]. Check your permissions as you may not have access to it. Message: {error.error_message}")
145
+ continue
146
+ else:
147
+ raise error
148
+ except GitlabListError as error:
149
+ if error.response_code == 404:
150
+ log.error(f"{error.response_code} error while listing subgroup with name: {group.name} [id: {group.id}]. Check your permissions as you may not have access to it. Message: {error.error_message}")
151
+ else:
152
+ raise error
142
153
 
143
154
  def load_gitlab_tree(self):
144
155
  log.debug(f"Starting group search with archived: {self.archived} search term: {self.group_search}")
@@ -1,12 +1,11 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: gitlabber
3
- Version: 1.2.6
3
+ Version: 1.2.8
4
4
  Summary: A Gitlab clone/pull utility for backing up or cloning Gitlab groups
5
- Home-page: https://github.com/ezbz/gitlabber
6
- Download-URL: https://github.com/ezbz/gitlabber/archive/main.zip
7
- Author: Erez Mazor
8
- Author-email: erezmazor@gmail.com
5
+ Author-email: Erez Mazor <erezmazor@gmail.com>
9
6
  License: MIT
7
+ Project-URL: Homepage, https://github.com/ezbz/gitlabber
8
+ Project-URL: Download, https://github.com/ezbz/gitlabber/archive/main.zip
10
9
  Keywords: gitlab,python,cli
11
10
  Classifier: Development Status :: 4 - Beta
12
11
  Classifier: Intended Audience :: Developers
@@ -15,14 +14,27 @@ Classifier: License :: OSI Approved :: MIT License
15
14
  Classifier: Natural Language :: English
16
15
  Classifier: Operating System :: OS Independent
17
16
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
17
  Classifier: Programming Language :: Python :: 3.9
20
18
  Classifier: Programming Language :: Python :: 3.10
21
19
  Classifier: Programming Language :: Python :: 3.11
22
20
  Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
23
22
  Requires-Python: >=3
24
23
  Description-Content-Type: text/x-rst
25
24
  License-File: LICENSE.txt
25
+ Requires-Dist: docopt
26
+ Requires-Dist: anytree
27
+ Requires-Dist: globre
28
+ Requires-Dist: pyyaml
29
+ Requires-Dist: tqdm
30
+ Requires-Dist: GitPython
31
+ Requires-Dist: python-gitlab
32
+ Provides-Extra: test
33
+ Requires-Dist: coverage; extra == "test"
34
+ Requires-Dist: pytest; extra == "test"
35
+ Requires-Dist: pytest-cov; extra == "test"
36
+ Requires-Dist: pytest-integration; extra == "test"
37
+ Dynamic: license-file
26
38
 
27
39
  .. image:: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml/badge.svg?branch=master
28
40
  :target: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml
@@ -40,7 +52,7 @@ License-File: LICENSE.txt
40
52
  :target: https://pypi.python.org/pypi/gitlabber/
41
53
 
42
54
  .. image:: https://readthedocs.org/projects/gitlabber/badge/?version=latest&style=plastic
43
- :target: https://gitlabber.readthedocs.io/en/latest/README.html
55
+ :target: https://app.readthedocs.org/projects/gitlabber/
44
56
 
45
57
 
46
58
  Gitlabber
@@ -104,7 +116,7 @@ Usage
104
116
 
105
117
  * To see how to use glob patterns and regex to filter tree nodes, see the `globre project page <https://pypi.org/project/globre/#details>`_.
106
118
 
107
- * Include/Exclude patterns do not work at the API level but work on the results returned from the API, for large Gitlab installations this can take a lot of time, if you need to reduce the amound of API calls for such projects use the ``--group-search`` parameter to search only for the top level groups the interest you using the `Gitlab Group Search API <https://docs.gitlab.com/ee/api/groups.html#search-for-group>` which allows you to do a partial like query for a Group's path or name
119
+ * Include/Exclude patterns do not work at the API level but work on the results returned from the API, for large Gitlab installations this can take a lot of time, if you need to reduce the amound of API calls for such projects use the ``--group-search`` parameter to search only for the top level groups the interest you using the `Gitlab Group Search API <https://docs.gitlab.com/ee/api/groups.html#search-for-group>`_ which allows you to do a partial like query for a Group's path or name
108
120
 
109
121
  * Cloning vs Pulling: when running Gitlabber consecutively with the same parameters, it will scan the local tree structure; if the project directory exists and is a valid git repository (has .git folder in it) Gitlabber will perform a git pull in the directory, otherwise the project directory will be created and the GitLab project will be cloned into it.
110
122
 
@@ -145,7 +157,6 @@ Usage
145
157
  comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
146
158
  -r, --recursive clone/pull git submodules recursively
147
159
  -F, --use-fetch clone/fetch git repository (mirrored repositories)
148
- -d, --dont-checkout don't checkout pulled git repository
149
160
  -s, --include-shared include shared projects in the results
150
161
  -g term, --group-search term
151
162
  only include groups matching the search term, filtering done at the API level (useful for large projects, see: https://docs.gitlab.com/ee/api/groups.html#search-for-group works with partial names of path or name)
@@ -174,7 +185,7 @@ Usage
174
185
  clone projects that start with a case insensitive 'w' using a regular expression:
175
186
  gitlabber -i '/{[w].*}' .
176
187
 
177
- clone a user's personal projects to username-personal-projects
188
+ clone the personal projects to username-personal-projects
178
189
  gitlabber -U .
179
190
 
180
191
  perform a shallow clone of the git repositories
@@ -201,3 +212,8 @@ Known Limitations
201
212
  * Renaming, moving and deleting projects: Gitlabber doesn't maintain local tree state (projects and groups). For that reason is does not rename move or delete local projects when they are modified on the server. When projects are moved or renamed, Gitlabber will clone them again under their new name or location. When deleted, Gitlabber will not delete the local project.
202
213
  * Folder naming strategy: Consecutively running Gitlabber with different values for the ``-n`` parameter will produce undesirable results. Use the same value as previous runs, or simply don't change it from the default (project name).
203
214
  * If you're going to clone a large number of projects, observe rate limits `for gitlab.com <https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits/>`_, and `for on-premise installations <https://docs.gitlab.com/ee/security/rate_limits.html>`_.
215
+
216
+
217
+ Links
218
+ -----
219
+ * Gitlabber `tutorial <https://medium.com/@natskvi/clone-all-your-gitlab-groups-repos-before-starting-to-code-dd559ec5c8d6>`_ by nsk
@@ -1,6 +1,7 @@
1
1
  LICENSE.txt
2
2
  MANIFEST.in
3
3
  README.rst
4
+ pyproject.toml
4
5
  setup.cfg
5
6
  setup.py
6
7
  gitlabber/__init__.py
@@ -34,16 +35,4 @@ tests/test_git.py
34
35
  tests/test_gitlab_tree.py
35
36
  tests/test_integration.py
36
37
  tests/test_method.py
37
- tests/test_naming.py
38
- tests/__pycache__/__init__.cpython-311.pyc
39
- tests/__pycache__/gitlab_test_utils.cpython-311.pyc
40
- tests/__pycache__/io_test_util.cpython-311.pyc
41
- tests/__pycache__/test_archive.cpython-311-pytest-8.2.2.pyc
42
- tests/__pycache__/test_cli.cpython-311-pytest-8.2.2.pyc
43
- tests/__pycache__/test_e2e.cpython-311-pytest-8.2.2.pyc
44
- tests/__pycache__/test_format.cpython-311-pytest-8.2.2.pyc
45
- tests/__pycache__/test_git.cpython-311-pytest-8.2.2.pyc
46
- tests/__pycache__/test_gitlab_tree.cpython-311-pytest-8.2.2.pyc
47
- tests/__pycache__/test_integration.cpython-311-pytest-8.2.2.pyc
48
- tests/__pycache__/test_method.cpython-311-pytest-8.2.2.pyc
49
- tests/__pycache__/test_naming.cpython-311-pytest-8.2.2.pyc
38
+ tests/test_naming.py
@@ -5,3 +5,9 @@ pyyaml
5
5
  tqdm
6
6
  GitPython
7
7
  python-gitlab
8
+
9
+ [test]
10
+ coverage
11
+ pytest
12
+ pytest-cov
13
+ pytest-integration
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "gitlabber"
7
+ version = "1.2.8"
8
+ description = "A Gitlab clone/pull utility for backing up or cloning Gitlab groups"
9
+ readme = "README.rst"
10
+ requires-python = ">=3"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Erez Mazor", email = "erezmazor@gmail.com"},
14
+ ]
15
+ keywords = ["gitlab", "python", "cli"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Topic :: Utilities",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Natural Language :: English",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Programming Language :: Python :: 3.13",
29
+ ]
30
+ dependencies = [
31
+ "docopt",
32
+ "anytree",
33
+ "globre",
34
+ "pyyaml",
35
+ "tqdm",
36
+ "GitPython",
37
+ "python-gitlab",
38
+ ]
39
+
40
+ [project.optional-dependencies]
41
+ test = [
42
+ "coverage",
43
+ "pytest",
44
+ "pytest-cov",
45
+ "pytest-integration",
46
+ ]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/ezbz/gitlabber"
50
+ Download = "https://github.com/ezbz/gitlabber/archive/main.zip"
51
+
52
+ [project.scripts]
53
+ gitlabber = "gitlabber.cli:main"
54
+
55
+ [tool.setuptools]
56
+ packages = ["gitlabber"]
57
+ include-package-data = true
58
+
59
+ [tool.pytest.ini_options]
60
+ addopts = [
61
+ "-vv",
62
+ "--cov=gitlabber",
63
+ "--no-cov-on-fail",
64
+ "--cov-append",
65
+ "--cov-report=term-missing",
66
+ "--cov-report=xml",
67
+ "--verbose",
68
+ "--capture=sys",
69
+ "--without-slow-integration",
70
+ "--integration-cover"
71
+ ]
72
+ norecursedirs = [
73
+ "dist",
74
+ "build"
75
+ ]
76
+ testpaths = ["tests"]
77
+
78
+ [tool.coverage.run]
79
+ parallel = true
@@ -0,0 +1,10 @@
1
+ [bdist_wheel]
2
+ universal = 1
3
+
4
+ [metadata]
5
+ description_file = README.rst
6
+
7
+ [egg_info]
8
+ tag_build =
9
+ tag_date = 0
10
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+
3
+ setup()
@@ -23,7 +23,7 @@ TREE_TEST_OUTPUT_FILE = "tests/test-output.tree"
23
23
 
24
24
 
25
25
  class MockNode:
26
- def __init__(self, type, id, name, url, subgroups=mock.MagicMock(), projects=mock.MagicMock(), parent_id=None, archived=0, shared=False, group_search=None, git_options=None):
26
+ def __init__(self, type, id, name, url, subgroups=mock.MagicMock(), projects=mock.MagicMock(), shared_projects=mock.MagicMock(), parent_id=None, archived=0, shared=False, group_search=None, git_options=None):
27
27
  self.type = type
28
28
  self.id = id
29
29
  self.name = name
@@ -34,6 +34,7 @@ class MockNode:
34
34
  self.http_url_to_repo = url
35
35
  self.subgroups = subgroups
36
36
  self.projects = projects
37
+ self.shared_projects = shared_projects
37
38
  self.parent_id = parent_id
38
39
  self.archived = archived
39
40
  self.shared = shared
@@ -119,9 +120,17 @@ def validate_tree(root):
119
120
  validate_subgroup(root.children[0].children[0])
120
121
  validate_project(root.children[0].children[0].children[0])
121
122
 
122
- def create_test_gitlab(monkeypatch, includes=None, excludes=None, in_file=None, hide_token=True, method=CloneMethod.SSH):
123
+ def create_test_gitlab(monkeypatch, includes=None, excludes=None, in_file=None, hide_token=True, method=CloneMethod.SSH, token=TOKEN):
123
124
  gl = gitlab_tree.GitlabTree(
124
- URL, TOKEN, "ssh", "name", includes=includes, excludes=excludes, in_file=in_file, hide_token=hide_token)
125
+ URL,
126
+ token,
127
+ method, # Use the method parameter instead of hardcoding "ssh"
128
+ "name",
129
+ includes=includes,
130
+ excludes=excludes,
131
+ in_file=in_file,
132
+ hide_token=hide_token
133
+ )
125
134
  projects = Listable(MockNode("project", 2, PROJECT_NAME, PROJECT_URL if hide_token else PROJECT_URL_WITH_TOKEN))
126
135
  groups = Listable(
127
136
  MockNode("group", 2, GROUP_NAME, GROUP_URL, subgroups=Listable(
@@ -131,7 +140,6 @@ def create_test_gitlab(monkeypatch, includes=None, excludes=None, in_file=None,
131
140
  monkeypatch.setattr(gl.gitlab, "groups", Tree(groups))
132
141
  return gl
133
142
 
134
-
135
143
  def create_test_gitlab_with_toplevel_subgroups(monkeypatch):
136
144
  gl = gitlab_tree.GitlabTree(URL, TOKEN, "ssh", "path")
137
145
  groups = Listable(
@@ -167,9 +175,12 @@ def create_test_gitlab_with_shared(monkeypatch, includes=None, excludes=None, in
167
175
 
168
176
  projects = Listable(
169
177
  MockNode("project", 19, PROJECT_NAME, PROJECT_URL),
178
+ )
179
+
180
+ shared_projects = Listable(
170
181
  MockNode("project", 20, "_shared_" + PROJECT_NAME, "_shared_" + PROJECT_URL, shared=True)
171
182
  )
172
183
  monkeypatch.setattr(gl.gitlab, "groups", Tree(Listable(
173
- MockNode("group", 21, GROUP_NAME, GROUP_URL, projects=projects)
184
+ MockNode("group", 21, GROUP_NAME, GROUP_URL, projects=projects, shared_projects=shared_projects)
174
185
  )))
175
186
  return gl
@@ -11,7 +11,7 @@ coverage.process_startup()
11
11
  @pytest.mark.slow_integration_test
12
12
  def test_clone_subgroup():
13
13
  os.environ['GITLAB_URL'] = 'https://gitlab.com/'
14
- output = io_util.execute(['-p', '--print-format', 'json', '--group-search', 'Group Test'], 60)
14
+ output = io_util.execute(['-p', '--print-format', 'json', '--group-search', 'Group Test'], 90)
15
15
  obj = json.loads(output)
16
16
  assert obj['children'][0]['name'] == 'Group Test'
17
17
  assert obj['children'][0]['children'][0]['name'] == 'Subgroup Test'
@@ -23,7 +23,7 @@ def test_clone_subgroup():
23
23
  @pytest.mark.slow_integration_test
24
24
  def test_clone_subgroup_exclude_archived():
25
25
  os.environ['GITLAB_URL'] = 'https://gitlab.com/'
26
- output = io_util.execute(['-p', '--print-format', 'json', '--group-search', 'Group Test', '-a', 'exclude'], 60)
26
+ output = io_util.execute(['-p', '--print-format', 'json', '--group-search', 'Group Test', '-a', 'exclude'], 90)
27
27
  obj = json.loads(output)
28
28
  assert obj['children'][0]['name'] == 'Group Test'
29
29
  assert obj['children'][0]['children'][0]['name'] == 'Subgroup Test'
@@ -34,7 +34,7 @@ def test_clone_subgroup_exclude_archived():
34
34
  @pytest.mark.slow_integration_test
35
35
  def test_clone_subgroup_only_archived():
36
36
  os.environ['GITLAB_URL'] = 'https://gitlab.com/'
37
- output = io_util.execute(['-p', '--print-format', 'json', '--group-search', 'Group Test', '-a', 'only'], 60)
37
+ output = io_util.execute(['-p', '--print-format', 'json', '--group-search', 'Group Test', '-a', 'only'], 90)
38
38
  obj = json.loads(output)
39
39
  assert obj['children'][0]['name'] == 'Group Test'
40
40
  assert obj['children'][0]['children'][0]['name'] == 'Subgroup Test'
@@ -45,7 +45,7 @@ def test_clone_subgroup_only_archived():
45
45
  @pytest.mark.slow_integration_test
46
46
  def test_clone_subgroup_naming_path():
47
47
  os.environ['GITLAB_URL'] = 'https://gitlab.com/'
48
- output = io_util.execute(['-p', '--print-format', 'json', '-n', 'path', '--group-search', 'Group Test'], 60)
48
+ output = io_util.execute(['-p', '--print-format', 'json', '-n', 'path', '--group-search', 'Group Test'], 90)
49
49
  obj = json.loads(output)
50
50
  assert obj['children'][0]['name'] == 'erez-group-test'
51
51
  assert obj['children'][0]['children'][0]['name'] == 'subgroup-test'
@@ -58,7 +58,7 @@ def test_clone_subgroup_naming_path():
58
58
  @pytest.mark.slow_integration_test
59
59
  def test_large_groups():
60
60
  os.environ['GITLAB_URL'] = 'https://gitlab.com/'
61
- output = io_util.execute(['-p', '--print-format', 'json', '-n', 'path', '--group-search', 'large-group-test'], 60)
61
+ output = io_util.execute(['-p', '--print-format', 'json', '-n', 'path', '--group-search', 'large-group-test'], 90)
62
62
  obj = json.loads(output)
63
63
  assert obj['children'][0]['name'] == 'large-group-test'
64
64
  assert obj['children'][0]['children'][0]['name'] == 'many-subgroups'
@@ -70,8 +70,18 @@ def test_large_groups():
70
70
  @pytest.mark.slow_integration_test
71
71
  def test_user_personal_projects():
72
72
  os.environ['GITLAB_URL'] = 'https://gitlab.com/'
73
- output = io_util.execute(['-p', '--print-format', 'json', '-n', 'path', '--user-projects'], 60)
73
+ output = io_util.execute(['-p', '--print-format', 'json', '-n', 'path', '--user-projects'], 90)
74
74
  obj = json.loads(output)
75
75
  assert obj['children'][0]['name'] == 'erezmazor-prsonal-projects'
76
76
  assert obj['children'][0]['children'][0]['name'] == 'gitlabber-personal-project'
77
+
78
+
79
+ @pytest.mark.slow_integration_test
80
+ def test_shared_group_and_project():
81
+ os.environ['GITLAB_URL'] = 'https://gitlab.com/'
82
+ output = io_util.execute(['-p', '--print-format', 'json', '-s', '--group-search', 'shared-group3'], 90)
83
+ obj = json.loads(output)
84
+ assert obj['children'][0]['name'] == 'Shared Group'
85
+ assert obj['children'][0]['children'][0]['name'] == 'Shared Project'
86
+
77
87
 
@@ -1,8 +1,10 @@
1
-
2
1
  from gitlabber.method import CloneMethod
3
2
  import tests.gitlab_test_utils as gitlab_util
4
3
  import tests.io_test_util as output_util
5
4
  from gitlabber.archive import ArchivedResults
5
+ import pytest
6
+ from unittest import mock
7
+ from gitlab.exceptions import GitlabGetError
6
8
 
7
9
  def test_load_tree(monkeypatch):
8
10
  gl = gitlab_util.create_test_gitlab(monkeypatch)
@@ -173,3 +175,73 @@ def test_hide_token_from_project_url(monkeypatch):
173
175
  gl.load_tree()
174
176
  gl.print_tree()
175
177
  assert 'gitlab-token:xxx@' not in gl.root.children[0].children[0].children[0].url
178
+
179
+
180
+ def test_get_subgroups_404_error(monkeypatch):
181
+ gl = gitlab_util.create_test_gitlab(monkeypatch)
182
+
183
+ # Create mock group and subgroup
184
+ mock_group = mock.Mock()
185
+ mock_group.name = "mock_group"
186
+ mock_group.id = 123
187
+
188
+ # Create a mock subgroup definition that will be returned by list()
189
+ mock_subgroup_def = mock.Mock()
190
+ mock_subgroup_def.id = 456
191
+
192
+ # Make subgroups.list return our mock subgroup definition
193
+ mock_group.subgroups.list.return_value = [mock_subgroup_def]
194
+
195
+ # This is the function that will raise the 404 when trying to get the subgroup
196
+ def mock_get_subgroup(id):
197
+ raise GitlabGetError(response_code=404, error_message="Not Found")
198
+
199
+ # Patch the groups.get method
200
+ monkeypatch.setattr(gl.gitlab.groups, "get", mock_get_subgroup)
201
+
202
+ with mock.patch("gitlabber.gitlab_tree.log.error") as mock_log_error:
203
+ gl.get_subgroups(mock_group, gl.root)
204
+
205
+ mock_log_error.assert_called_once_with(
206
+ f"404 error while getting subgroup with name: {mock_group.name} [id: {mock_group.id}]. Check your permissions as you may not have access to it. Message: Not Found"
207
+ )
208
+
209
+ def test_hide_token_in_project_url_both_cases(monkeypatch):
210
+ test_token = "test-token-123"
211
+
212
+ # Create two instances to test both cases
213
+ gl_hidden = gitlab_util.create_test_gitlab(monkeypatch,
214
+ hide_token=True,
215
+ method=CloneMethod.HTTP,
216
+ token=test_token)
217
+
218
+ gl_visible = gitlab_util.create_test_gitlab(monkeypatch,
219
+ hide_token=False,
220
+ method=CloneMethod.HTTP,
221
+ token=test_token)
222
+
223
+ # Create mock project
224
+ mock_project = mock.Mock()
225
+ mock_project.name = "test-project"
226
+ mock_project.path = "test-project"
227
+ mock_project.ssh_url_to_repo = "git@gitlab.com:group/test-project.git"
228
+ mock_project.http_url_to_repo = "https://gitlab.com/group/test-project.git"
229
+
230
+ # Test with hide_token=True
231
+ with mock.patch("gitlabber.gitlab_tree.log.debug") as mock_log_debug_hidden:
232
+ gl_hidden.add_projects(gl_hidden.root, [mock_project])
233
+ project_node_hidden = gl_hidden.root.children[0]
234
+
235
+ # Verify token is not in URL
236
+ assert project_node_hidden.url == "https://gitlab.com/group/test-project.git"
237
+ mock_log_debug_hidden.assert_any_call("Hiding token from project url: %s",
238
+ project_node_hidden.url)
239
+
240
+ # Test with hide_token=False
241
+ with mock.patch("gitlabber.gitlab_tree.log.debug") as mock_log_debug_visible:
242
+ gl_visible.add_projects(gl_visible.root, [mock_project])
243
+ project_node_visible = gl_visible.root.children[0]
244
+
245
+ expected_url = f"https://gitlab-token:{test_token}@gitlab.com/group/test-project.git"
246
+ assert project_node_visible.url == expected_url
247
+ mock_log_debug_visible.assert_any_call("Generated URL: %s", expected_url)
@@ -1 +0,0 @@
1
- __version__ = '1.2.6'
gitlabber-1.2.6/setup.cfg DELETED
@@ -1,24 +0,0 @@
1
- [bdist_wheel]
2
- universal = 1
3
-
4
- [metadata]
5
- description-file = README.rst
6
-
7
- [coverage:run]
8
- parallel = true
9
-
10
- [tool:pytest]
11
- addopts =
12
- -vv --cov=gitlabber --no-cov-on-fail --cov-append --cov-report term-missing --cov-report xml --verbose --capture=sys --without-slow-integration --integration-cover
13
- norecursedirs =
14
- dist
15
- build
16
-
17
- [aliases]
18
- dists = sdist bdist_wheel
19
- testpaths = tests
20
-
21
- [egg_info]
22
- tag_build =
23
- tag_date = 0
24
-
gitlabber-1.2.6/setup.py DELETED
@@ -1,89 +0,0 @@
1
- """Packaging settings."""
2
-
3
-
4
- from codecs import open
5
- import os
6
- from os.path import abspath, dirname, join
7
- from subprocess import call
8
-
9
- from setuptools import Command, find_packages, setup
10
-
11
- from gitlabber import __version__
12
-
13
-
14
- this_dir = abspath(dirname(__file__))
15
- with open(join(this_dir, 'README.rst'), encoding='utf-8') as file:
16
- long_description = file.read()
17
-
18
-
19
- class RunTests(Command):
20
- """Run all tests."""
21
- description = 'run tests'
22
- user_options = []
23
-
24
- def initialize_options(self):
25
- pass
26
-
27
- def finalize_options(self):
28
- pass
29
-
30
- def run(self):
31
- """Run all tests!"""
32
- errno = call(['py.test'])
33
- raise SystemExit(errno)
34
- setup(
35
- python_requires='>=3',
36
- name = 'gitlabber',
37
- packages = ['gitlabber'],
38
- version = __version__,
39
- description = 'A Gitlab clone/pull utility for backing up or cloning Gitlab groups',
40
- long_description = long_description,
41
- long_description_content_type="text/x-rst",
42
- url = 'https://github.com/ezbz/gitlabber',
43
- download_url = 'https://github.com/ezbz/gitlabber/archive/main.zip',
44
- author = 'Erez Mazor',
45
- author_email = 'erezmazor@gmail.com',
46
- license = 'MIT',
47
- keywords = ['gitlab', 'python', 'cli'],
48
- include_package_data = True,
49
-
50
- classifiers = [
51
- 'Development Status :: 4 - Beta',
52
- 'Intended Audience :: Developers',
53
- 'Topic :: Utilities',
54
- 'License :: OSI Approved :: MIT License',
55
- 'Natural Language :: English',
56
- 'Operating System :: OS Independent',
57
- 'Programming Language :: Python :: 3',
58
- 'Programming Language :: Python :: 3.8',
59
- 'Programming Language :: Python :: 3.9',
60
- 'Programming Language :: Python :: 3.10',
61
- 'Programming Language :: Python :: 3.11',
62
- 'Programming Language :: Python :: 3.12'
63
- ],
64
- setup_requires = [
65
- 'docopt',
66
- 'anytree',
67
- 'globre',
68
- 'pyyaml',
69
- 'tqdm',
70
- 'GitPython',
71
- 'python-gitlab'
72
- ],
73
- install_requires = [
74
- 'docopt',
75
- 'anytree',
76
- 'globre',
77
- 'pyyaml',
78
- 'tqdm',
79
- 'GitPython',
80
- 'python-gitlab'
81
- ],
82
- tests_require= ['coverage', 'pytest', 'pytest-cov', 'pytest-integration'],
83
- entry_points = {
84
- 'console_scripts': [
85
- 'gitlabber=gitlabber.cli:main',
86
- ],
87
- },
88
- cmdclass = {'test': RunTests},
89
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes