From a243eae91acddd60264cdf0789c4cc72ebae7073 Mon Sep 17 00:00:00 2001 From: Scott Alfter Date: Thu, 23 Oct 2025 09:21:23 -0700 Subject: [PATCH] initial commit --- .gitignore | 1 + README.md | 57 +++++++++++++++++++++++++++++++++++ config.sh | 15 +++++++++ delete-gitea-projects.sh | 17 +++++++++++ get-gitlab-container-repos.sh | 13 ++++++++ import.sh | 28 +++++++++++++++++ list-gitlab-projects.sh | 9 ++++++ 7 files changed, 140 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.sh create mode 100755 delete-gitea-projects.sh create mode 100644 get-gitlab-container-repos.sh create mode 100755 import.sh create mode 100755 list-gitlab-projects.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b25c15b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..4942212 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +GitLab-to-Gitea Import Tools +============================ + +I knocked these together while migrating from a self-hosted GitLab CE +instance to a self-hosted Gitea instance. It should also prove useful for +migrating from gitlab.com. It is aimed mainly at batch-migrating +repositories for a single user, but admins could extend it to migrate +repositories for all users. + +config.sh +--------- + +Set your GitLab and Gitea URLs, usernames, and access tokens here. + +list-gitlab-projects.sh +----------------------- + +This pulls a list of projects owned by the account associated with the +access token. Remove "&owned=true" for all projects on the instance...this +might be useful on a self-hosted GitLab, but probably should be left in +place for gitlab.com. :) + +You might find it useful to filter the output to get just the repo names: + +```./list-gitlab-projects.sh | sed "s/.*\///;s/\.git//" >repos.txt``` + +import.sh +--------- + +This fires off a request through the Gitea API to migrate a repository from +GitLab. It takes two arguments: + +* repo name +* private flag (will import as a private repo unless this is set to + ```false```) + +You might want to call this from a script that batches up all your imports +together. + +get-gitlab-container-repos.sh +----------------------------- + +This pulls a list of container image tags within your repositories. Once +you have this list, it's trivial to script up a ```docker image +pull```/```docker image push``` loop to transfer the images. + +This script requires [jq](https://github.com/jqlang/jq) for some JSON +filtering. Your Linux distro probably has it. (Arch does, at least.) + +delete-gitea-projects.sh +------------------------ + +This is supposed to remove all projects in your Gitea account, but I found +that it might need to be run several times before it completely clears out +your account. It worked well enough for me to not have to wipe out the +entire Gitea instance and start over. Depending on your circumstances, you +might not need it. diff --git a/config.sh b/config.sh new file mode 100644 index 0000000..2607bc2 --- /dev/null +++ b/config.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +# GitLab +GL_DOMAIN="https://gitlab.alfter.us" +GL_CR_DOMAIN="https://cr.gitlab.alfter.us" +GL_USER="salfter" +GL_TOKEN="glpat-PUT-YOUR-GITLAB-PERSONAL-ACCESS-TOKEN-HERE" +# a token with read-only access is sufficient + +# Gitea +GT_DOMAIN="https://git.alfter.us" +# Gitea serves container images on the same domain, so GT_CR_DOMAIN isn't needed +GT_USER="salfter" +GT_TOKEN="PUT-YOUR-GITEA-ACCESS-TOKEN-HERE" +# a token with read/write access is required diff --git a/delete-gitea-projects.sh b/delete-gitea-projects.sh new file mode 100755 index 0000000..5954359 --- /dev/null +++ b/delete-gitea-projects.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# BUG: need to repeat until all repos are gone...it's not getting all of them + +source config.sh + +for ((i=1; ; i+=1)); do + contents=$(curl -sX GET "${GT_DOMAIN}/api/v1/user/repos?limit=30&page=${i}" -H "Accept: application/json" -H "Authorization: token ${GT_TOKEN}") + if [ "$(echo $contents | jq -e '. | length == 0')" == "true" ]; then + break + fi + for repo in $(echo "$contents" | jq -r '.[].name') + do + echo $repo + curl -sX DELETE "${GT_DOMAIN}/api/v1/repos/${GT_USER}/${repo}" -H "Accept: application/json" -H "Authorization: token ${GT_TOKEN}" + done +done diff --git a/get-gitlab-container-repos.sh b/get-gitlab-container-repos.sh new file mode 100644 index 0000000..ad184d1 --- /dev/null +++ b/get-gitlab-container-repos.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +source config.sh + +SCOPE="registry:catalog:*" + +token=$(curl -s --request GET \ + --user "${GL_USER}:${GL_TOKEN}" \ + --url "${GL_DOMAIN}/jwt/auth?service=container_registry&scope=${SCOPE}" | jq -r .token) + +curl --header "Authorization: Bearer $token" \ + --url "${GL_CR_DOMAIN}/v2/_catalog" + \ No newline at end of file diff --git a/import.sh b/import.sh new file mode 100755 index 0000000..b251e99 --- /dev/null +++ b/import.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +source config.sh + +if [ "$2" != "false" ] +then + private=true +else + private=false +fi + +curl -sX 'POST' \ + "${GT_DOMAIN}/api/v1/repos/migrate" \ + -H 'accept: application/json' \ + -H 'Authorization: token '${GT_TOKEN}'' \ + -H 'Content-Type: application/json' \ + -d '{ + "auth_token": "'${GL_TOKEN}'", + "clone_addr": "'${GL_DOMAIN}'/'${GL_USER}'/'$1'", + "mirror": false, + "private": '$private', + "pull_requests": true, + "releases": true, + "repo_name": "'$1'", + "repo_owner": "'${GT_USER}'", + "service": "gitlab", + "wiki": true +}' | jq -r '.id' diff --git a/list-gitlab-projects.sh b/list-gitlab-projects.sh new file mode 100755 index 0000000..969b135 --- /dev/null +++ b/list-gitlab-projects.sh @@ -0,0 +1,9 @@ +#!/bin/bash +source config.sh +for ((i=1; ; i+=1)); do + contents=$(curl -s "$GL_DOMAIN/api/v4/projects?private_token=$GL_TOKEN&per_page=100&owned=true&page=$i") + if jq -e '. | length == 0' >/dev/null; then + break + fi <<< "$contents" + echo "$contents" | jq -r '.[].ssh_url_to_repo' +done