mirror of
https://github.com/go-i2p/gitlab-to-gitea.git
synced 2025-07-01 21:37:23 -04:00
check in the script
This commit is contained in:
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 idk
|
Copyright (c) 2025 idk, go-i2p
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
73
README.md
73
README.md
@ -1,2 +1,73 @@
|
|||||||
# gitlab-to-gitea
|
# gitlab-to-gitea
|
||||||
A port of the old gitlab-to-gitea migration script from python to Go with feature improvements
|
|
||||||
|
Go-based tool for migrating GitLab repositories, users, groups, issues and related data to Gitea instances.
|
||||||
|
|
||||||
|
## Core Functionality
|
||||||
|
|
||||||
|
- Migrates users, groups, and their relationships from GitLab to Gitea
|
||||||
|
- Transfers repositories with labels, milestones, issues, and comments
|
||||||
|
- Preserves user relationships (collaborators) and SSH keys
|
||||||
|
- Supports resumable migrations through state tracking
|
||||||
|
- Handles username normalization and entity mapping between platforms
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Ensure Go 1.24+ is installed
|
||||||
|
2. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/go-i2p/gitlab-to-gitea.git
|
||||||
|
cd gitlab-to-gitea
|
||||||
|
```
|
||||||
|
3. Install dependencies:
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
```
|
||||||
|
4. Build the executable:
|
||||||
|
```bash
|
||||||
|
go build -o gitlab-to-gitea ./cmd/migrate/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
1. Copy the example environment file:
|
||||||
|
```bash
|
||||||
|
cp _env.example .env
|
||||||
|
```
|
||||||
|
2. Edit `.env` with your GitLab and Gitea details:
|
||||||
|
```
|
||||||
|
GITLAB_URL=https://your-gitlab-instance.com
|
||||||
|
GITLAB_TOKEN=your-gitlab-token
|
||||||
|
GITEA_URL=https://your-gitea-instance.com
|
||||||
|
GITEA_TOKEN=your-gitea-token
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Execute the migration tool after configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gitlab-to-gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
The tool will:
|
||||||
|
1. Connect to both GitLab and Gitea instances
|
||||||
|
2. Migrate users and groups first
|
||||||
|
3. Migrate projects with all associated data
|
||||||
|
4. Track progress in `migration_state.json` (resumable if interrupted)
|
||||||
|
|
||||||
|
## Key Dependencies
|
||||||
|
|
||||||
|
- github.com/xanzy/go-gitlab: GitLab API client
|
||||||
|
- github.com/joho/godotenv: Environment variable handling
|
||||||
|
- github.com/go-sql-driver/mysql: Optional database connectivity for action import
|
||||||
|
|
||||||
|
## Optional Features
|
||||||
|
|
||||||
|
For commit action import to Gitea's activity timeline:
|
||||||
|
1. Configure database details in `.env`
|
||||||
|
2. Generate a commit log file
|
||||||
|
3. Use the database import functionality in the `gitea` package
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
@ -10,8 +10,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-i2p/gitlab-to-gitea/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client handles communication with the Gitea API
|
// Client handles communication with the Gitea API
|
||||||
@ -27,66 +30,26 @@ type VersionResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FetchCSRFToken retrieves a CSRF token from Gitea
|
// FetchCSRFToken retrieves a CSRF token from Gitea
|
||||||
func (c *Client) FetchCSRFToken() error {
|
// I don't think it works.
|
||||||
// Create a request to the Gitea login page to get a CSRF token
|
func (c *Client) FetchCSRFToken() (string, error) {
|
||||||
u, err := c.baseURL.Parse("/")
|
resp, err := c.request("GET", "/user/login", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid URL: %w", err)
|
return "", fmt.Errorf("failed to fetch login page: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
// Use a normal HTTP client without our custom transport for this request
|
|
||||||
// to avoid circular dependency (we need the token for the transport)
|
|
||||||
httpClient := &http.Client{}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", u.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := httpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("request failed: %w", err)
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Extract CSRF token from Set-Cookie header
|
body, err := io.ReadAll(resp.Body)
|
||||||
for _, cookie := range resp.Cookies() {
|
|
||||||
if cookie.Name == "_csrf" {
|
|
||||||
// Update the transport with the CSRF token
|
|
||||||
transport, ok := c.httpClient.Transport.(*CSRFTokenTransport)
|
|
||||||
if ok {
|
|
||||||
transport.CSRFToken = cookie.Value
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("transport is not a CSRFTokenTransport")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we couldn't find a CSRF token, try to extract it from the response body
|
|
||||||
bodyBytes, err := io.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read response body: %w", err)
|
return "", fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for <meta name="_csrf" content="..." /> in the HTML
|
// Use a single approach targeting Gitea's current HTML structure
|
||||||
body := string(bodyBytes)
|
metaTagRegex := regexp.MustCompile(`<meta name="_csrf" content="([^"]+)"`)
|
||||||
csrfMetaStart := strings.Index(body, `<meta name="_csrf" content="`)
|
if matches := metaTagRegex.FindSubmatch(body); len(matches) > 1 {
|
||||||
if csrfMetaStart != -1 {
|
return string(matches[1]), nil
|
||||||
csrfMetaStart += len(`<meta name="_csrf" content="`)
|
|
||||||
csrfMetaEnd := strings.Index(body[csrfMetaStart:], `"`)
|
|
||||||
if csrfMetaEnd != -1 {
|
|
||||||
csrfToken := body[csrfMetaStart : csrfMetaStart+csrfMetaEnd]
|
|
||||||
|
|
||||||
transport, ok := c.httpClient.Transport.(*CSRFTokenTransport)
|
|
||||||
if ok {
|
|
||||||
transport.CSRFToken = csrfToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("transport is not a CSRFTokenTransport")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("could not find CSRF token in response")
|
return "", fmt.Errorf("could not find CSRF token in login page")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(baseURL, token string) (*Client, error) {
|
func NewClient(baseURL, token string) (*Client, error) {
|
||||||
@ -178,7 +141,7 @@ func (c *Client) request(method, path string, data, result interface{}) (*http.R
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debug output to see what endpoint is being called
|
// Debug output to see what endpoint is being called
|
||||||
fmt.Printf("Making %s request to: %s%s\n", method, c.baseURL.String(), path)
|
utils.PrintInfo(fmt.Sprintf("Making %s request to: %s%s\n", method, c.baseURL.String(), path))
|
||||||
|
|
||||||
u, err := c.baseURL.Parse(path)
|
u, err := c.baseURL.Parse(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -59,13 +59,13 @@ func (m *Manager) ImportUser(user *gitlab.User, notify bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Debug what endpoint we're calling and with what method
|
// Debug what endpoint we're calling and with what method
|
||||||
fmt.Printf("Attempting to create user via: POST /admin/users\n")
|
utils.PrintInfo("Attempting to create user via: POST /admin/users\n")
|
||||||
|
|
||||||
var result map[string]interface{}
|
var result map[string]interface{}
|
||||||
err := m.giteaClient.Post("/admin/users", userReq, &result)
|
err := m.giteaClient.Post("/admin/users", userReq, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try the alternative user creation endpoint if the first one failed
|
// Try the alternative user creation endpoint if the first one failed
|
||||||
fmt.Printf("First attempt failed, trying alternative endpoint\n")
|
utils.PrintInfo("First attempt failed, trying alternative endpoint\n")
|
||||||
err = m.giteaClient.Post("/api/v1/admin/users", userReq, &result)
|
err = m.giteaClient.Post("/api/v1/admin/users", userReq, &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create user %s: %w", user.Username, err)
|
return fmt.Errorf("failed to create user %s: %w", user.Username, err)
|
||||||
|
Reference in New Issue
Block a user