gitlabber 1.2.3__tar.gz → 1.2.5__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.3/gitlabber.egg-info → gitlabber-1.2.5}/PKG-INFO +48 -48
  2. {gitlabber-1.2.3 → gitlabber-1.2.5}/README.rst +46 -39
  3. gitlabber-1.2.5/gitlabber/__init__.py +1 -0
  4. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/cli.py +17 -7
  5. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/git.py +6 -1
  6. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/gitlab_tree.py +29 -12
  7. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/progress.py +6 -7
  8. {gitlabber-1.2.3 → gitlabber-1.2.5/gitlabber.egg-info}/PKG-INFO +48 -48
  9. {gitlabber-1.2.3 → gitlabber-1.2.5}/setup.py +1 -1
  10. gitlabber-1.2.5/tests/__pycache__/gitlab_test_utils.cpython-311.pyc +0 -0
  11. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_archive.cpython-311-pytest-8.2.2.pyc +0 -0
  12. gitlabber-1.2.5/tests/__pycache__/test_cli.cpython-311-pytest-8.2.2.pyc +0 -0
  13. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_e2e.cpython-311-pytest-8.2.2.pyc +0 -0
  14. gitlabber-1.2.5/tests/__pycache__/test_git.cpython-311-pytest-8.2.2.pyc +0 -0
  15. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_gitlab_tree.cpython-311-pytest-8.2.2.pyc +0 -0
  16. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/gitlab_test_utils.py +23 -24
  17. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test-output.json +4 -0
  18. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test-output.yaml +4 -0
  19. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_cli.py +8 -8
  20. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_e2e.py +31 -20
  21. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_git.py +11 -11
  22. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_gitlab_tree.py +2 -2
  23. gitlabber-1.2.3/gitlabber/__init__.py +0 -1
  24. gitlabber-1.2.3/tests/__pycache__/gitlab_test_utils.cpython-311.pyc +0 -0
  25. gitlabber-1.2.3/tests/__pycache__/test_cli.cpython-311-pytest-8.2.2.pyc +0 -0
  26. gitlabber-1.2.3/tests/__pycache__/test_git.cpython-311-pytest-8.2.2.pyc +0 -0
  27. {gitlabber-1.2.3 → gitlabber-1.2.5}/LICENSE.txt +0 -0
  28. {gitlabber-1.2.3 → gitlabber-1.2.5}/MANIFEST.in +0 -0
  29. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/__main__.py +0 -0
  30. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/archive.py +0 -0
  31. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/format.py +0 -0
  32. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/method.py +0 -0
  33. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber/naming.py +0 -0
  34. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber.egg-info/SOURCES.txt +0 -0
  35. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber.egg-info/dependency_links.txt +0 -0
  36. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber.egg-info/entry_points.txt +0 -0
  37. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber.egg-info/requires.txt +0 -0
  38. {gitlabber-1.2.3 → gitlabber-1.2.5}/gitlabber.egg-info/top_level.txt +0 -0
  39. {gitlabber-1.2.3 → gitlabber-1.2.5}/setup.cfg +0 -0
  40. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__init__.py +0 -0
  41. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  42. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/io_test_util.cpython-311.pyc +0 -0
  43. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_format.cpython-311-pytest-8.2.2.pyc +0 -0
  44. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_integration.cpython-311-pytest-8.2.2.pyc +0 -0
  45. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_method.cpython-311-pytest-8.2.2.pyc +0 -0
  46. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/__pycache__/test_naming.cpython-311-pytest-8.2.2.pyc +0 -0
  47. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/io_test_util.py +0 -0
  48. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test-input.yaml +0 -0
  49. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test-output.tree +0 -0
  50. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_archive.py +0 -0
  51. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_format.py +0 -0
  52. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_integration.py +0 -0
  53. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_method.py +0 -0
  54. {gitlabber-1.2.3 → gitlabber-1.2.5}/tests/test_naming.py +0 -0
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: gitlabber
3
- Version: 1.2.3
3
+ Version: 1.2.5
4
4
  Summary: A Gitlab clone/pull utility for backing up or cloning Gitlab groups
5
5
  Home-page: https://github.com/ezbz/gitlabber
