Skip to main content

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.

Supported Platforms

Gitea supports migrating from the following platforms:
PlatformCodeIssuesPRsReleasesLabelsMilestonesWiki
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

1
Create a GitHub token
2
  • 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
  • 3
    Start migration in Gitea
    4
  • Click the + icon in the top navigation
  • Select New Migration
  • Choose GitHub as the source
  • 5
    Configure migration settings
    6
  • 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
  • 7
    Select migration items
    8
    Choose what to migrate:
    9
  • Git data (always included)
  • Issues
  • Pull requests
  • Labels
  • Milestones
  • Releases
  • Wiki
  • Comments
  • 10
    Complete migration
    11
    Click Migrate Repository to start the process. Large repositories may take several minutes.

    Migrating from GitLab

    1
    Create a GitLab token
    2
  • Go to GitLab User Settings > Access Tokens
  • Create a new token with these scopes:
    • read_api
    • read_repository
  • Copy the token
  • 3
    Configure migration
    4
  • In Gitea, start a new migration
  • Select GitLab as the source
  • Enter the GitLab repository URL
  • Paste your access token
  • 5
    Complete migration
    6
    Select the items to migrate and click Migrate Repository.

    Migrating from Bitbucket

    Bitbucket repositories can be migrated as plain Git repositories:
    1
    Get repository URL
    2
  • Go to your Bitbucket repository
  • Click Clone and copy the HTTPS URL
  • 3
    Create an app password
    4
  • Go to Bitbucket Settings > App passwords
  • Create a new app password with repository:read permission
  • Copy the password
  • 5
    Migrate to Gitea
    6
  • 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:
    1
    Validation
    2
    Gitea validates the source URL and credentials:
    3
    func IsMigrateURLAllowed(remoteURL string, doer *user_model.User) error {
        // Validate URL format
        // Check against allow/block lists
        // Verify permissions
    }
    
    4
    Reference: services/migrations/migrate.go:44-87
    5
    Repository creation
    6
    Gitea creates the destination repository and prepares for data import.
    7
    Git data migration
    8
    The Git repository is cloned:
    9
    git clone --mirror <source-url> <destination>
    
    10
    Reference: services/migrations/migrate.go:221-226
    11
    Metadata migration
    12
    If the source platform supports it, Gitea migrates:
    13
  • 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
  • 14
    Reference: services/migrations/migrate.go:228-510
    15
    Finalization
    16
    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

    1. Test with small repositories first - Verify the migration process works before migrating large or critical repositories
    2. Use tokens over passwords - Tokens provide better security and can be scoped to specific permissions
    3. Review permissions - Ensure your authentication token has all necessary scopes:
      • Repository read access
      • Issue read access
      • Pull request read access
      • Release read access
    4. Check rate limits - Some platforms (especially GitHub) have API rate limits. Large migrations may need to be scheduled or split.
    5. 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
    6. Plan for downtime - During migration:
      • Source repository should not receive new commits
      • Coordinate with team to avoid conflicts
      • Consider making source repository read-only
    7. 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
        
    8. 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.

    Build docs developers (and LLMs) love