mirror of
https://github.com/go-i2p/gitlab-to-gitea.git
synced 2025-06-08 10:32:04 -04:00
273 lines
7.9 KiB
Go
273 lines
7.9 KiB
Go
// manager.go
|
|
|
|
// Package migration handles the migration of data from GitLab to Gitea
|
|
package migration
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/go-i2p/gitlab-to-gitea/utils"
|
|
|
|
"github.com/go-i2p/gitlab-to-gitea/config"
|
|
"github.com/go-i2p/gitlab-to-gitea/gitea"
|
|
"github.com/go-i2p/gitlab-to-gitea/gitlab"
|
|
|
|
gogitlab "github.com/xanzy/go-gitlab"
|
|
)
|
|
|
|
// Manager handles the migration process
|
|
type Manager struct {
|
|
gitlabClient *gitlab.Client
|
|
giteaClient *gitea.Client
|
|
config *config.Config
|
|
state *State
|
|
}
|
|
|
|
func FileExists(filename string) bool {
|
|
_, err := os.Stat(filename)
|
|
return !os.IsNotExist(err)
|
|
}
|
|
|
|
// NewManager creates a new migration manager
|
|
func NewManager(gitlabClient *gitlab.Client, giteaClient *gitea.Client, cfg *config.Config) *Manager {
|
|
// Initialize state
|
|
state := NewState(cfg.MigrationStateFile)
|
|
if FileExists(cfg.MigrationStateFile) && cfg.ResumeMigration {
|
|
utils.PrintInfo("Resuming previous migration...")
|
|
if err := state.Load(); err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Could not load migration state: %v. Starting new migration.", err))
|
|
}
|
|
} else {
|
|
utils.PrintInfo("Starting new migration...")
|
|
if err := state.Reset(); err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Could not reset migration state: %v", err))
|
|
}
|
|
utils.PrintInfo("Migration state reset.")
|
|
}
|
|
utils.PrintInfo("Migration state initialized.")
|
|
|
|
return &Manager{
|
|
gitlabClient: gitlabClient,
|
|
giteaClient: giteaClient,
|
|
config: cfg,
|
|
state: state,
|
|
}
|
|
}
|
|
|
|
// ImportUsersGroups imports users and groups from GitLab to Gitea
|
|
func (m *Manager) ImportUsersGroups() error {
|
|
utils.PrintInfo("Fetching users from GitLab...")
|
|
// Get GitLab users
|
|
users, err := m.gitlabClient.ListUsers()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list GitLab users: %w", err)
|
|
}
|
|
utils.PrintInfo(fmt.Sprintf("Found %d GitLab users", len(users)))
|
|
|
|
utils.PrintInfo("Fetching groups from GitLab...")
|
|
// Get GitLab groups
|
|
groups, err := m.gitlabClient.ListGroups()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list GitLab groups: %w", err)
|
|
}
|
|
utils.PrintInfo(fmt.Sprintf("Found %d GitLab groups", len(groups)))
|
|
|
|
utils.PrintHeader("Importing users")
|
|
// Import users
|
|
for _, user := range users {
|
|
utils.PrintInfo(fmt.Sprintf("Importing user %s...", user.Username))
|
|
if m.config.ResumeMigration && m.state.HasImportedUser(user.Username) {
|
|
utils.PrintWarning(fmt.Sprintf("User %s already imported, skipping!", user.Username))
|
|
continue
|
|
}
|
|
|
|
if err := m.ImportUser(user, false); err != nil {
|
|
utils.PrintError(fmt.Sprintf("Failed to import user %s: %v", user.Username, err))
|
|
continue
|
|
}
|
|
|
|
m.state.MarkUserImported(user.Username)
|
|
if err := m.state.Save(); err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Failed to save migration state: %v", err))
|
|
}
|
|
utils.PrintSuccess(fmt.Sprintf("Imported user %s.", user.Username))
|
|
}
|
|
|
|
utils.PrintHeader("Importing groups")
|
|
// Import groups
|
|
for _, group := range groups {
|
|
cleanName := utils.CleanName(group.Name)
|
|
utils.PrintInfo(fmt.Sprintf("Importing group: %s...", cleanName))
|
|
if m.config.ResumeMigration && m.state.HasImportedGroup(cleanName) {
|
|
utils.PrintWarning(fmt.Sprintf("Group %s already imported, skipping!", cleanName))
|
|
continue
|
|
}
|
|
|
|
if err := m.ImportGroup(group); err != nil {
|
|
utils.PrintError(fmt.Sprintf("Failed to import group %s: %v", group.Name, err))
|
|
continue
|
|
}
|
|
|
|
m.state.MarkGroupImported(cleanName)
|
|
if err := m.state.Save(); err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Failed to save migration state: %v", err))
|
|
}
|
|
utils.PrintSuccess(fmt.Sprintf("Imported group: %s.", cleanName))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ImportProjects imports projects from GitLab to Gitea
|
|
func (m *Manager) ImportProjects() error {
|
|
// Get GitLab projects
|
|
projects, err := m.gitlabClient.ListProjects()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list GitLab projects: %w", err)
|
|
}
|
|
utils.PrintInfo(fmt.Sprintf("Found %d GitLab projects", len(projects)))
|
|
|
|
// Import projects
|
|
utils.PrintInfo("Pre-creating all necessary users for project migration...")
|
|
|
|
// Create a set of all usernames and namespaces that need to exist
|
|
requiredUsers := m.collectRequiredUsers(projects)
|
|
|
|
// Create any missing users
|
|
utils.PrintInfo(fmt.Sprintf("Found %d users that need to exist in Gitea", len(requiredUsers)))
|
|
for username := range requiredUsers {
|
|
exists, err := m.userExists(utils.NormalizeUsername(username))
|
|
if err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Error checking if user exists: %v", err))
|
|
continue
|
|
}
|
|
|
|
if !exists {
|
|
if err := m.ImportPlaceholderUser(username); err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Failed to create placeholder user: %v", err))
|
|
}
|
|
}
|
|
}
|
|
|
|
utils.PrintInfo("Starting project migration...")
|
|
|
|
// Import projects
|
|
for _, project := range projects {
|
|
projectKey := fmt.Sprintf("%s/%s", project.Namespace.Name, utils.CleanName(project.Name))
|
|
|
|
// Skip if project was already fully imported
|
|
if m.config.ResumeMigration && m.state.HasImportedProject(projectKey) {
|
|
utils.PrintWarning(fmt.Sprintf("Project %s already imported, skipping!", projectKey))
|
|
continue
|
|
}
|
|
|
|
// Import project
|
|
if err := m.ImportProject(project); err != nil {
|
|
utils.PrintError(fmt.Sprintf("Failed to import project %s: %v", project.Name, err))
|
|
continue
|
|
}
|
|
|
|
m.state.MarkProjectImported(projectKey)
|
|
if err := m.state.Save(); err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Failed to save migration state: %v", err))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// collectRequiredUsers builds a set of usernames that need to exist before project migration
|
|
func (m *Manager) collectRequiredUsers(projects []*gogitlab.Project) map[string]struct{} {
|
|
required := make(map[string]struct{})
|
|
|
|
utils.PrintHeader("Collecting required users for project migration")
|
|
|
|
// Helper function to add a user to the required map if not already present
|
|
addUser := func(username string) {
|
|
if username == "" {
|
|
return
|
|
}
|
|
if _, exists := required[username]; !exists {
|
|
required[username] = struct{}{}
|
|
utils.PrintInfo(fmt.Sprintf("Adding required user: %s", username))
|
|
}
|
|
}
|
|
|
|
// Collect users from projects
|
|
for _, project := range projects {
|
|
utils.PrintInfo(fmt.Sprintf("Collecting users for project %s...", project.Name))
|
|
|
|
// Add project namespace/owner if it's a user
|
|
if project.Namespace.Kind == "user" {
|
|
addUser(project.Namespace.Path)
|
|
}
|
|
|
|
// Collect project members
|
|
members, err := m.gitlabClient.GetProjectMembers(project.ID)
|
|
if err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Error collecting members for %s: %v", project.Name, err))
|
|
continue
|
|
}
|
|
|
|
for _, member := range members {
|
|
addUser(member.Username)
|
|
}
|
|
|
|
// Collect issues and related users
|
|
issues, err := m.gitlabClient.GetProjectIssues(project.ID)
|
|
if err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Error collecting issues for %s: %v", project.Name, err))
|
|
continue
|
|
}
|
|
|
|
for _, issue := range issues {
|
|
// Add issue author
|
|
if issue.Author != nil {
|
|
addUser(issue.Author.Username)
|
|
}
|
|
|
|
// Add issue assignees
|
|
if issue.Assignee != nil {
|
|
addUser(issue.Assignee.Username)
|
|
}
|
|
|
|
for _, assignee := range issue.Assignees {
|
|
addUser(assignee.Username)
|
|
}
|
|
|
|
// Process issue notes/comments for authors
|
|
notes, err := m.gitlabClient.GetIssueNotes(project.ID, issue.IID)
|
|
if err != nil {
|
|
utils.PrintWarning(fmt.Sprintf("Error collecting notes for issue #%d: %v", issue.IID, err))
|
|
continue
|
|
}
|
|
|
|
for _, note := range notes {
|
|
if !note.System && note.Author.ID != 0 {
|
|
addUser(note.Author.Username)
|
|
}
|
|
}
|
|
|
|
// Extract mentioned users from issue description
|
|
/*for _, mention := range utils.ExtractUserMentions(issue.Description) {
|
|
addUser(mention)
|
|
}
|
|
|
|
// Extract mentioned users from notes
|
|
for _, note := range notes {
|
|
if !note.System {
|
|
for _, mention := range utils.ExtractUserMentions(note.Body) {
|
|
addUser(mention)
|
|
}
|
|
}
|
|
}*/
|
|
}
|
|
|
|
// Milestones don't have authors
|
|
}
|
|
|
|
utils.PrintInfo(fmt.Sprintf("Collected a total of %d unique required users", len(required)))
|
|
return required
|
|
}
|