6
- Download-URL: https://github.com/ezbz/gitlabber/archive/master.zip
6
+ Download-URL: https://github.com/ezbz/gitlabber/archive/main.zip
7
7
  Author: Erez Mazor
8
8
  Author-email: erezmazor@gmail.com
9
9
  License: MIT
@@ -23,18 +23,11 @@ Classifier: Programming Language :: Python :: 3.12
23
23
  Requires-Python: >=3
24
24
  Description-Content-Type: text/x-rst
25
25
  License-File: LICENSE.txt
26
- Requires-Dist: docopt
27
- Requires-Dist: anytree
28
- Requires-Dist: globre
29
- Requires-Dist: pyyaml
30
- Requires-Dist: tqdm
31
- Requires-Dist: GitPython
32
- Requires-Dist: python-gitlab
33
26
 
34
27
  .. image:: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml/badge.svg?branch=master
35
28
  :target: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml
36
29
 
37
- .. image:: https://codecov.io/gh/ezbz/gitlabber/branch/master/graph/badge.svg
30
+ .. image:: https://codecov.io/gh/ezbz/gitlabber/branch/main/graph/badge.svg
38
31
  :target: https://codecov.io/gh/ezbz/gitlabber
39
32
 
40
33
  .. image:: https://badge.fury.io/py/gitlabber.svg
@@ -62,7 +55,6 @@ Purpose
62
55
  Gitlabber clones or pulls all projects under a subset of groups / subgroups by building a tree from the GitLab API and allowing you to specify which subset of the tree you want to clone using glob patterns and/or regex expressions.
63
56
 
64
57
 
65
-
66
58
  Installation
67
59
  ------------
68
60
 
@@ -73,12 +65,13 @@ Installation
73
65
  pip install gitlabber
74
66
 
75
67
  * You'll need to create an `access token <https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html>`_ from GitLab with API scopes `read_repository`
76
- and `read_api` (or `api`, for GitLab versions <12.0)
68
+ and ``read_api`` (or ``api``, for GitLab versions <12.0)
77
69
 
78
70
  Usage
79
71
  -----
80
72
 
81
- * Arguments can be provided via the CLI arguments directly or via environment variables::
73
+ * Arguments can be provided via the CLI arguments directly or via environment variables:
74
+
82
75
  +---------------+---------------+---------------------------+
83
76
  | Argument | Flag | Environment Variable |
84
77
  +===============+===============+===========================+
@@ -95,7 +88,7 @@ Usage
95
88
  | exclude | -x | `GITLABBER_EXCLUDE` |
96
89
  +---------------+---------------+---------------------------+
97
90
 
98
- * To view the tree run the command with your includes/excludes and the `-p` flag. It will print your tree like so:
91
+ * To view the tree run the command with your includes/excludes and the ``-p`` flag. It will print your tree like so:
99
92
 
100
93
  .. code-block:: bash
101
94
 
@@ -111,72 +104,78 @@ Usage
111
104
 
112
105
  * To see how to use glob patterns and regex to filter tree nodes, see the `globre project page <https://pypi.org/project/globre/#details>`_.
113
106
 
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
108
+
114
109
  * 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.
115
110
 
116
- * Cloning submodules: use the `-r` flag to recurse git submodules, uses the `--recursive` for cloning and utilizes `GitPython's smart update method <https://github.com/gitpython-developers/GitPython/blob/20f4a9d49b466a18f1af1fdfb480bc4520a4cdc2/git/objects/submodule/root.py#L67>`_ for updating cloned repositories
111
+ * Cloning submodules: use the ``-r`` flag to recurse git submodules, uses the ``--recursive`` for cloning and utilizes `GitPython's smart update method <https://github.com/gitpython-developers/GitPython/blob/20f4a9d49b466a18f1af1fdfb480bc4520a4cdc2/git/objects/submodule/root.py#L67>`_ for updating cloned repositories
117
112
 
118
113
  * Printed Usage:
119
114
 
120
115
  .. code-block:: bash
121
116
 
