gitlabber 1.1.9__tar.gz → 1.2.0__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.0/PKG-INFO +187 -0
  2. {gitlabber-1.1.9 → gitlabber-1.2.0}/README.rst +26 -23
  3. gitlabber-1.2.0/gitlabber/__init__.py +1 -0
  4. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/cli.py +26 -1
  5. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/git.py +19 -11
  6. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/gitlab_tree.py +25 -7
  7. gitlabber-1.2.0/gitlabber.egg-info/PKG-INFO +187 -0
  8. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber.egg-info/SOURCES.txt +13 -1
  9. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber.egg-info/entry_points.txt +0 -1
  10. {gitlabber-1.1.9 → gitlabber-1.2.0}/setup.py +5 -5
  11. gitlabber-1.2.0/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  12. gitlabber-1.2.0/tests/__pycache__/gitlab_test_utils.cpython-311.pyc +0 -0
  13. gitlabber-1.2.0/tests/__pycache__/io_test_util.cpython-311.pyc +0 -0
  14. gitlabber-1.2.0/tests/__pycache__/test_archive.cpython-311-pytest-8.2.2.pyc +0 -0
  15. gitlabber-1.2.0/tests/__pycache__/test_cli.cpython-311-pytest-8.2.2.pyc +0 -0
  16. gitlabber-1.2.0/tests/__pycache__/test_e2e.cpython-311-pytest-8.2.2.pyc +0 -0
  17. gitlabber-1.2.0/tests/__pycache__/test_format.cpython-311-pytest-8.2.2.pyc +0 -0
  18. gitlabber-1.2.0/tests/__pycache__/test_git.cpython-311-pytest-8.2.2.pyc +0 -0
  19. gitlabber-1.2.0/tests/__pycache__/test_gitlab_tree.cpython-311-pytest-8.2.2.pyc +0 -0
  20. gitlabber-1.2.0/tests/__pycache__/test_integration.cpython-311-pytest-8.2.2.pyc +0 -0
  21. gitlabber-1.2.0/tests/__pycache__/test_method.cpython-311-pytest-8.2.2.pyc +0 -0
  22. gitlabber-1.2.0/tests/__pycache__/test_naming.cpython-311-pytest-8.2.2.pyc +0 -0
  23. gitlabber-1.2.0/tests/gitlab_test_utils.py +174 -0
  24. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_cli.py +7 -4
  25. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_git.py +17 -2
  26. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_gitlab_tree.py +21 -0
  27. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_integration.py +0 -1
  28. gitlabber-1.1.9/PKG-INFO +0 -184
  29. gitlabber-1.1.9/gitlabber/__init__.py +0 -1
  30. gitlabber-1.1.9/gitlabber.egg-info/PKG-INFO +0 -184
  31. gitlabber-1.1.9/tests/gitlab_test_utils.py +0 -133
  32. {gitlabber-1.1.9 → gitlabber-1.2.0}/LICENSE.txt +0 -0
  33. {gitlabber-1.1.9 → gitlabber-1.2.0}/MANIFEST.in +0 -0
  34. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/__main__.py +0 -0
  35. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/archive.py +0 -0
  36. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/format.py +0 -0
  37. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/method.py +0 -0
  38. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/naming.py +0 -0
  39. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber/progress.py +0 -0
  40. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber.egg-info/dependency_links.txt +0 -0
  41. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber.egg-info/requires.txt +0 -0
  42. {gitlabber-1.1.9 → gitlabber-1.2.0}/gitlabber.egg-info/top_level.txt +0 -0
  43. {gitlabber-1.1.9 → gitlabber-1.2.0}/setup.cfg +0 -0
  44. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/__init__.py +0 -0
  45. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/io_test_util.py +0 -0
  46. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test-input.yaml +0 -0
  47. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test-output.json +0 -0
  48. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test-output.tree +0 -0
  49. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test-output.yaml +0 -0
  50. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_archive.py +0 -0
  51. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_e2e.py +0 -0
  52. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_format.py +0 -0
  53. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_method.py +0 -0
  54. {gitlabber-1.1.9 → gitlabber-1.2.0}/tests/test_naming.py +0 -0
