mirror of
https://github.com/go-i2p/gitlab-to-gitea.git
synced 2025-06-07 18:24:23 -04:00
check in the script
This commit is contained in:
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
73
README.md
73
README.md
@ -1,2 +1,73 @@
|
||||
# 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"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-i2p/gitlab-to-gitea/utils"
|
||||
)
|
||||
|
||||
// Client handles communication with the Gitea API
|
||||
@ -27,66 +30,26 @@ type VersionResponse struct {
|
||||
}
|
||||
|
||||
// FetchCSRFToken retrieves a CSRF token from Gitea
|
||||
func (c *Client) FetchCSRFToken() error {
|
||||
// Create a request to the Gitea login page to get a CSRF token
|
||||
u, err := c.baseURL.Parse("/")
|
||||
// I don't think it works.
|
||||
func (c *Client) FetchCSRFToken() (string, error) {
|
||||
resp, err := c.request("GET", "/user/login", nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid URL: %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)
|
||||
return "", fmt.Errorf("failed to fetch login page: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Extract CSRF token from Set-Cookie header
|
||||
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)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
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
|
||||
body := string(bodyBytes)
|
||||
csrfMetaStart := strings.Index(body, `<meta name="_csrf" content="`)
|
||||
if csrfMetaStart != -1 {
|
||||
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")
|
||||
}
|
||||
// Use a single approach targeting Gitea's current HTML structure
|
||||
metaTagRegex := regexp.MustCompile(`<meta name="_csrf" content="([^"]+)"`)
|
||||
if matches := metaTagRegex.FindSubmatch(body); len(matches) > 1 {
|
||||
return string(matches[1]), nil
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -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
|
||||
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)
|
||||
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
|
||||
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{}
|
||||
err := m.giteaClient.Post("/admin/users", userReq, &result)
|
||||
if err != nil {
|
||||
// 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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create user %s: %w", user.Username, err)
|
||||
|
Reference in New Issue
Block a user