122
- usage: gitlabber [-h] [-t token] [-T] [-u url] [--verbose] [-p] [--print-format {json,yaml,tree}] [-n {name,path}] [-m {ssh,http}] [-a {include,exclude,only}] [-i csv] [-x csv] [-r] [-F] [-d] [-s] [--version]
123
- [dest]
117
+ usage: gitlabber [-h] [-t token] [-T] [-u url] [--verbose] [-p] [--print-format {json,yaml,tree}] [-n {name,path}] [-m {ssh,http}]
118
+ [-a {include,exclude,only}] [-i csv] [-x csv] [-r] [-F] [-d] [-s] [-g term] [--version]
119
+ [dest]
124
120
 
125
121
  Gitlabber - clones or pulls entire groups/projects tree from gitlab
126
-
122
+
127
123
  positional arguments:
128
- dest destination path for the cloned tree (created if doesn't exist)
129
-
124
+ dest destination path for the cloned tree (created if doesn't exist)
125
+
130
126
  options:
131
- -h, --help show this help message and exit
132
- -t token, --token token
127
+ -h, --help show this help message and exit
128
+ -t token, --token token
133
129
  gitlab personal access token https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
134
- -T, --hide-token use an inline URL token (avoids storing the gitlab personal access token in the .git/config)
135
- -u url, --url url base gitlab url (e.g.: 'http://gitlab.mycompany.com')
136
- --verbose print more verbose output
137
- -p, --print print the tree without cloning
138
- --print-format {json,yaml,tree}
130
+ -T, --hide-token use an inline URL token (avoids storing the gitlab personal access token in the .git/config)
131
+ -u url, --url url base gitlab url (e.g.: 'http://gitlab.mycompany.com')
132
+ --verbose print more verbose output
133
+ -p, --print print the tree without cloning
134
+ --print-format {json,yaml,tree}
139
135
  print format (default: 'tree')
140
- -n {name,path}, --naming {name,path}
136
+ -n {name,path}, --naming {name,path}
141
137
  the folder naming strategy for projects from the gitlab API attributes (default: "name")
142
- -m {ssh,http}, --method {ssh,http}
138
+ -m {ssh,http}, --method {ssh,http}
143
139
  the git transport method to use for cloning (default: "ssh")
144
- -a {include,exclude,only}, --archived {include,exclude,only}
140
+ -a {include,exclude,only}, --archived {include,exclude,only}
145
141
  include archived projects and groups in the results (default: "include")
146
- -i csv, --include csv
142
+ -i csv, --include csv
147
143
  comma delimited list of glob patterns of paths to projects or groups to clone/pull
148
- -x csv, --exclude csv
144
+ -x csv, --exclude csv
149
145
  comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
150
- -r, --recursive clone/pull git submodules recursively
151
- -F, --use-fetch clone/fetch git repository (mirrored repositories)
152
- -d, --dont-checkout don't checkout pulled git repository
153
- -s, --include-shared include shared projects in the results
154
- --version print the version
155
-
146
+ -r, --recursive clone/pull git submodules recursively
147
+ -F, --use-fetch clone/fetch git repository (mirrored repositories)
148
+ -d, --dont-checkout don't checkout pulled git repository
149
+ -s, --include-shared include shared projects in the results
150
+ -g term, --group-search term
151
+ 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)
152
+ --version print the version
153
+
156
154
  examples:
157
-
155
+
158
156
  clone an entire gitlab tree using a url and a token:
159
157
  gitlabber -t <personal access token> -u <gitlab url>
160
-
158
+
161
159
  only print the gitlab tree:
162
160
  gitlabber -p .
163
-
161
+
164
162
  clone only projects under subgroup 'MySubGroup' to location '~/GitlabRoot':
165
163
  gitlabber -i '/MyGroup/MySubGroup**' ~/GitlabRoot
166
-
164
+
167
165
  clone only projects under group 'MyGroup' excluding any projects under subgroup 'MySubGroup':
168
166
  gitlabber -i '/MyGroup**' -x '/MyGroup/MySubGroup**' .
169
-
167
+
170
168
  clone an entire gitlab tree except projects under groups named 'ArchiveGroup':
171
169
  gitlabber -x '/ArchiveGroup**' .
172
-
170
+
173
171
  clone projects that start with a case insensitive 'w' using a regular expression:
174
172
  gitlabber -i '/{[w].*}' .
175
173
 
176
174
 
175
+
177
176
  Debugging
178
177
  ---------
179
- * You can use the `--verbose` flag to print Gitlabber debug messages
178
+ * You can use the ``--verbose`` flag to print Gitlabber debug messages
180
179
  * For more verbose GitLab messages, you can get the `GitPython <https://gitpython.readthedocs.io/en/stable>`_ module to print more debug messages by setting the environment variable:
181
180
 
182
181
  .. code-block:: bash
@@ -185,10 +184,11 @@ Debugging
185
184
 
186
185
  Troubleshooting
187
186
  ---------------
188
- * `GitlabHttpError: 503`: make sure you provide the base URL to your GitLab installation (e.g., `https://gitlab.my.com` and not `https://gitlab.my.com/some/nested/path`)
189
-
187
+ * ``GitlabHttpError: 503``: make sure you provide the base URL to your GitLab installation (e.g., `https://gitlab.my.com` and not `https://gitlab.my.com/some/nested/path`)
188
+ * ``git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)`` OR ``ERROR: The project you were looking for could not be found or you don't have permission to view it.``: if you are using Git's SSH method, follow the `SSH Guide <https://docs.gitlab.com/ee/user/ssh.html>`_ from Gitlab and ensure you have your SSH key in Gitlab for clone or use the HTTP method (``-m http`` flag)
189
+
190
190
  Known Limitations