@@ -0,0 +1,187 @@
1
+ Metadata-Version: 2.1
2
+ Name: gitlabber
3
+ Version: 1.2.0
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/master.zip
7
+ Author: Erez Mazor
8
+ Author-email: erezmazor@gmail.com
9
+ License: MIT
10
+ Keywords: gitlab,python,cli
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Utilities
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Requires-Python: >=3
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE.txt
26
+
27
+ .. image:: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml/badge.svg?branch=master
28
+ :target: https://github.com/ezbz/gitlabber/actions/workflows/python-app.yml
29
+
30
+ .. image:: https://codecov.io/gh/ezbz/gitlabber/branch/master/graph/badge.svg
31
+ :target: https://codecov.io/gh/ezbz/gitlabber
32
+
33
+ .. image:: https://badge.fury.io/py/gitlabber.svg
34
+ :target: https://badge.fury.io/py/gitlabber
35
+
36
+ .. image:: https://img.shields.io/pypi/l/gitlabber.svg
37
+ :target: https://pypi.python.org/pypi/gitlabber/
38
+
39
+ .. image:: https://img.shields.io/pypi/pyversions/gitlabber
40
+ :target: https://pypi.python.org/pypi/gitlabber/
41
+
42
+ .. image:: https://readthedocs.org/projects/gitlabber/badge/?version=latest&style=plastic
43
+ :target: https://gitlabber.readthedocs.io/en/latest/README.html
44
+
45
+ Gitlabber
46
+ =========
47
+
48
+ * A utility to clone and pull GitLab groups, subgroups, projects based on path selection
49
+
50
+
51
+ Purpose
52
+ -------
53
+
54
+ 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.
55
+
56
+
57
+
58
+ Installation
59
+ ------------
60
+
61
+ * You can install Gitlabber from `PyPi <https://pypi.org/project/gitlabber>`_:
62
+
63
+ .. code-block:: bash
64
+
65
+ pip install gitlabber
66
+
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`
68
+ and `read_api` (or `api`, for GitLab versions <12.0)
69
+
70
+ Usage
71
+ -----
72
+
73
+ * Arguments can be provided via the CLI arguments directly or via environment variables::
74
+ +---------------+---------------+---------------------------+
75
+ | Argument | Flag | Environment Variable |
76
+ +===============+===============+===========================+
77
+ | token | -t | `GITLAB_TOKEN` |
78
+ +---------------+---------------+---------------------------+
79
+ | url | -u | `GITLAB_URL` |
80
+ +---------------+---------------+---------------------------+
81
+ | method | -m | `GITLABBER_CLONE_METHOD` |
82
+ +---------------+---------------+---------------------------+
83
+ | naming | -n | `GITLABBER_FOLDER_NAMING` |
84
+ +---------------+---------------+---------------------------+
85
+ | include | -i | `GITLABBER_INCLUDE` |
86
+ +---------------+---------------+---------------------------+
87
+ | exclude | -x | `GITLABBER_EXCLUDE` |
88
+ +---------------+---------------+---------------------------+
89
+
90
+ * To view the tree run the command with your includes/excludes and the `-p` flag. It will print your tree like so:
91
+
92
+ .. code-block:: bash
93
+
94
+ root [http://gitlab.my.com]
95
+ ├── group1 [/group1]
96
+ │ └── subgroup1 [/group1/subgroup1]
97
+ │ └── project1 [/group1/subgroup1/project1]
98
+ └── group2 [/group2]
99
+ ├── subgroup1 [/group2/subgroup1]
100
+ │ └── project2 [/group2/subgroup1/project2]
101
+ ├── subgroup2 [/group2/subgroup2]
102
+ └── subgroup3 [/group2/subgroup3]
103
+
104
+ * To see how to use glob patterns and regex to filter tree nodes, see the `globre project page <https://pypi.org/project/globre/#details>`_.
105
+
106
+ * 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.
107
+
108
+ * 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
109
+
110
+ * Printed Usage:
111
+
112
+ .. code-block:: bash
113
+
114
+ usage: gitlabber [-h] [-t token] [-u url] [--verbose] [-p] [-d]
115
+ [--print-format {json,yaml,tree}] [-m {ssh,https}] [-i csv]
116
+ [-x csv] [--version] [-g {id,full_path,full_name}]
117
+ [dest]
118
+
119
+ Gitlabber - clones or pulls entire groups/projects tree from GitLab
120
+
121
+ positional arguments:
122
+ dest destination path for the cloned tree (created if doesn't exist)
123
+
124
+ optional arguments:
125
+ -h, --help show this help message and exit
126
+ -t token, --token token
127
+ gitlab personal access token https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html
128
+ -u url, --url url base gitlab url (e.g.: 'http://gitlab.mycompany.com')
129
+ --verbose print more verbose output
130
+ -p, --print print the tree without cloning
131
+ --print-format {json,yaml,tree}
132
+ print format (default: 'tree')
133
+ -n {name,path}, --naming {name,path}
134
+ the folder naming strategy for projects (default: "name")
135
+ -m {ssh,http}, --method {ssh,http}
136
+ the git transport method to use for cloning (default: "ssh")
137
+ -a {include,exclude,only}, --archived {include,exclude,only}
138
+ include archived projects and groups in the results (default: "include")
139
+ -i csv, --include csv
140
+ comma delimited list of glob patterns of paths to projects or groups to clone/pull
141
+ -x csv, --exclude csv
142
+ comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
143
+ -r, --recursive clone/pull git submodules recursively
144
+ -T, --hide-token hide the token from the gitlab request URL (for method: http)
145
+ -f, --use-fetch clone/fetch git repository (mirrored repositories)
146
+ -s, --include-shared include shared projects in the results
147
+ --version print the version
148
+
149
+ examples:
150
+
151
+ clone an entire gitlab tree using a base url and a token:
152
+ gitlabber -t <personal access token> -u <gitlab url> .
153
+
154
+ # the following examples assume you provided token/url in environment variables so these arguments are omitted
155
+ only print the gitlab tree:
156
+ gitlabber -p .
157
+
158
+ clone only projects under subgroup 'MySubGroup' to location '~/GitlabRoot':
159
+ gitlabber -i '/MyGroup/MySubGroup**' ~/GitlabRoot
160
+
161
+ clone only projects under group 'MyGroup' excluding any projects under subgroup 'MySubGroup':
162
+ gitlabber -i '/MyGroup**' -x '/MyGroup/MySubGroup**' .
163
+
164
+ clone an entire gitlab tree except projects under groups named 'ArchiveGroup':
165
+ gitlabber -x '/ArchiveGroup**' .
166
+
167
+ clone projects that start with a case insensitive 'w' using a regular expression:
168
+ gitlabber -i '/{[w].*}' .
169
+
170
+ Debugging
171
+ ---------
172
+ * You can use the `--verbose` flag to print Gitlabber debug messages
173
+ * 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:
174
+
175
+ .. code-block:: bash
176
+
177
+ export GIT_PYTHON_TRACE='full'
178
+
179
+ Troubleshooting
180
+ ---------------
181
+ * `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`)
182
+
183
+ Known Limitations
184
+ -----------------
185
+ * 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.
186
+ * 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).
187
+ * 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>`_.
@@ -10,7 +10,7 @@
10
10
  .. image:: https://img.shields.io/pypi/l/gitlabber.svg
11
11
  :target: https://pypi.python.org/pypi/gitlabber/
12
12
 
13
- .. image:: https://img.shields.io/pypi/pyversions/ansicolortags.svg
13
+ .. image:: https://img.shields.io/pypi/pyversions/gitlabber
14
14
  :target: https://pypi.python.org/pypi/gitlabber/
15
15
 
16
16
  .. image:: https://readthedocs.org/projects/gitlabber/badge/?version=latest&style=plastic
@@ -19,27 +19,27 @@
19
19
  Gitlabber
20
20
  =========
21
21
 
22
- * A utility to clone and pull Gitlab groups, subgroups, projects based on path selection
22
+ * A utility to clone and pull GitLab groups, subgroups, projects based on path selection
23
23
 
24
24
 
25
25
  Purpose
26
26
  -------
27
27
 
28
- 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.
28
+ 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.
29
29
 
30
30
 
31
31
 
32
32
  Installation
33
33
  ------------
34
34
 
35
- * You can install gitlabber from `PyPi <https://pypi.org/project/gitlabber>`_ :
35
+ * You can install Gitlabber from `PyPi <https://pypi.org/project/gitlabber>`_:
36
36
 
