Skip to content

Instantly share code, notes, and snippets.

@bjeanes
Last active October 26, 2020 22:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjeanes/c8610fe1842cb401d8e34ba7159adae1 to your computer and use it in GitHub Desktop.
Save bjeanes/c8610fe1842cb401d8e34ba7159adae1 to your computer and use it in GitHub Desktop.
scrappy script to migrate Gitlab->Gitea
#!/usr/bin/env ruby
## NOTE: sometimes (probably because GL can't handle load) the repo is created but the import/migrate fails.
## go to <gitea>/admin/repos?sort=size to find 0-byte repos, delete them, and try again. Unfortunately,
## deleting these repos (either because of that page or because the repo isn't fully initialised) doesn't
## clean up on disk, so I had to manually `rm -rf` them too.
require 'json'
require 'uri'
require 'net/https'
require 'pry-byebug'
GITLAB_TOKEN = 'REPLACE ME WITH ADMIN + READ_REPO TOKEN'
GITEA_TOKEN = 'REPLACE ME'
GITLAB_HEADER = {'Authorization' => "Bearer #{GITLAB_TOKEN}"}
GITEA_HEADER = {
'Accept' => 'application/json',
'Authorization' => "token #{GITEA_TOKEN}",
'Content-Type' => 'application/json',
}
$gitea = Net::HTTP.new('git.bjeanes.com', 443)
$gitea.use_ssl = true
$gitlab = Net::HTTP.new('gitlab.bjeanes.com', 443)
$gitlab.use_ssl = true
# hacky "cache" to skip migrated ones when re-runnign script, due to GitLab's incredibly slow API
$migrated_file = File.open('./migrated.txt', 'a+')
$migrated = File.readlines('./migrated.txt').map(&:chomp)
def each_gitlab_repo(&block)
path = '/api/v4/projects?pagination=keyset&per_page=10&order_by=id&sort=asc'
loop do
resp = $gitlab.get(path, GITLAB_HEADER)
data = JSON.parse(resp.body)
data.each(&block)
next_path = Array(resp.to_hash['link']).grep(/rel="next"/)[0].to_s.scan(/<(.*?)>/).flatten.first
return unless next_path
path = URI.parse(next_path).request_uri
end
end
def create_gitea_org(name)
$created_orgs ||= []
return true if $created_orgs.include?(name)
resp = $gitea.post("/api/v1/orgs", JSON.generate({visibility: :private, username: name}), GITEA_HEADER)
if resp.code == "201" || resp.body.match("user already exists")
puts "Created org #{name}" if resp.code == 201
$created_orgs << name
else
$migrated_file.close
raise resp.body
end
end
def migrate(repo)
return if $migrated.include?(repo["path_with_namespace"])
owner, repo_name = repo["path_with_namespace"].split('/', 2)
# Everything under this account on my GL is a stale mirror of GH account.
# I will pull these into Gitea separately so they stay as up-to-date mirrors
if owner == "bjeanes"
return
else
create_gitea_org(owner)
end
# Gitea doesn't support nested namespaces, unlike GitLab :(
# See https://github.com/go-gitea/gitea/issues/1872
repo_name = repo_name.gsub('/','__')
is_private = repo["visibility"] == "private"
resp = $gitea.post("/api/v1/repos/migrate", JSON.generate({
service: "gitlab",
private: is_private,
clone_addr: repo["http_url_to_repo"],
repo_owner: owner,
repo_name: repo_name,
auth_token: GITLAB_TOKEN,
mirror: false,
issues: false,
labels: false,
milestones: false,
releases: false,
pull_requests: false,
wiki: false,
}), GITEA_HEADER)
if resp.code == "201"
puts "Repo #{owner}/#{repo_name} created and queued for import :)"
elsif resp.code == "409" && resp.body.match("Files already exist for this repository")
# I had to fix these manually by `rm -rf` the files under Gitea
puts "Gitea bug? Repo doesn't exist but can't be created"
return
elsif resp.code == "409" && resp.body.match("repository with the same name already exists.")
# Assume we already imported this so just skip
puts "Repo #{owner}/#{repo_name} already exists"
else
$migrated_file.close
raise resp.body
end
$migrated_file.puts repo["path_with_namespace"]
$migrated_file.flush
# Un-watch repo
$gitea.delete("/api/v1/repos/#{owner}/#{repo_name}/subscription", GITEA_HEADER)
end
each_gitlab_repo { |r|
next if r['path'] =~ /gitlab/
migrate(r)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment