Browse Source

first commit

Christoph Haas 4 months ago
commit
7713b0eef2
4 changed files with 759 additions and 0 deletions
  1. 123
    0
      .gitignore
  2. 5
    0
      LICENSE
  3. 17
    0
      README.md
  4. 614
    0
      migrate.py

+ 123
- 0
.gitignore View File

@@ -0,0 +1,123 @@
1
+# ---> Python
2
+# Byte-compiled / optimized / DLL files
3
+__pycache__/
4
+*.py[cod]
5
+*$py.class
6
+
7
+# C extensions
8
+*.so
9
+
10
+# Distribution / packaging
11
+.Python
12
+build/
13
+develop-eggs/
14
+dist/
15
+downloads/
16
+eggs/
17
+.eggs/
18
+lib/
19
+lib64/
20
+parts/
21
+sdist/
22
+var/
23
+wheels/
24
+*.egg-info/
25
+.installed.cfg
26
+*.egg
27
+MANIFEST
28
+
29
+# PyInstaller
30
+#  Usually these files are written by a python script from a template
31
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+*.manifest
33
+*.spec
34
+
35
+# Installer logs
36
+pip-log.txt
37
+pip-delete-this-directory.txt
38
+
39
+# Unit test / coverage reports
40
+htmlcov/
41
+.tox/
42
+.nox/
43
+.coverage
44
+.coverage.*
45
+.cache
46
+nosetests.xml
47
+coverage.xml
48
+*.cover
49
+.hypothesis/
50
+.pytest_cache/
51
+
52
+# Translations
53
+*.mo
54
+*.pot
55
+
56
+# Django stuff:
57
+*.log
58
+local_settings.py
59
+db.sqlite3
60
+
61
+# Flask stuff:
62
+instance/
63
+.webassets-cache
64
+
65
+# Scrapy stuff:
66
+.scrapy
67
+
68
+# Sphinx documentation
69
+docs/_build/
70
+
71
+# PyBuilder
72
+target/
73
+
74
+# Jupyter Notebook
75
+.ipynb_checkpoints
76
+
77
+# IPython
78
+profile_default/
79
+ipython_config.py
80
+
81
+# pyenv
82
+.python-version
83
+
84
+# celery beat schedule file
85
+celerybeat-schedule
86
+
87
+# SageMath parsed files
88
+*.sage.py
89
+
90
+# Environments
91
+.env
92
+.venv
93
+env/
94
+venv/
95
+ENV/
96
+env.bak/
97
+venv.bak/
98
+
99
+# Spyder project settings
100
+.spyderproject
101
+.spyproject
102
+
103
+# Rope project settings
104
+.ropeproject
105
+
106
+# mkdocs documentation
107
+/site
108
+
109
+# mypy
110
+.mypy_cache/
111
+.dmypy.json
112
+dmypy.json
113
+
114
+# Pyre type checker
115
+.pyre/
116
+
117
+# ---> VisualStudioCode
118
+.vscode/*
119
+!.vscode/settings.json
120
+!.vscode/tasks.json
121
+!.vscode/launch.json
122
+!.vscode/extensions.json
123
+

+ 5
- 0
LICENSE View File

@@ -0,0 +1,5 @@
1
+MIT License
2
+Copyright (c) <year> <copyright holders>
3
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
5
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 17
- 0
README.md View File

@@ -0,0 +1,17 @@
1
+# Gitlab to Gitea migration script.
2
+
3
+This script uses the Gitlab and Gitea API's to migrate all data from
4
+Gitlab to Gitea.
5
+
6
+This script support migrating the following data:
7
+ - Repositories & Wiki
8
+ - Milestones
9
+ - Labels
10
+ - Issues
11
+ - Users
12
+ - Groups
13
+
14
+## Usage
15
+Change items in the config section of the script.
16
+
17
+Install all dependencies and use python3 to execute the script.

+ 614
- 0
migrate.py View File

@@ -0,0 +1,614 @@
1
+import base64
2
+import os
3
+import time
4
+import random
5
+import string
6
+import requests
7
+import json
8
+import dateutil.parser
9
+import datetime
10
+
11
+import gitlab  # pip install python-gitlab
12
+import gitlab.v4.objects
13
+import pygitea # pip install pygitea (https://github.com/h44z/pygitea)
14
+
15
+SCRIPT_VERSION = "1.0"
16
+GLOBAL_ERROR_COUNT = 0
17
+
18
+#######################
19
+# CONFIG SECTION START
20
+#######################
21
+GITLAB_URL = 'https://gitlab.source.com'
22
+GITLAB_TOKEN = 'gitlab token'
23
+
24
+# needed to clone the repositories, keep empty to try publickey (untested)
25
+GITLAB_ADMIN_USER = 'admin username'
26
+GITLAB_ADMIN_PASS = 'admin password'
27
+
28
+GITEA_URL = 'https://gitea.dest.com'
29
+GITEA_TOKEN = 'gitea token'
30
+#######################
31
+# CONFIG SECTION END
32
+#######################
33
+
34
+
35
+def main():
36
+    print_color(bcolors.HEADER, "---=== Gitlab to Gitea migration ===---")
37
+    print("Version: " + SCRIPT_VERSION)
38
+    print()
39
+
40
+    # private token or personal token authentication
41
+    gl = gitlab.Gitlab(GITLAB_URL, private_token=GITLAB_TOKEN)
42
+    gl.auth()
43
+    assert(isinstance(gl.user, gitlab.v4.objects.CurrentUser))
44
+
45
+
46
+    gt = pygitea.API(GITEA_URL, token=GITEA_TOKEN)
47
+    gt_version = gt.get('/version').json()
48
+    print_info("Connected to Gitea, version: " + str(gt_version['version']))
49
+
50
+    # IMPORT USERS AND GROUPS
51
+    import_users_groups(gl, gt)
52
+
53
+    # IMPORT PROJECTS
54
+    import_projects(gl, gt)
55
+
56
+    print()
57
+    if GLOBAL_ERROR_COUNT == 0:
58
+        print_success("Migration finished with no errors!")
59
+    else:
60
+        print_error("Migration finished with " + str(GLOBAL_ERROR_COUNT) + " errors!")
61
+
62
+
63
+# 
64
+# Data loading helpers for Gitea
65
+#
66
+
67
+def get_labels(gitea_api: pygitea, owner: string, repo: string) -> []:
68
+    existing_labels = []
69
+    label_response: requests.Response = gitea_api.get("/repos/" + owner + "/" + repo + "/labels")
70
+    if label_response.ok:
71
+        existing_labels = label_response.json()
72
+    else:
73
+        print_error("Failed to load existing milestones for project " + repo + "! " + label_response.text)
74
+
75
+    return existing_labels
76
+
77
+
78
+def get_milestones(gitea_api: pygitea, owner: string, repo: string) -> []:
79
+    existing_milestones = []
80
+    milestone_response: requests.Response = gitea_api.get("/repos/" + owner + "/" + repo + "/milestones")
81
+    if milestone_response.ok:
82
+        existing_milestones = milestone_response.json()
83
+    else:
84
+        print_error("Failed to load existing milestones for project " + repo + "! " + milestone_response.text)
85
+
86
+    return existing_milestones
87
+
88
+
89
+def get_issues(gitea_api: pygitea, owner: string, repo: string) -> []:
90
+    existing_issues = []
91
+    issue_response: requests.Response = gitea_api.get("/repos/" + owner + "/" + repo + "/issues", params={
92
+        "state": "all",
93
+        "page": -1
94
+    })
95
+    if issue_response.ok:
96
+        existing_issues = issue_response.json()
97
+    else:
98
+        print_error("Failed to load existing issues for project " + repo + "! " + issue_response.text)
99
+
100
+    return existing_issues
101
+
102
+
103
+def get_teams(gitea_api: pygitea, orgname: string) -> []:
104
+    existing_teams = []
105
+    team_response: requests.Response = gitea_api.get("/orgs/" + orgname + "/teams")
106
+    if team_response.ok:
107
+        existing_teams = team_response.json()
108
+    else:
109
+        print_error("Failed to load existing teams for organization " + orgname + "! " + team_response.text)
110
+
111
+    return existing_teams
112
+
113
+
114
+def get_team_members(gitea_api: pygitea, teamid: int) -> []:
115
+    existing_members = []
116
+    member_response: requests.Response = gitea_api.get("/teams/" + str(teamid) + "/members")
117
+    if member_response.ok:
118
+        existing_members = member_response.json()
119
+    else:
120
+        print_error("Failed to load existing members for team " + str(teamid) + "! " + member_response.text)
121
+
122
+    return existing_members
123
+
124
+
125
+def get_collaborators(gitea_api: pygitea, owner: string, repo: string) -> []:
126
+    existing_collaborators = []
127
+    collaborator_response: requests.Response = gitea_api.get("/repos/" + owner+ "/" + repo + "/collaborators")
128
+    if collaborator_response.ok:
129
+        existing_collaborators = collaborator_response.json()
130
+    else:
131
+        print_error("Failed to load existing collaborators for project " + repo + "! " + collaborator_response.text)
132
+
133
+    return existing_collaborators
134
+
135
+
136
+def get_user_or_group(gitea_api: pygitea, name: string) -> {}:
137
+    result = None
138
+    response: requests.Response = gitea_api.get("/users/" + name)
139
+    if response.ok:
140
+        result = response.json()
141
+    else:
142
+        print_error("Failed to load user or group " + name + "! " + response.text)
143
+
144
+    return result
145
+
146
+
147
+def get_user_keys(gitea_api: pygitea, username: string) -> {}:
148
+    result = []
149
+    key_response: requests.Response = gitea_api.get("/users/" + username + "/keys")
150
+    if key_response.ok:
151
+        result = key_response.json()
152
+    else:
153
+        print_error("Failed to load user keys for user " + username + "! " + key_response.text)
154
+
155
+    return result
156
+
157
+
158
+def user_exists(gitea_api: pygitea, username: string) -> bool:
159
+    user_response: requests.Response = gitea_api.get("/users/" + username)
160
+    if user_response.ok:
161
+        print_warning("User " + username + " does already exist in Gitea, skipping!")
162
+    else:
163
+        print("User " + username + " not found in Gitea, importing!")
164
+
165
+    return user_response.ok
166
+
167
+
168
+def user_key_exists(gitea_api: pygitea, username: string, keyname: string) -> bool:
169
+    existing_keys = get_user_keys(gitea_api, username)
170
+    if existing_keys:
171
+        existing_key = next((item for item in existing_keys if item["title"] == keyname), None)
172
+
173
+        if existing_key is not None:
174
+            print_warning("Public key " + keyname + " already exists for user " + username + ", skipping!")
175
+            return True
176
+        else:
177
+            print("Public key " + keyname + " does not exists for user " + username + ", importing!")
178
+            return False
179
+    else:
180
+        print("No public keys for user " + username + ", importing!")
181
+        return False
182
+
183
+
184
+def organization_exists(gitea_api: pygitea, orgname: string) -> bool:
185
+        group_response: requests.Response = gitea_api.get("/orgs/" + orgname)
186
+        if group_response.ok:
187
+            print_warning("Group " + orgname + " does already exist in Gitea, skipping!")
188
+        else:
189
+            print("Group " + orgname + " not found in Gitea, importing!")
190
+
191
+        return group_response.ok
192
+
193
+
194
+def member_exists(gitea_api: pygitea, username: string, teamid: int) -> bool:
195
+    existing_members = get_team_members(gitea_api, teamid)
196
+    if existing_members:
197
+        existing_member = next((item for item in existing_members if item["username"] == username), None)
198
+
199
+        if existing_member:
200
+            print_warning("Member " + username + " is already in team " + str(teamid) + ", skipping!")
201
+            return True
202
+        else:
203
+            print("Member " + username + " is not in team " + str(teamid) + ", importing!")
204
+            return False
205
+    else:
206
+        print("No members in team " + str(teamid) + ", importing!")
207
+        return False
208
+
209
+
210
+def collaborator_exists(gitea_api: pygitea, owner: string, repo: string, username: string) -> bool:
211
+    collaborator_response: requests.Response = gitea_api.get("/repos/" + owner + "/" + repo + "/collaborators/" + username)
212
+    if collaborator_response.ok:
213
+        print_warning("Collaborator " + username + " does already exist in Gitea, skipping!")
214
+    else:
215
+        print("Collaborator " + username + " not found in Gitea, importing!")
216
+
217
+    return collaborator_response.ok
218
+
219
+
220
+def repo_exists(gitea_api: pygitea, owner: string, repo: string) -> bool:
221
+    repo_response: requests.Response = gitea_api.get("/repos/" + owner + "/" + repo)
222
+    if repo_response.ok:
223
+        print_warning("Project " + repo + " does already exist in Gitea, skipping!")
224
+    else:
225
+        print("Project " + repo + " not found in Gitea, importing!")
226
+
227
+    return repo_response.ok
228
+
229
+
230
+def label_exists(gitea_api: pygitea, owner: string, repo: string, labelname: string) -> bool:
231
+    existing_labels = get_labels(gitea_api, owner, repo)
232
+    if existing_labels:
233
+        existing_label = next((item for item in existing_labels if item["name"] == labelname), None)
234
+
235
+        if existing_label is not None:
236
+            print_warning("Label " + labelname + " already exists in project " + repo + ", skipping!")
237
+            return True
238
+        else:
239
+            print("Label " + labelname + " does not exists in project " + repo + ", importing!")
240
+            return False
241
+    else:
242
+        print("No labels in project " + repo + ", importing!")
243
+        return False
244
+
245
+
246
+def milestone_exists(gitea_api: pygitea, owner: string, repo: string, milestone: string) -> bool:
247
+    existing_milestones = get_milestones(gitea_api, owner, repo)
248
+    if existing_milestones:
249
+        existing_milestone = next((item for item in existing_milestones if item["title"] == milestone), None)
250
+
251
+        if existing_milestone is not None:
252
+            print_warning("Milestone " + milestone + " already exists in project " + repo + ", skipping!")
253
+            return True
254
+        else:
255
+            print("Milestone " + milestone + " does not exists in project " + repo + ", importing!")
256
+            return False
257
+    else:
258
+        print("No milestones in project " + repo + ", importing!")
259
+        return False
260
+
261
+
262
+def issue_exists(gitea_api: pygitea, owner: string, repo: string, issue: string) -> bool:
263
+    existing_issues = get_issues(gitea_api, owner, repo)
264
+    if existing_issues:
265
+        existing_issue = next((item for item in existing_issues if item["title"] == issue), None)
266
+
267
+        if existing_issue is not None:
268
+            print_warning("Issue " + issue + " already exists in project " + repo + ", skipping!")
269
+            return True
270
+        else:
271
+            print("Issue " + issue + " does not exists in project " + repo + ", importing!")
272
+            return False
273
+    else:
274
+        print("No issues in project " + repo + ", importing!")
275
+        return False
276
+
277
+
278
+#
279
+# Import helper functions
280
+#
281
+
282
+def _import_project_labels(gitea_api: pygitea, labels: [gitlab.v4.objects.ProjectLabel], owner: string, repo: string):
283
+    for label in labels:
284
+        if not label_exists(gitea_api, owner, repo, label.name):
285
+            import_response: requests.Response = gitea_api.post("/repos/" + owner + "/" + repo + "/labels", json={
286
+                "name": label.name,
287
+                "color": label.color,
288
+                "description": label.description # currently not supported
289
+            })
290
+            if import_response.ok:
291
+                print_info("Label " + label.name + " imported!")
292
+            else:
293
+                print_error("Label " + label.name + " import failed: " + import_response.text)
294
+
295
+
296
+def _import_project_milestones(gitea_api: pygitea, milestones: [gitlab.v4.objects.ProjectMilestone], owner: string, repo: string):
297
+    for milestone in milestones:
298
+        if not milestone_exists(gitea_api, owner, repo, milestone.title):                    
299
+            due_date = None
300
+            if milestone.due_date is not None and milestone.due_date != '':
301
+                due_date = dateutil.parser.parse(milestone.due_date).strftime('%Y-%m-%dT%H:%M:%SZ')
302
+
303
+            import_response: requests.Response = gitea_api.post("/repos/" + owner + "/" + repo + "/milestones", json={
304
+                "description": milestone.description,
305
+                "due_on": due_date,
306
+                "title": milestone.title,
307
+            })
308
+            if import_response.ok:
309
+                print_info("Milestone " + milestone.title + " imported!")
310
+                existing_milestone = import_response.json()
311
+
312
+                if existing_milestone:
313
+                    # update milestone state, this cannot be done in the initial import :(
314
+                    # TODO: gitea api ignores the closed state...
315
+                    update_response: requests.Response = gitea_api.patch("/repos/" + owner + "/" + repo + "/milestones/" + str(existing_milestone['id']), json={
316
+                        "description": milestone.description,
317
+                        "due_on": due_date,
318
+                        "title": milestone.title,
319
+                        "state": milestone.state
320
+                    })
321
+                    if update_response.ok:
322
+                        print_info("Milestone " + milestone.title + " updated!")
323
+                    else:
324
+                        print_error("Milestone " + milestone.title + " update failed: " + update_response.text)
325
+            else:
326
+                print_error("Milestone " + milestone.title + " import failed: " + import_response.text)
327
+
328
+
329
+def _import_project_issues(gitea_api: pygitea, issues: [gitlab.v4.objects.ProjectIssue], owner: string, repo: string):
330
+    # reload all existing milestones and labels, needed for assignment in issues
331
+    existing_milestones = get_milestones(gitea_api, owner, repo)
332
+    existing_labels = get_labels(gitea_api, owner, repo)
333
+
334
+    for issue in issues:
335
+        if not issue_exists(gitea_api, owner, repo, issue.title):
336
+            due_date = ''
337
+            if issue.due_date is not None:
338
+                due_date = dateutil.parser.parse(issue.due_date).strftime('%Y-%m-%dT%H:%M:%SZ')
339
+            
340
+            assignee = None
341
+            if issue.assignee is not None:
342
+                assignee = issue.assignee['username']
343
+
344
+            assignees = []
345
+            for tmp_assignee in issue.assignees:
346
+                assignees.append(tmp_assignee['username'])
347
+
348
+            milestone = None
349
+            if issue.milestone is not None:
350
+                existing_milestone = next((item for item in existing_milestones if item["title"] == issue.milestone['title']), None)
351
+                if existing_milestone:
352
+                    milestone = existing_milestone['id']
353
+
354
+            labels = []
355
+            for label in issue.labels:
356
+                existing_label = next((item for item in existing_labels if item["name"] == label), None)
357
+                if existing_label:
358
+                    labels.append(existing_label['id'])
359
+
360
+            import_response: requests.Response = gitea_api.post("/repos/" + owner + "/" + repo + "/issues", json={
361
+                "assignee": assignee,
362
+                "assignees": assignees,
363
+                "body": issue.description,
364
+                "closed": issue.state == 'closed',
365
+                "due_on": due_date,
366
+                "labels": labels,
367
+                "milestone": milestone,
368
+                "title": issue.title,
369
+            })
370
+            if import_response.ok:
371
+                print_info("Issue " + issue.title + " imported!")
372
+            else:
373
+                print_error("Issue " + issue.title + " import failed: " + import_response.text)
374
+
375
+
376
+def _import_project_repo(gitea_api: pygitea, project: gitlab.v4.objects.Project):
377
+    if not repo_exists(gitea_api, project.namespace['name'], project.name):
378
+        clone_url = project.http_url_to_repo
379
+        if GITLAB_ADMIN_PASS is '' and GITLAB_ADMIN_USER is '':
380
+            clone_url = project.ssh_url_to_repo
381
+        private = project.visibility == 'private' or project.visibility == 'internal'
382
+
383
+        # Load the owner (users and groups can both be fetched using the /users/ endpoint)
384
+        owner = get_user_or_group(gitea_api, project.namespace['name'])
385
+        if owner:
386
+            import_response: requests.Response = gitea_api.post("/repos/migrate", json={
387
+                "auth_password": GITLAB_ADMIN_PASS,
388
+                "auth_username": GITLAB_ADMIN_USER,
389
+                "clone_addr": clone_url,
390
+                "description": project.description,
391
+                "mirror": False,
392
+                "private": private,
393
+                "repo_name": project.name,
394
+                "uid": owner['id']
395
+            })
396
+            if import_response.ok:
397
+                print_info("Project " + project.name + " imported!")
398
+            else:
399
+                print_error("Project " + project.name + " import failed: " + import_response.text)
400
+        else:
401
+            print_error("Failed to load project owner for project " + project.name)
402
+
403
+
404
+def _import_project_repo_collaborators(gitea_api: pygitea, collaborators: [gitlab.v4.objects.ProjectMember], project: gitlab.v4.objects.Project):
405
+    for collaborator in collaborators:
406
+        
407
+        if not collaborator_exists(gitea_api, project.namespace['name'], project.name, collaborator.username):
408
+            permission = "read"
409
+            
410
+            if collaborator.access_level == 10:    # guest access
411
+                permission = "read"
412
+            elif collaborator.access_level == 20:  # reporter access
413
+                permission = "read"
414
+            elif collaborator.access_level == 30:  # developer access
415
+                permission = "write"
416
+            elif collaborator.access_level == 40:  # maintainer access
417
+                permission = "admin"
418
+            elif collaborator.access_level == 50:  # owner access (only for groups)
419
+                print_error("Groupmembers are currently not supported!")
420
+                continue  # groups are not supported
421
+            else:
422
+                print_warning("Unsupported access level " + str(collaborator.access_level) + ", setting permissions to 'read'!")
423
+            
424
+            import_response: requests.Response = gitea_api.put("/repos/" + project.namespace['name'] +"/" + project.name + "/collaborators/" + collaborator.username, json={
425
+                "permission": permission
426
+            })
427
+            if import_response.ok:
428
+                print_info("Collaborator " + collaborator.username + " imported!")
429
+            else:
430
+                print_error("Collaborator " + collaborator.username + " import failed: " + import_response.text)
431
+
432
+
433
+def _import_users(gitea_api: pygitea, users: [gitlab.v4.objects.User], notify: bool = False):
434
+    for user in users:
435
+        keys: [gitlab.v4.objects.UserKey] = user.keys.list(all=True)
436
+
437
+        print("Importing user " + user.username + "...")
438
+        print("Found " + str(len(keys)) + " public keys for user " + user.username)
439
+
440
+        if not user_exists(gitea_api, user.username):
441
+            tmp_password = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10))
442
+            import_response: requests.Response = gitea_api.post("/admin/users", json={
443
+                "email": user.email,
444
+                "full_name": user.name,
445
+                "login_name": user.username,
446
+                "password": tmp_password,
447
+                "send_notify": notify,
448
+                "source_id": 0, # local user
449
+                "username": user.username
450
+            })
451
+            if import_response.ok:
452
+                print_info("User " + user.username + " imported, temporary password: " + tmp_password)
453
+            else:
454
+                print_error("User " + user.username + " import failed: " + import_response.text)
455
+        
456
+        # import public keys
457
+        _import_user_keys(gitea_api, keys, user)
458
+
459
+
460
+def _import_user_keys(gitea_api: pygitea, keys: [gitlab.v4.objects.UserKey], user: gitlab.v4.objects.User):
461
+    for key in keys:
462
+        if not user_key_exists(gitea_api, user.username, key.title):
463
+            import_response: requests.Response = gitea_api.post("/admin/users/" + user.username + "/keys", json={
464
+                "key": key.key,
465
+                "read_only": True,
466
+                "title": key.title,
467
+            })
468
+            if import_response.ok:
469
+                print_info("Public key " + key.title + " imported!")
470
+            else:
471
+                print_error("Public key " + key.title + " import failed: " + import_response.text)
472
+
473
+
474
+def _import_groups(gitea_api: pygitea, groups: [gitlab.v4.objects.Group]):
475
+    for group in groups:
476
+        members: [gitlab.v4.objects.GroupMember] = group.members.list(all=True)
477
+
478
+        print("Importing group " + group.name + "...")
479
+        print("Found " + str(len(members)) + " gitlab members for group " + group.name)
480
+
481
+        if not organization_exists(gitea_api, group.name):
482
+            import_response: requests.Response = gitea_api.post("/orgs", json={
483
+                "description": group.description,
484
+                "full_name": group.full_name,
485
+                "location": "",
486
+                "username": group.name,
487
+                "website": ""
488
+            })
489
+            if import_response.ok:
490
+                print_info("Group " + group.name + " imported!")
491
+            else:
492
+                print_error("Group " + group.name + " import failed: " + import_response.text)
493
+
494
+        # import group members
495
+        _import_group_members(gitea_api, members, group)
496
+
497
+
498
+def _import_group_members(gitea_api: pygitea, members: [gitlab.v4.objects.GroupMember], group: gitlab.v4.objects.Group):
499
+    # TODO: create teams based on gitlab permissions (access_level of group member)
500
+    existing_teams = get_teams(gitea_api, group.name)
501
+    if existing_teams:
502
+        first_team = existing_teams[0]
503
+        print("Organization teams fetched, importing users to first team: " + first_team['name'])
504
+
505
+        # add members to teams
506
+        for member in members:
507
+            if not member_exists(gitea_api, member.username, first_team['id']):
508
+                import_response: requests.Response = gitea_api.put("/teams/" + str(first_team['id']) + "/members/" + member.username)
509
+                if import_response.ok:
510
+                    print_info("Member " + member.username + " added to group " + group.name + "!")
511
+                else:
512
+                    print_error("Failed to add member " + member.username + " to group " + group.name + "!")
513
+    else:
514
+        print_error("Failed to import members to group " + group.name + ": no teams found!")
515
+
516
+
517
+#
518
+# Import functions
519
+#
520
+
521
+def import_users_groups(gitlab_api: gitlab.Gitlab, gitea_api: pygitea, notify=False):
522
+    # read all users
523
+    users: [gitlab.v4.objects.User] = gitlab_api.users.list(all=True)
524
+    groups: [gitlab.v4.objects.Group] = gitlab_api.groups.list(all=True)
525
+
526
+    print("Found " + str(len(users)) + " gitlab users as user " + gitlab_api.user.username)
527
+    print("Found " + str(len(groups)) + " gitlab groups as user " + gitlab_api.user.username)
528
+
529
+    # import all non existing users
530
+    _import_users(gitea_api, users, notify)
531
+
532
+    # import all non existing groups
533
+    _import_groups(gitea_api, groups)
534
+
535
+
536
+def import_projects(gitlab_api: gitlab.Gitlab, gitea_api: pygitea):
537
+    # read all projects and their issues
538
+    projects: gitlab.v4.objects.Project = gitlab_api.projects.list(all=True)
539
+
540
+    print("Found " + str(len(projects)) + " gitlab projects as user " + gitlab_api.user.username)
541
+
542
+    for project in projects:
543
+        collaborators: [gitlab.v4.objects.ProjectMember] = project.members.list(all=True)
544
+        labels: [gitlab.v4.objects.ProjectLabel] = project.labels.list(all=True)
545
+        milestones: [gitlab.v4.objects.ProjectMilestone] = project.milestones.list(all=True)
546
+        issues: [gitlab.v4.objects.ProjectIssue] = project.issues.list(all=True)
547
+
548
+        print("Importing project " + project.name + " from owner " + project.namespace['name'])
549
+        print("Found " + str(len(collaborators)) + " collaborators for project " + project.name)
550
+        print("Found " + str(len(labels)) + " labels for project " + project.name)
551
+        print("Found " + str(len(milestones)) + " milestones for project " + project.name)
552
+        print("Found " + str(len(issues)) + " issues for project " + project.name)
553
+
554
+        # import project repo
555
+        _import_project_repo(gitea_api, project)
556
+
557
+        # import collaborators
558
+        _import_project_repo_collaborators(gitea_api, collaborators, project)
559
+
560
+        # import labels
561
+        _import_project_labels(gitea_api, labels, project.namespace['name'], project.name)
562
+
563
+        # import milestones
564
+        _import_project_milestones(gitea_api, milestones, project.namespace['name'], project.name)
565
+
566
+        # import issues
567
+        _import_project_issues(gitea_api, issues, project.namespace['name'], project.name)
568
+
569
+
570
+#
571
+# Helper functions
572
+#
573
+
574
+class bcolors:
575
+    HEADER = '\033[95m'
576
+    OKBLUE = '\033[94m'
577
+    OKGREEN = '\033[92m'
578
+    WARNING = '\033[93m'
579
+    FAIL = '\033[91m'
580
+    ENDC = '\033[0m'
581
+    BOLD = '\033[1m'
582
+    UNDERLINE = '\033[4m'
583
+
584
+
585
+def color_message(color, message, colorend=bcolors.ENDC, bold=False):
586
+    if bold:
587
+        return bcolors.BOLD + color_message(color, message, colorend, False)
588
+
589
+    return color + message + colorend
590
+
591
+def print_color(color, message, colorend=bcolors.ENDC, bold=False):
592
+    print(color_message(color, message, colorend))
593
+
594
+
595
+def print_info(message):
596
+    print_color(bcolors.OKBLUE, message)
597
+
598
+
599
+def print_success(message):
600
+    print_color(bcolors.OKGREEN, message)
601
+
602
+
603
+def print_warning(message):
604
+    print_color(bcolors.WARNING, message)
605
+
606
+
607
+def print_error(message):
608
+    global GLOBAL_ERROR_COUNT
609
+    GLOBAL_ERROR_COUNT += 1
610
+    print_color(bcolors.FAIL, message)
611
+
612
+
613
+if __name__ == "__main__":
614
+    main()

Loading…
Cancel
Save