37
37
  .. code-block:: bash
38
38
 
39
39
  pip install gitlabber
40
40
 
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`
42
- and `read_api`
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`
42
+ and `read_api` (or `api`, for GitLab versions <12.0)
43
43
 
44
44
  Usage
45
45
  -----
@@ -61,7 +61,7 @@ Usage
61
61
  | exclude | -x | `GITLABBER_EXCLUDE` |
62
62
  +---------------+---------------+---------------------------+
63
63
 
64
- * To view the tree run the command with your includes/excludes and the `-p` flag it will print your tree like so
64
+ * To view the tree run the command with your includes/excludes and the `-p` flag. It will print your tree like so:
65
65
 
66
66
  .. code-block:: bash
67
67
 
@@ -75,22 +75,22 @@ Usage
75
75
  ├── subgroup2 [/group2/subgroup2]
76
76
  └── subgroup3 [/group2/subgroup3]
77
77
 
78
- * To see how to use glob patterns and regex to filter tree nodes see `globre project page <https://pypi.org/project/globre/>`_ .
78
+ * To see how to use glob patterns and regex to filter tree nodes, see the `globre project page <https://pypi.org/project/globre/#details>`_.
79
79
 
80
- * Cloning vs Pulling: when running Gitlabber consecutively with 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.
80
+ * 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.
81
81
 