191
191
  -----------------
192
192
  * 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.
193
- * 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).
193
+ * 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).
194
194
  * 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>`_.
@@ -1,7 +1,7 @@
1
1
  .. image:: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml/badge.svg?branch=master
2
2
  :target: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml
3
3
 
4
- .. image:: https://codecov.io/gh/ezbz/gitlabber/branch/master/graph/badge.svg
4
+ .. image:: https://codecov.io/gh/ezbz/gitlabber/branch/main/graph/badge.svg
5
5
  :target: https://codecov.io/gh/ezbz/gitlabber
6
6
 
7
7
  .. image:: https://badge.fury.io/py/gitlabber.svg
@@ -29,7 +29,6 @@ Purpose
29
29
  Gitlabber clones or pulls all projects under a subset of groups / subgroups by building a tree from the GitLab API and allowing you to specify which subset of the tree you want to clone using glob patterns and/or regex expressions.
30
30
 
31
31
 
32
-
33
32
  Installation
34
33
  ------------
35
34
 
@@ -40,12 +39,13 @@ Installation
40
39
  pip install gitlabber
41
40
 
42
41
  * You'll need to create an `access token <https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html>`_ from GitLab with API scopes `read_repository`
43
- and `read_api` (or `api`, for GitLab versions <12.0)
42
+ and ``read_api`` (or ``api``, for GitLab versions <12.0)
44
43
 
45
44
  Usage
46
45
  -----
47
46
 
48
- * Arguments can be provided via the CLI arguments directly or via environment variables::
47
+ * Arguments can be provided via the CLI arguments directly or via environment variables:
48
+
49
49
  +---------------+---------------+---------------------------+
50
50
  | Argument | Flag | Environment Variable |
51
51
  +===============+===============+===========================+
@@ -62,7 +62,7 @@ Usage
62
62
  | exclude | -x | `GITLABBER_EXCLUDE` |
63
63
  +---------------+---------------+---------------------------+
64
64
 
65
- * To view the tree run the command with your includes/excludes and the `-p` flag. It will print your tree like so:
65
+ * To view the tree run the command with your includes/excludes and the ``-p`` flag. It will print your tree like so:
66
66
 
67
67
  .. code-block:: bash
68
68
 
@@ -78,72 +78,78 @@ 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
82
+
81
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.
82
84
 
83
- * Cloning submodules: use the `-r` flag to recurse git submodules, uses the `--recursive` for cloning and utilizes `GitPython's smart update method <https://github.com/gitpython-developers/GitPython/blob/20f4a9d49b466a18f1af1fdfb480bc4520a4cdc2/git/objects/submodule/root.py#L67>`_ for updating cloned repositories
85
+ * Cloning submodules: use the ``-r`` flag to recurse git submodules, uses the ``--recursive`` for cloning and utilizes `GitPython's smart update method <https://github.com/gitpython-developers/GitPython/blob/20f4a9d49b466a18f1af1fdfb480bc4520a4cdc2/git/objects/submodule/root.py#L67>`_ for updating cloned repositories
84
86
 
