> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/go-gitea/gitea/llms.txt
> Use this file to discover all available pages before exploring further.

# Repository Migrations

> Migrate repositories from GitHub, GitLab, Bitbucket, and other Git platforms to Gitea

## 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:

| 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:

```go theme={null}
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

<Steps>
  ### Create a GitHub token

  1. Go to GitHub Settings > Developer settings > Personal access tokens
  2. Click **Generate new token (classic)**
  3. Select scopes:
     * `repo` (all) - For private repositories
     * `public_repo` - For public repositories only
  4. Generate and copy the token

  ### Start migration in Gitea

  1. Click the **+** icon in the top navigation
  2. Select **New Migration**
  3. Choose **GitHub** as the source

  ### Configure migration settings

  * **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

  ### Select migration items

  Choose what to migrate:

  * Git data (always included)
  * Issues
  * Pull requests
  * Labels
  * Milestones
  * Releases
  * Wiki
  * Comments

  ### Complete migration

  Click **Migrate Repository** to start the process. Large repositories may take several minutes.
</Steps>

## Migrating from GitLab

<Steps>
  ### Create a GitLab token

  1. Go to GitLab User Settings > Access Tokens
  2. Create a new token with these scopes:
     * `read_api`
     * `read_repository`
  3. Copy the token

  ### Configure migration

  1. In Gitea, start a new migration
  2. Select **GitLab** as the source
  3. Enter the GitLab repository URL
  4. Paste your access token

  ### Complete migration

  Select the items to migrate and click **Migrate Repository**.
</Steps>

## Migrating from Bitbucket

Bitbucket repositories can be migrated as plain Git repositories:

<Steps>
  ### Get repository URL

  1. Go to your Bitbucket repository
  2. Click **Clone** and copy the HTTPS URL

  ### Create an app password

  1. Go to Bitbucket Settings > App passwords
  2. Create a new app password with `repository:read` permission
  3. Copy the password

  ### Migrate to Gitea

  1. Start a new migration in Gitea
  2. Select **Git** as the source (plain Git migration)
  3. Enter the Bitbucket clone URL
  4. Enter your Bitbucket username and app password
  5. Configure repository settings
  6. Click **Migrate Repository**
</Steps>

<Warning>
  Plain Git migrations only transfer the Git repository and wiki. Issues, pull requests, and other metadata are not migrated.
</Warning>

## Authentication Methods

Different platforms support different authentication methods:

### Token Authentication

Supported platforms: GitHub, GitLab, Gitea

```go theme={null}
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:

<Steps>
  ### Validation

  Gitea validates the source URL and credentials:

  ```go theme={null}
  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`

  ### Repository creation

  Gitea creates the destination repository and prepares for data import.

  ### Git data migration

  The Git repository is cloned:

  ```bash theme={null}
  git clone --mirror <source-url> <destination>
  ```

  Reference: `services/migrations/migrate.go:221-226`

  ### Metadata migration

  If the source platform supports it, Gitea migrates:

  1. **Topics** - Repository topics/tags
  2. **Milestones** - Project milestones with due dates
  3. **Labels** - Issue and PR labels with colors
  4. **Releases** - Release tags with descriptions and assets
  5. **Issues** - Issues with comments, labels, assignees
  6. **Pull Requests** - PRs with reviews, comments, and status
  7. **Comments** - All comments on issues and PRs

  Reference: `services/migrations/migrate.go:228-510`

  ### Finalization

  The migration is finalized and the repository becomes available.
</Steps>

## URL Restrictions

For security, Gitea restricts which URLs can be migrated from:

### Allowed Domains

Configure allowed domains in `app.ini`:

```ini theme={null}
[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:

```ini theme={null}
[migrations]
BLOCKED_DOMAINS = internal.company.com,sensitive.domain.com
```

### Local File System

Administrators can migrate from local file system paths:

```go theme={null}
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:

```go theme={null}
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:

```ini theme={null}
[repository]
MIRROR_INTERVAL = 8h  # Default sync interval
```

Or specify per-repository:

```go theme={null}
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:

```go theme={null}
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`:

```ini theme={null}
[migrations]
MAX_ATTEMPTS = 3
RETRY_BACKOFF = 3s
```

## Batch Size Configuration

For large repositories, Gitea processes data in batches:

```go theme={null}
// 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:

```bash theme={null}
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:

```go theme={null}
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`:
  ```ini theme={null}
  [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:
     ```bash theme={null}
     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:

```go theme={null}
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.