82
- * 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 upading cloned repositories
82
+ * 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
83
83
 
84
84
  * Printed Usage:
85
85
 
86
86
  .. code-block:: bash
87
87
 
88
- usage: gitlabber [-h] [-t token] [-u url] [--verbose] [-p]
88
+ usage: gitlabber [-h] [-t token] [-u url] [--verbose] [-p] [-d]
89
89
  [--print-format {json,yaml,tree}] [-m {ssh,https}] [-i csv]
90
- [-x csv] [--version]
90
+ [-x csv] [--version] [-g {id,full_path,full_name}]
91
91
  [dest]
92
92
 
93
- Gitlabber - clones or pulls entire groups/projects tree from gitlab
93
+ Gitlabber - clones or pulls entire groups/projects tree from GitLab
94
94
 
95
95
  positional arguments:
96
96
  dest destination path for the cloned tree (created if doesn't exist)
@@ -115,6 +115,9 @@ Usage
115
115
  -x csv, --exclude csv
116
116
  comma delimited list of glob patterns of paths to projects or groups to exclude from clone/pull
117
117
  -r, --recursive clone/pull git submodules recursively
118
+ -T, --hide-token hide the token from the gitlab request URL (for method: http)
119
+ -f, --use-fetch clone/fetch git repository (mirrored repositories)
120
+ -s, --include-shared include shared projects in the results
118
121
  --version print the version
119
122
 
120
123
  examples:
@@ -138,21 +141,21 @@ Usage
138
141
  clone projects that start with a case insensitive 'w' using a regular expression:
139
142
  gitlabber -i '/{[w].*}' .
140
143
 
141
- Debugging
144
+ Debugging
142
145
  ---------
143
- * You can use the `--verbose` flag to get Gitlabber debug messages printed
144
- * For more verbose gitlab messages you can get `GitPython <https://gitpython.readthedocs.io/en/stable>`__ module to print more debug messages by setting the environment variable:
146
+ * You can use the `--verbose` flag to print Gitlabber debug messages
147
+ * 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:
145
148
 
146
149
  .. code-block:: bash
147
150
 
148
151
  export GIT_PYTHON_TRACE='full'
149
152
 
150
- Toubleshooting
151
- --------------
152
- * `GitlabHttpError: 503`: make sure you provide the base url to your gitalb installation (e.g., `https://gitlab.my.com` and not `https://gitlab.my.com/some/nested/path`)
153
+ Troubleshooting
154
+ ---------------
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`)
153
156
 
154
157
  Known Limitations
155
- -----------------
156
- * Project Renaming: Gitlabber doesn't maintain local state and will not rename local projects when they are renamed on the server and it will clone them again under their new name.
157
- * Folder Naming Strategy: consecutively running gitlabber with different values for the `-n` parameter will produce undesirable results, keep the same value as previous runs or simply don't change it from the default (project name)
158
- * When using gitlab.com observe `rate limits <https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits/>`__ when cloning large number of projects and the `ones <https://docs.gitlab.com/ee/security/rate_limits.html>`__ for on-premise installations
158
+ -----------------
159
+ * 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).
161
+ * 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.0'
@@ -38,7 +38,8 @@ def main():
38
38
  excludes=split(args.exclude)
