Overview
Gitea provides a comprehensive migration system that allows you to import repositories from external Git hosting platforms. The migration process can transfer not only the Git repository itself, but also issues, pull requests, labels, milestones, releases, and other metadata.
Gitea supports migrating from the following platforms:
| Platform | Code | Issues | PRs | Releases | Labels | Milestones | Wiki |
|---|
| GitHub | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| GitLab | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Gitea | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Gogs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| OneDev | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| GitBucket | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Codebase | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| CodeCommit | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| Plain Git | ✓ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |
Reference: modules/structs/repo.go:325-336
Migration Options
When migrating a repository, you can configure the following options:
type MigrateOptions struct {
CloneAddr string // Source repository URL
AuthUsername string // Authentication username
AuthPassword string // Authentication password
AuthToken string // Authentication token
RepoName string // Destination repository name
Mirror bool // Create as mirror repository
LFS bool // Migrate LFS objects
LFSEndpoint string // Custom LFS endpoint
Private bool // Make repository private
Description string // Repository description
Wiki bool // Migrate wiki
Issues bool // Migrate issues
Milestones bool // Migrate milestones
Labels bool // Migrate labels
Releases bool // Migrate releases
Comments bool // Migrate comments
PullRequests bool // Migrate pull requests
ReleaseAssets bool // Migrate release assets
}
Reference: modules/migration/options.go:9-44
Migrating from GitHub
Go to GitHub Settings > Developer settings > Personal access tokens
Click Generate new token (classic)
Select scopes:
repo (all) - For private repositories
public_repo - For public repositories only
Generate and copy the token
Click the + icon in the top navigation
Select New Migration
Choose GitHub as the source
Clone Address: GitHub repository URL (e.g., https://github.com/user/repo)
Auth Token: Paste your GitHub personal access token
Owner: Select the organization or user that will own the repository
Repository Name: Choose a name for the migrated repository
Git data (always included)
Issues
Pull requests
Labels
Milestones
Releases
Wiki
Comments
Click Migrate Repository to start the process. Large repositories may take several minutes.
Migrating from GitLab
Go to GitLab User Settings > Access Tokens
Create a new token with these scopes:
Copy the token
In Gitea, start a new migration
Select GitLab as the source
Enter the GitLab repository URL
Paste your access token
Select the items to migrate and click Migrate Repository.
Migrating from Bitbucket
Bitbucket repositories can be migrated as plain Git repositories:
Go to your Bitbucket repository
Click Clone and copy the HTTPS URL
Go to Bitbucket Settings > App passwords
Create a new app password with repository:read permission
Copy the password
Start a new migration in Gitea
Select Git as the source (plain Git migration)
Enter the Bitbucket clone URL
Enter your Bitbucket username and app password
Configure repository settings
Click Migrate Repository
Plain Git migrations only transfer the Git repository and wiki. Issues, pull requests, and other metadata are not migrated.
Authentication Methods
Different platforms support different authentication methods:
Token Authentication
Supported platforms: GitHub, GitLab, Gitea
func (gt GitServiceType) TokenAuth() bool {
switch gt {
case GithubService, GiteaService, GitlabService:
return true
}
return false
}
Reference: modules/structs/repo.go:405-411
Username and Password
Supported by all platforms, but tokens are recommended when available.
SSH Keys
For plain Git migrations, you can use SSH URLs with configured SSH keys.
Migration Process
The migration process follows these steps:
Gitea validates the source URL and credentials:
func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error {
// Validate URL format
// Check against allow/block lists
// Verify permissions
}
Reference: services/migrations/migrate.go:44-87
Gitea creates the destination repository and prepares for data import.
The Git repository is cloned:
git clone --mirror <source-url> <destination>
Reference: services/migrations/migrate.go:221-226
If the source platform supports it, Gitea migrates:
Topics - Repository topics/tags
Milestones - Project milestones with due dates
Labels - Issue and PR labels with colors
Releases - Release tags with descriptions and assets
Issues - Issues with comments, labels, assignees
Pull Requests - PRs with reviews, comments, and status
Comments - All comments on issues and PRs
Reference: services/migrations/migrate.go:228-510
The migration is finalized and the repository becomes available.
URL Restrictions
For security, Gitea restricts which URLs can be migrated from:
Allowed Domains
Configure allowed domains in app.ini:
[migrations]
ALLOWED_DOMAINS = github.com,gitlab.com,bitbucket.org
ALLOW_LOCALNETWORKS = false
By default, only external hosts are allowed. Internal/local network addresses are blocked.
Reference: services/migrations/migrate.go:514-532
Blocked Domains
Block specific domains:
[migrations]
BLOCKED_DOMAINS = internal.company.com,sensitive.domain.com
Local File System
Administrators can migrate from local file system paths:
if u.Scheme == "file" || u.Scheme == "" {
if !doer.CanImportLocal() {
return &git.ErrInvalidCloneAddr{
Host: "<LOCAL_FILESYSTEM>",
IsPermissionDenied: true,
LocalPath: true
}
}
}
Reference: services/migrations/migrate.go:51-68
LFS Migration
Gitea can migrate Git LFS (Large File Storage) objects:
Enable LFS migration
Check the Migrate LFS option when creating the migration.
Custom LFS endpoint
If the source repository uses a custom LFS server:
if opts.LFS && len(opts.LFSEndpoint) > 0 {
err := IsMigrateURLAllowed(opts.LFSEndpoint, doer)
if err != nil {
return nil, err
}
}
Reference: services/migrations/migrate.go:116-121
LFS authentication
LFS migration uses the same authentication credentials as the Git repository.
Mirror Repositories
You can create a mirror instead of a one-time migration:
Pull mirrors
- Periodically sync from source repository
- Read-only on Gitea (pushes go to upstream)
- Configured during migration by checking This repository will be a mirror
Mirror interval
Set how often the mirror syncs:
[repository]
MIRROR_INTERVAL = 8h # Default sync interval
Or specify per-repository:
type MigrateOptions struct {
MirrorInterval string // e.g., "8h", "30m", "1h"
}
Reference: modules/migration/options.go:40
Retry and Error Handling
Migrations support automatic retries for transient failures:
if setting.Migrations.MaxAttempts > 1 {
downloader = base.NewRetryDownloader(
downloader,
setting.Migrations.MaxAttempts,
setting.Migrations.RetryBackoff,
)
}
Reference: services/migrations/migrate.go:170-173
Configure in app.ini:
[migrations]
MAX_ATTEMPTS = 3
RETRY_BACKOFF = 3s
Batch Size Configuration
For large repositories, Gitea processes data in batches:
// Milestones
msBatchSize := uploader.MaxBatchInsertSize("milestone")
// Labels
lbBatchSize := uploader.MaxBatchInsertSize("label")
// Issues
issueBatchSize := uploader.MaxBatchInsertSize("issue")
// Pull requests
prBatchSize := uploader.MaxBatchInsertSize("pullrequest")
Reference: services/migrations/migrate.go:253-395
This prevents memory issues and database timeouts on large migrations.
Migrating via API
You can trigger migrations programmatically using the Gitea API:
curl -X POST "https://gitea.example.com/api/v1/repos/migrate" \
-H "Authorization: token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"clone_addr": "https://github.com/user/repo.git",
"auth_token": "github_token",
"repo_owner": "gitea_user",
"repo_name": "migrated-repo",
"service": "github",
"mirror": false,
"private": true,
"issues": true,
"pull_requests": true,
"releases": true,
"milestones": true,
"labels": true,
"wiki": true
}'
Reference: modules/structs/repo.go:369-402
Migration Status
During migration, Gitea provides progress updates:
type Messenger interface {
// Send progress message
func(message string)
}
// Usage in migration
messenger("repo.migrate.migrating_git")
messenger("repo.migrate.migrating_topics")
messenger("repo.migrate.migrating_milestones")
messenger("repo.migrate.migrating_labels")
messenger("repo.migrate.migrating_releases")
messenger("repo.migrate.migrating_issues")
messenger("repo.migrate.migrating_pulls")
Reference: services/migrations/migrate.go:179-510
Troubleshooting
Migration Fails with URL Error
Error: Invalid clone address
Solutions:
- Verify the repository URL is correct
- Check that the domain is not in the blocked list
- Ensure the domain is in the allowed list if configured
- For private repositories, verify authentication credentials
Authentication Failures
Error: Authentication failed
Solutions:
- For GitHub/GitLab: Verify token has required scopes
- For username/password: Check credentials are correct
- For SSH: Ensure SSH key is properly configured
- Check if two-factor authentication is blocking access
Partial Migration
Issue: Git repository migrated, but issues/PRs missing
Solutions:
- Verify you selected those items during migration setup
- Check that your token has permission to read issues/PRs
- Review migration logs for specific errors
- Some platforms may rate-limit API requests
Large Repository Timeout
Issue: Migration times out for large repositories
Solutions:
- Increase timeout in
app.ini:
[migrations]
MAX_ATTEMPTS = 5
RETRY_BACKOFF = 10s
- Consider migrating Git data first, then metadata separately
- Use mirror mode for continuous sync instead of one-time migration
LFS Objects Not Migrated
Issue: LFS files show as pointers instead of actual content
Solutions:
- Ensure Migrate LFS was checked during migration
- Verify LFS is enabled in source repository
- Check authentication has access to LFS endpoint
- Confirm LFS endpoint URL if using custom server
Best Practices
-
Test with small repositories first - Verify the migration process works before migrating large or critical repositories
-
Use tokens over passwords - Tokens provide better security and can be scoped to specific permissions
-
Review permissions - Ensure your authentication token has all necessary scopes:
- Repository read access
- Issue read access
- Pull request read access
- Release read access
-
Check rate limits - Some platforms (especially GitHub) have API rate limits. Large migrations may need to be scheduled or split.
-
Verify migrated data - After migration:
- Check that all branches were migrated
- Verify issue and PR counts match
- Test that LFS files downloaded correctly
- Confirm release assets are accessible
-
Plan for downtime - During migration:
- Source repository should not receive new commits
- Coordinate with team to avoid conflicts
- Consider making source repository read-only
-
Update integrations - After migration:
- Update CI/CD pipelines
- Reconfigure webhooks
- Update local git remotes:
git remote set-url origin https://gitea.example.com/user/repo.git
-
Archive source repository - After successful migration and verification:
- Archive the source repository (don’t delete immediately)
- Add a redirect notice in README
- Keep source accessible for 30-90 days
Advanced: Custom Migrations
For platforms not natively supported, you can implement a custom downloader:
type Downloader interface {
GetRepoInfo(ctx context.Context) (*Repository, error)
GetTopics(ctx context.Context) ([]string, error)
GetMilestones(ctx context.Context) ([]*Milestone, error)
GetReleases(ctx context.Context) ([]*Release, error)
GetLabels(ctx context.Context) ([]*Label, error)
GetIssues(ctx context.Context, page, perPage int) ([]*Issue, bool, error)
GetComments(ctx context.Context, commentable Commentable) ([]*Comment, bool, error)
GetPullRequests(ctx context.Context, page, perPage int) ([]*PullRequest, bool, error)
GetReviews(ctx context.Context, reviewable Reviewable) ([]*Review, error)
FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error)
}
Reference: modules/migration/downloader.go:14-27
Implement this interface and register your downloader factory to add support for additional platforms.