85
87
  * Printed Usage:
86
88
 
87
89
  .. code-block:: bash
88
90
 
89
- usage: gitlabber [-h] [-t token] [-T] [-u url] [--verbose] [-p] [--print-format {json,yaml,tree}] [-n {name,path}] [-m {ssh,http}] [-a {include,exclude,only}] [-i csv] [-x csv] [-r] [-F] [-d] [-s] [--version]
90
- [dest]
91
+ usage: gitlabber [-h] [-t token] [-T] [-u url] [--verbose] [-p] [--print-format {json,yaml,tree}] [-n {name,path}] [-m {ssh,http}]
92
+ [-a {include,exclude,only}] [-i csv] [-x csv] [-r] [-F] [-d] [-s] [-g term] [--version]
93
+ [dest]
91
94
 
92
95
  Gitlabber - clones or pulls entire groups/projects tree from gitlab
93
-
96
+
94
97
  positional arguments:
95
- dest destination path for the cloned tree (created if doesn't exist)
96
-
98
+ dest destination path for the cloned tree (created if doesn't exist)
99
+
97
100
  options:
98
- -h, --help show this help message and exit
99
- -t token, --token token
101
+ -h, --help show this help message and exit
102
+ -t token, --token token
100
103
  gitlab personal access token https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
101
- -T, --hide-token use an inline URL token (avoids storing the gitlab personal access token in the .git/config)
102
- -u url, --url url base gitlab url (e.g.: 'http://gitlab.mycompany.com')
103
- --verbose print more verbose output
104
- -p, --print print the tree without cloning
105
- --print-format {json,yaml,tree}
104
+ -T, --hide-token use an inline URL token (avoids storing the gitlab personal access token in the .git/config)
105
+ -u url, --url url base gitlab url (e.g.: 'http://gitlab.mycompany.com')
106
+ --verbose print more verbose output
107
+ -p, --print print the tree without cloning
108
+ --print-format {json,yaml,tree}
106
109
  print format (default: 'tree')
107
- -n {name,path}, --naming {name,path}
110
+ -n {name,path}, --naming {name,path}
108
111
  the folder naming strategy for projects from the gitlab API attributes (default: "name")
109
- -m {ssh,http}, --method {ssh,http}
112
+ -m {ssh,http}, --method {ssh,http}
110
113
  the git transport method to use for cloning (default: "ssh")
111
- -a {include,exclude,only}, --archived {include,exclude,only}
114
+ -a {include,exclude,only}, --archived {include,exclude,only}
112
115
  include archived projects and groups in the results (default: "include")
113
- -i csv, --include csv
116
+ -i csv, --include csv
114
117
  comma delimited list of glob patterns of paths to projects or groups to clone/pull
115
- -x csv, --exclude csv
118
+ -x csv, --exclude csv
116
119
  comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
117
- -r, --recursive clone/pull git submodules recursively
118
- -F, --use-fetch clone/fetch git repository (mirrored repositories)
119
- -d, --dont-checkout don't checkout pulled git repository
120
- -s, --include-shared include shared projects in the results
121
- --version print the version
122
-
120
+ -r, --recursive clone/pull git submodules recursively
121
+ -F, --use-fetch clone/fetch git repository (mirrored repositories)
122
+ -d, --dont-checkout don't checkout pulled git repository
123
+ -s, --include-shared include shared projects in the results
124
+ -g term, --group-search term
125
+ 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)
126
+ --version print the version
127
+
123
128
  examples:
124
-
129
+
125
130
  clone an entire gitlab tree using a url and a token:
126
131
  gitlabber -t <personal access token> -u <gitlab url>
127
-
132
+
128
133
  only print the gitlab tree:
129
134
  gitlabber -p .
130
-
135
+
131
136
  clone only projects under subgroup 'MySubGroup' to location '~/GitlabRoot':
132
137
  gitlabber -i '/MyGroup/MySubGroup**' ~/GitlabRoot
133
-
138
+
134
139
  clone only projects under group 'MyGroup' excluding any projects under subgroup 'MySubGroup':
135
140
  gitlabber -i '/MyGroup**' -x '/MyGroup/MySubGroup**' .
136
-
141
+
137
142
  clone an entire gitlab tree except projects under groups named 'ArchiveGroup':
138
143
  gitlabber -x '/ArchiveGroup**' .
139
-
144
+
140
145
  clone projects that start with a case insensitive 'w' using a regular expression:
141
146
  gitlabber -i '/{[w].*}' .
142
147
 
143
148
 
149
+
144
150
  Debugging
145
151
  ---------
146
- * You can use the `--verbose` flag to print Gitlabber debug messages
152
+ * You can use the ``--verbose`` flag to print Gitlabber debug messages
147
153
  * For more verbose GitLab messages, you can get the `GitPython <https://gitpython.readthedocs.io/en/stable>`_ module to print more debug messages by setting the environment variable:
148
154
 
149
155
  .. code-block:: bash
@@ -152,10 +158,11 @@ Debugging
152
158
 
153
159
  Troubleshooting
154
160
  ---------------
155
- * `GitlabHttpError: 503`: make sure you provide the base URL to your GitLab installation (e.g., `https://gitlab.my.com` and not `https://gitlab.my.com/some/nested/path`)
156
-
161
+ * ``GitlabHttpError: 503``: make sure you provide the base URL to your GitLab installation (e.g., `https://gitlab.my.com` and not `https://gitlab.my.com/some/nested/path`)
162
+ * ``git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)`` OR ``ERROR: The project you were looking for could not be found or you don't have permission to view it.``: if you are using Git's SSH method, follow the `SSH Guide <https://docs.gitlab.com/ee/user/ssh.html>`_ from Gitlab and ensure you have your SSH key in Gitlab for clone or use the HTTP method (``-m http`` flag)
163
+
157
164
  Known Limitations