39
39
 
40
40
  tree = GitlabTree(args.url, args.token, args.method, args.naming, args.archived.api_value, includes,
41
- excludes, args.file, args.concurrency, args.recursive, args.verbose)
41
+ excludes, args.file, args.concurrency, args.recursive, args.verbose,
42
+ args.use_fetch, args.hide_token)
42
43
  log.debug("Reading projects tree from gitlab at [%s]", args.url)
43
44
  tree.load_tree()
44
45
 
@@ -105,6 +106,12 @@ def parse_args(argv=None):
105
106
  metavar=('token'),
106
107
  default=os.environ.get('GITLAB_TOKEN'),
107
108
  help='gitlab personal access token https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html')
109
+ parser.add_argument(
110
+ '-T',
111
+ '--hide-token',
112
+ action='store_true',
113
+ default=False,
114
+ help='use an inline URL token (avoids storing the gitlab personal access token in the .git/config)')
108
115
  parser.add_argument(
109
116
  '-u',
110
117
  '--url',
@@ -177,6 +184,24 @@ def parse_args(argv=None):
177
184
  action='store_true',
178
185
  default=False,
179
186
  help='clone/pull git submodules recursively')
187
+ parser.add_argument(
188
+ '-F',
189
+ '--use-fetch',
190
+ action='store_true',
191
+ default=False,
192
+ help='clone/fetch git repository (mirrored repositories)')
193
+ parser.add_argument(
194
+ '-d',
195
+ '--dont-checkout',
196
+ action='store_true',
197
+ default=False,
198
+ help="don't checkout pulled git repository")
199
+ parser.add_argument(
200
+ '-s',
201
+ '--include-shared',
202
+ action='store_true',
203
+ default=True,
204
+ help='include shared projects in the results')
180
205
  parser.add_argument(
181
206
  '--version',
182
207
  action='store_true',
@@ -12,31 +12,33 @@ progress = ProgressBar('* syncing projects')
12
12
 
13
13
 
14
14
  class GitAction:
15
- def __init__(self, node, path, recursive=False):
15
+ def __init__(self, node, path, recursive=False, use_fetch=False, inline_token=False):
16
16
  self.node = node
17
17
  self.path = path
18
18
  self.recursive = recursive
19
+ self.use_fetch = use_fetch
20
+ self.inline_token = inline_token
19
21
 
20
- def sync_tree(root, dest, concurrency=1, disable_progress=False, recursive=False):
22
+ def sync_tree(root, dest, concurrency=1, disable_progress=False, recursive=False, use_fetch=False, inline_token=False):
21
23
  if not disable_progress:
22
24
  progress.init_progress(len(root.leaves))
23
- actions = get_git_actions(root, dest, recursive)
25
+ actions = get_git_actions(root, dest, recursive, use_fetch, inline_token)
24
26
  with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
25
27
  executor.map(clone_or_pull_project, actions)
26
28
  elapsed = progress.finish_progress()
27
29
  log.debug("Syncing projects took [%s]", elapsed)
28
30
 
29
31
 
30
- def get_git_actions(root, dest, recursive):
32
+ def get_git_actions(root, dest, recursive, use_fetch, inline_token):
31
33
  actions = []
32
34
  for child in root.children:
33
35
  path = "%s%s" % (dest, child.root_path)
34
36
  if not os.path.exists(path):
35
37
  os.makedirs(path)
36
38
  if child.is_leaf:
37
- actions.append(GitAction(child, path, recursive))
39
+ actions.append(GitAction(child, path, recursive, use_fetch, inline_token))
38
40
  if not child.is_leaf:
39
- actions.extend(get_git_actions(child, dest, recursive))
41
+ actions.extend(get_git_actions(child, dest, recursive, use_fetch, inline_token))
40
42
  return actions
41
43
 
42
44
 
@@ -57,7 +59,10 @@ def clone_or_pull_project(action):
57
59
  progress.show_progress(action.node.name, 'pull')
58
60
  try:
59
61
  repo = git.Repo(action.path)
60
- repo.remotes.origin.pull()
62
+ if(not action.use_fetch):
63
+ repo.remotes.origin.pull()
64
+ else:
65
+ repo.remotes.origin.fetch()
61
66
  if(action.recursive):
62
67
  repo.submodule_update(recursive=True)
63
68
  except KeyboardInterrupt:
@@ -71,11 +76,14 @@ def clone_or_pull_project(action):
71
76
  '''
72
77
  log.debug("cloning new project %s", action.path)
73
78
  progress.show_progress(action.node.name, 'clone')
79
+ multi_options = []
80
+ if(action.recursive):
81
+ multi_options.append('--recursive')
82
+ if(action.use_fetch):
83
+ multi_options.append('--mirror')
74
84
  try:
75
- if(action.recursive):
76
- git.Repo.clone_from(action.node.url, action.path, multi_options=['--recursive'])
77
- else:
78
- git.Repo.clone_from(action.node.url, action.path)
85
+ git.Repo.clone_from(action.node.url, action.path, multi_options=multi_options)
86
+
79
87
  except KeyboardInterrupt:
80
88
  log.fatal("User interrupted")
81
89
  sys.exit(0)
@@ -1,4 +1,6 @@
1
1
  from gitlab import Gitlab
2
+ from gitlab.exceptions import GitlabGetError, GitlabListError
3
+ from gitlab.exceptions import GitlabGetError
2
4
  from anytree import Node, RenderTree
3
5
  from anytree.exporter import DictExporter, JsonExporter
4
6
  from anytree.importer import DictImporter
@@ -16,7 +18,8 @@ log = logging.getLogger(__name__)
16
18
 
17
19
  class GitlabTree:
18
20
 
19
- def __init__(self, url, token, method, naming, archived=None, includes=[], excludes=[], in_file=None, concurrency=1, recursive=False, disable_progress=False):
21
+ 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):
20
23
  self.includes = includes
21
24
  self.excludes = excludes
22
25
  self.url = url
@@ -31,6 +34,10 @@ class GitlabTree:
31
34
  self.recursive = recursive
32
35
  self.disable_progress = disable_progress
33
36
  self.progress = ProgressBar('* loading tree', disable_progress)
37
+ self.token = token
38
+ self.include_shared = include_shared
39
+ self.use_fetch = use_fetch
40
+ self.hide_token = hide_token
34
41
 
35
42
  @staticmethod
36
43
  def get_ca_path():
@@ -57,7 +64,7 @@ class GitlabTree:
57
64
 
58
65
  def is_excluded(self, node):
59
66
  '''
60
- returns True if the node should be excluded
67
+ returns True if the node should be excluded
61
68
  if the are no exclude patterns then nothing is excluded
62
69
  any exclude pattern matching the root path will result in exclusion
63
70
  '''
@@ -96,14 +103,23 @@ class GitlabTree:
96
103
  for project in projects:
97
104
  project_id = project.name if self.naming == FolderNaming.NAME else project.path
98
105
  project_url = project.ssh_url_to_repo if self.method is CloneMethod.SSH else project.http_url_to_repo
106
+ if self.token is not None and self.method is CloneMethod.HTTP:
107
+ if (not self.hide_token):
108
+ project_url = project_url.replace('://', '://gitlab-token:%s@' % self.token)
109
+ log.debug("Generated URL: %s", project_url)
110
+ else:
111
+ log.debug("Hiding token from project url: %s", project_url)
99
112
  node = self.make_node(project_id, parent,
100
113
  url=project_url)
101
114
  self.progress.show_progress(node.name, 'project')
102
115
 
103
116
  def get_projects(self, group, parent):
104
- projects = group.projects.list(as_list=False, archived=self.archived)
105
- self.progress.update_progress_length(len(projects))
106
- self.add_projects(parent, projects)
117
+ try:
118
+ projects = group.projects.list(archived=self.archived, with_shared=self.include_shared, get_all=True)
119
+ self.progress.update_progress_length(len(projects))
120
+ self.add_projects(parent, projects)
121
+ except GitlabListError as error:
122
+ log.error(f"Error getting projects on {group.name} (ID: {group.id}): {error} )")
107
123
 
108
124
  def get_subgroups(self, group, parent):
109
125
  subgroups = group.subgroups.list(as_list=False, archived=self.archived)
@@ -116,6 +132,7 @@ class GitlabTree:
116
132
  self.get_subgroups(subgroup, node)
117
133
  self.get_projects(subgroup, node)
118
134
 
135
+
119
136
  def load_gitlab_tree(self):
120
137
  groups = self.gitlab.groups.list(as_list=False, archived=self.archived)
121
138
  self.progress.init_progress(len(groups))
@@ -125,7 +142,7 @@ class GitlabTree:
125
142
  node = self.make_node(group_id, self.root, url=group.web_url)
126
143
  self.progress.show_progress(node.name, 'group')
127
144
  self.get_subgroups(group, node)
128
- self.get_projects(group, node)
145
+ self.get_projects(group, node)
129
146
 
130
147
  elapsed = self.progress.finish_progress()
131
148
  log.debug("Loading projects tree from gitlab took [%s]", elapsed)
@@ -178,7 +195,8 @@ class GitlabTree:
178
195
  log.debug("Going to clone/pull [%s] groups and [%s] projects" %
179
196
  (len(self.root.descendants) - len(self.root.leaves), len(self.root.leaves)))
180
197
  sync_tree(self.root, dest, concurrency=self.concurrency,
181
- disable_progress=self.disable_progress, recursive=self.recursive)
198
+ disable_progress=self.disable_progress, recursive=self.recursive,
199
+ use_fetch=self.use_fetch, hide_token=self.hide_token)
182
200
 
183
201
  def is_empty(self):
184
202
  return self.root.height < 1