158
165
  -----------------
159
166
  * 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.
160
- * 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).
167
+ * 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).
161
168
  * 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>`_.
@@ -0,0 +1 @@
1
+ __version__ = '1.2.5'
@@ -37,10 +37,13 @@ def main():
37
37
  includes=split(args.include)
38
38
  excludes=split(args.exclude)
39
39
 
40
+ args_print = vars(args).copy()
41
+ args_print['token'] = '__hidden__'
42
+ log.debug("running with args [%s]", args_print)
43
+
40
44
  tree = GitlabTree(args.url, args.token, args.method, args.naming, args.archived.api_value, includes,
41
45
  excludes, args.file, args.concurrency, args.recursive, args.verbose,
42
- args.use_fetch, args.hide_token)
43
- log.debug("Reading projects tree from gitlab at [%s]", args.url)
46
+ args.include_shared, args.use_fetch, args.hide_token, args.user_projects, group_search=args.group_search)
44
47
  tree.load_tree()
45
48
 
46
49
  if tree.is_empty():
@@ -202,16 +205,23 @@ def parse_args(argv=None):
202
205
  action='store_true',
203
206
  default=True,
204
207
  help='include shared projects in the results')
208
+ parser.add_argument(
209
+ '-g',
210
+ '--group-search',
211
+ metavar=('term'),
212
+ help='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)')
213
+ parser.add_argument(
214
+ '-U',
215
+ '--user-projects',
216
+ action='store_true',
217
+ default=False,
218
+ help='fetch only user personal projects (skips the group tree altogether, group related parameters are ignored). Clones personal projects to \'{gitlab-username}-personal-projects\'')
205
219
  parser.add_argument(
206
220
  '--version',
207
221
  action='store_true',
208
222
  help='print the version')
209
223
 
210
- args = parser.parse_args(argv)
211
- args_print = vars(args).copy()
212
- args_print['token'] = 'xxxxx'
213
- log.debug("running with args [%s]", args_print)
214
- return args
224
+ return parser.parse_args(argv)
215
225
 
216
226
  def validate_path(value):
217
227
  if value.endswith('/'):
@@ -25,6 +25,7 @@ def sync_tree(root, dest, concurrency=1, disable_progress=False, recursive=False
25
25
  actions = get_git_actions(root, dest, recursive, use_fetch, hide_token)
26
26
  with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
27
27
  executor.map(clone_or_pull_project, actions)
28
+
28
29
  elapsed = progress.finish_progress()
29
30
  log.debug("Syncing projects took [%s]", elapsed)
30
31
 
@@ -57,6 +58,7 @@ def clone_or_pull_project(action):
57
58
  '''
58
59
  log.debug("updating existing project %s", action.path)
59
60
  progress.show_progress(action.node.name, 'pull')
61
+
60
62
  try:
61
63
  repo = git.Repo(action.path)
62
64
  if(not action.use_fetch):
@@ -74,6 +76,9 @@ def clone_or_pull_project(action):
74
76
  '''
75
77
  Clone new project
76
78
  '''
79
+ if(action.node.type != "project"):
80
+ log.debug("Skipping clone of node with type [%s] (empty subgroup/group)", action.node.type)
81
+ return
77
82
  log.debug("cloning new project %s", action.path)
78
83
  progress.show_progress(action.node.name, 'clone')
79
84
  multi_options = []
@@ -88,5 +93,5 @@ def clone_or_pull_project(action):
88
93
  log.fatal("User interrupted")
89
94
  sys.exit(0)
90
95
  except Exception as e:
91
- log.debug("Error cloning project %s", action.path, exc_info=True)
96
+ log.error("Error cloning project %s", action.path, exc_info=True)
92
97
 
@@ -17,13 +17,12 @@ log = logging.getLogger(__name__)
17
17
 
18
18
 
19
19
  class GitlabTree:
20
-
21
20
  def __init__(self, url, token, method, naming=None, archived=None, includes=[], excludes=[], in_file=None, concurrency=1, recursive=False, disable_progress=False,
22
- include_shared=True, use_fetch=False, hide_token=False):
21
+ include_shared=True, use_fetch=False, hide_token=False, user_projects=False, group_search=None):
23
22
  self.includes = includes
24
23
  self.excludes = excludes
25
24
  self.url = url
26
- self.root = Node("", root_path="", url=url)
25
+ self.root = Node("", root_path="", url=url, type="root")
27
26
  self.gitlab = Gitlab(url, private_token=token,
28
27
  ssl_verify=GitlabTree.get_ca_path())
29
28
  self.method = method
@@ -38,6 +37,8 @@ class GitlabTree:
38
37
  self.include_shared = include_shared
39
38
  self.use_fetch = use_fetch
40
39
  self.hide_token = hide_token
40
+ self.user_projects = user_projects
41
+ self.group_search = group_search
41
42
 
42
43
  @staticmethod
43
44
  def get_ca_path():
@@ -93,8 +94,8 @@ class GitlabTree:
93
94
  def root_path(self, node):
94
95
  return "/".join([str(n.name) for n in node.path])
95
96
 
96
- def make_node(self, name, parent, url):
97
- node = Node(name=name, parent=parent, url=url)
97
+ def make_node(self, type, name, parent, url):
98
+ node = Node(name=name, parent=parent, url=url, type=type)
98
99
  node.root_path = self.root_path(node)
99
100
  return node
100
101
 
@@ -108,7 +109,7 @@ class GitlabTree:
108
109
  log.debug("Generated URL: %s", project_url)
109
110
  else:
110
111
  log.debug("Hiding token from project url: %s", project_url)
111
- node = self.make_node(project_id, parent,
112
+ node = self.make_node("project", project_id, parent,
112
113
  url=project_url)
113
114
  self.progress.show_progress(node.name, 'project')
114
115
 
@@ -118,16 +119,16 @@ class GitlabTree:
118
119
  self.progress.update_progress_length(len(projects))
119
120
  self.add_projects(parent, projects)
120
121
  except GitlabListError as error:
121
- log.error(f"Error getting projects on {group.name} (ID: {group.id}): {error} )")
122
+ log.error(f"Error getting projects on {group.name} id: [{group.id}] error message: [{error.error_message}]")
122
123
 
123
124
  def get_subgroups(self, group, parent):
124
- subgroups = group.subgroups.list(as_list=False, archived=self.archived, get_all=True)
125
+ subgroups = group.subgroups.list(as_list=False, get_all=True)
125
126
  self.progress.update_progress_length(len(subgroups))
126
127
  for subgroup_def in subgroups:
127
128
  try:
128
129
  subgroup = self.gitlab.groups.get(subgroup_def.id)
129
130
  subgroup_id = subgroup.name if self.naming == FolderNaming.NAME else subgroup.path
130
- node = self.make_node(subgroup_id, parent, url=subgroup.web_url)
131
+ node = self.make_node("subgroup", subgroup_id, parent, url=subgroup.web_url)
131
132
  self.progress.show_progress(node.name, 'group')
132
133
  self.get_subgroups(subgroup, node)
133
134
  self.get_projects(subgroup, node)
@@ -139,12 +140,14 @@ class GitlabTree:
139
140
  raise error
140
141
 
141
142
  def load_gitlab_tree(self):
142
- groups = self.gitlab.groups.list(as_list=False, archived=self.archived, get_all=True)
143
+ log.debug(f"Starting group search with archived: {self.archived} search term: {self.group_search}")
144
+
145
+ groups = self.gitlab.groups.list(as_list=False, archived=self.archived, get_all=True, search=self.group_search)
143
146
  self.progress.init_progress(len(groups))
144
147
  for group in groups:
145
148
  if group.parent_id is None:
146
149
  group_id = group.name if self.naming == FolderNaming.NAME else group.path
147
- node = self.make_node(group_id, self.root, url=group.web_url)
150
+ node = self.make_node("group", group_id, self.root, url=group.web_url)
148
151
  self.progress.show_progress(node.name, 'group')
149
152
  self.get_subgroups(group, node)
150
153
  self.get_projects(group, node)
@@ -157,12 +160,26 @@ class GitlabTree:
157
160
  dct = yaml.safe_load(stream)
158
161
  self.root = DictImporter().import_(dct)
159
162
 
163
+ def load_user_tree(self):
164
+ log.debug(f"Starting user project search with archived: {self.archived}")
165
+ self.gitlab.auth()
166
+ user = self.gitlab.users.get(self.gitlab.user.id)
167
+ username = user.username
168
+ projects = user.projects.list(as_list=False, archived=self.archived, get_all=True)
169
+ self.progress.init_progress(len(projects))
170
+ root = self.make_node("group", f"{username}-prsonal-projects", self.root, url=f"{self.url}/users/{username}/projects")
171
+ self.add_projects(root, projects)
172
+
173
+
160
174
  def load_tree(self):
161
175
  if self.in_file:
162
176
  log.debug("Loading tree from file [%s]", self.in_file)
163
177
  self.load_file_tree()
178
+ elif self.user_projects:
179
+ log.debug("Loading user personal projects from gitlab server [%s]", self.url)
180
+ self.load_user_tree()
164
181
  else:
165
- log.debug("Loading projects tree gitlab server [%s]", self.url)
182
+ log.debug("Loading projects tree from gitlab server [%s]", self.url)
166
183
  self.load_gitlab_tree()
167
184
 
168
185
  log.debug("Fetched root node with [%d] projects" % len(
@@ -7,13 +7,12 @@ class ProgressBar:
7
7
  self.progress = None
8
8
  self.description = description
9
9
  self.disabled = disabled
10
- self.start = None
10
+ self.start = time.time()
11
11
 
12
12
  def init_progress(self, total):
13
13
  if self.progress is None:
14
- self.start = time.time()
15
14
  self.progress = tqdm(total=total, unit="projects",
16
- bar_format="{desc}: {percentage:.1f}%|{bar:80}| {n_fmt}/{total_fmt}{postfix}", desc=self.description, leave=False, disable=self.disabled)
15
+ bar_format="{desc}: {percentage:.1f}%|{bar:80}| {n_fmt}/{total_fmt}{postfix}", desc=self.description, leave=False, disable=self.disabled)
17
16
 
18
17
  def update_progress_length(self, added):
19
18
  if self.progress is not None:
@@ -29,7 +28,7 @@ class ProgressBar:
29
28
  def finish_progress(self):
30
29
  if self.progress is not None:
31
30
  self.progress.close()
32
- end = time.time()
33
- hours, rem = divmod(end-self.start, 3600)
34
- minutes, seconds = divmod(rem, 60)
35
- return "{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), seconds)
31
+ end = time.time()
32
+ hours, rem = divmod(end-self.start, 3600)
33
+ minutes, seconds = divmod(rem, 60)
34
+ return "{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), seconds)