mirror of
https://github.com/go-i2p/gitlab-to-gitea.git
synced 2025-07-01 09:06:22 -04:00
extend timeout
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -24,4 +24,7 @@ go.work.sum
|
||||
# env file
|
||||
.env
|
||||
/migrate
|
||||
/unmigrate
|
||||
/unmigrate
|
||||
/migration_state.json
|
||||
log.log
|
||||
err.log
|
@ -44,7 +44,7 @@ func main() {
|
||||
|
||||
// Get current user (admin)
|
||||
var currentUser map[string]interface{}
|
||||
err = giteaClient.Get("/user", ¤tUser)
|
||||
err = giteaClient.Get("/api/v1/user", ¤tUser)
|
||||
if err != nil {
|
||||
utils.PrintError(fmt.Sprintf("Failed to get current user: %v", err))
|
||||
os.Exit(1)
|
||||
@ -176,14 +176,13 @@ func (u *Unmigrator) deleteAllOrganizations() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// deleteAllNonAdminUsers deletes all users except the admin in Gitea
|
||||
// deleteAllNonAdminUsers deletes all users except admins and the current user
|
||||
func (u *Unmigrator) deleteAllNonAdminUsers() error {
|
||||
utils.PrintHeader("Deleting users...")
|
||||
|
||||
// Get all users
|
||||
var users []map[string]interface{}
|
||||
err := u.client.Get("/admin/users?limit=1000", &users)
|
||||
err := u.client.Get("admin/users?limit=1000", &users)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get users: %w", err)
|
||||
}
|
||||
@ -212,6 +211,9 @@ func (u *Unmigrator) deleteAllNonAdminUsers() error {
|
||||
utils.PrintInfo(fmt.Sprintf("Found %d users to delete", deleteCount))
|
||||
|
||||
// Delete each non-admin, non-current user
|
||||
deletedCount := 0
|
||||
failedCount := 0
|
||||
|
||||
for _, user := range users {
|
||||
username, ok := user["login"].(string)
|
||||
if !ok {
|
||||
@ -239,21 +241,31 @@ func (u *Unmigrator) deleteAllNonAdminUsers() error {
|
||||
continue
|
||||
}
|
||||
|
||||
userID, ok := user["id"].(float64)
|
||||
if !ok {
|
||||
utils.PrintWarning(fmt.Sprintf("Could not get user ID for %s, skipping", username))
|
||||
continue
|
||||
// Try deleting by username instead of ID
|
||||
utils.PrintInfo(fmt.Sprintf("Deleting user: %s", username))
|
||||
err := u.client.Delete(fmt.Sprintf("admin/users/%s", username))
|
||||
if err != nil {
|
||||
// If that fails, try with the ID as fallback
|
||||
userID, ok := user["id"].(float64)
|
||||
if ok {
|
||||
utils.PrintInfo(fmt.Sprintf("Retrying deletion with ID: %d", int(userID)))
|
||||
err = u.client.Delete(fmt.Sprintf("admin/users/%d", int(userID)))
|
||||
if err != nil {
|
||||
utils.PrintWarning(fmt.Sprintf("Failed to delete user %s: %v", username, err))
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
utils.PrintWarning(fmt.Sprintf("Failed to delete user %s and could not get ID", username))
|
||||
failedCount++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
utils.PrintInfo(fmt.Sprintf("Deleting user: %s (ID: %d)", username, int(userID)))
|
||||
err := u.client.Delete(fmt.Sprintf("/admin/users/%d", int(userID)))
|
||||
if err != nil {
|
||||
utils.PrintWarning(fmt.Sprintf("Failed to delete user %s: %v", username, err))
|
||||
} else {
|
||||
utils.PrintSuccess(fmt.Sprintf("User %s deleted", username))
|
||||
}
|
||||
utils.PrintSuccess(fmt.Sprintf("User %s deleted", username))
|
||||
deletedCount++
|
||||
}
|
||||
|
||||
utils.PrintSuccess("User deletion complete")
|
||||
utils.PrintSuccess(fmt.Sprintf("User deletion complete. Deleted: %d, Failed: %d", deletedCount, failedCount))
|
||||
return nil
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ type SearchResponse struct {
|
||||
// SearchRepositories searches for repositories and returns the results
|
||||
func (c *Client) SearchRepositories() ([]map[string]interface{}, error) {
|
||||
var response SearchResponse
|
||||
err := c.Get("/repos/search?limit=1000", &response)
|
||||
err := c.Get("repos/search?limit=1000", &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -70,24 +70,20 @@ func (c *Client) FetchCSRFToken() (string, error) {
|
||||
}
|
||||
|
||||
func NewClient(baseURL, token string) (*Client, error) {
|
||||
if !strings.HasSuffix(baseURL, "/") {
|
||||
baseURL += "/"
|
||||
}
|
||||
// Remove trailing slash from baseURL if present
|
||||
baseURL = strings.TrimSuffix(baseURL, "/")
|
||||
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
// Create a standard HTTP client without the custom transport
|
||||
httpClient := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
return &Client{
|
||||
baseURL: u,
|
||||
httpClient: httpClient,
|
||||
token: token,
|
||||
baseURL: u,
|
||||
httpClient: &http.Client{
|
||||
Timeout: 360 * time.Second,
|
||||
},
|
||||
token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -153,17 +149,19 @@ func (c *Client) Delete(path string) error {
|
||||
|
||||
// request sends an HTTP request to the Gitea API
|
||||
func (c *Client) request(method, path string, data, result interface{}) (*http.Response, error) {
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/api/v1/" + path
|
||||
// Normalize path - remove leading slash if present
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
|
||||
// Add API prefix if not already present
|
||||
if !strings.HasPrefix(path, "api/v1/") {
|
||||
path = "api/v1/" + path
|
||||
}
|
||||
|
||||
// Construct the full URL without double slashes
|
||||
fullURL := fmt.Sprintf("%s/%s", strings.TrimSuffix(c.baseURL.String(), "/"), path)
|
||||
|
||||
// Debug output to see what endpoint is being called
|
||||
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 {
|
||||
return nil, fmt.Errorf("invalid path: %w", err)
|
||||
}
|
||||
utils.PrintInfo(fmt.Sprintf("Making %s request to: %s", method, fullURL))
|
||||
|
||||
var body io.Reader
|
||||
if data != nil {
|
||||
@ -172,17 +170,14 @@ func (c *Client) request(method, path string, data, result interface{}) (*http.R
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
body = bytes.NewBuffer(jsonData)
|
||||
|
||||
// Debug output for request body
|
||||
// fmt.Printf("Request body: %s\n", string(jsonData))
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), body)
|
||||
req, err := http.NewRequest(method, fullURL, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Set headers - simplified approach with just basic auth headers
|
||||
// Set headers
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", fmt.Sprintf("token %s", c.token))
|
||||
@ -193,12 +188,11 @@ func (c *Client) request(method, path string, data, result interface{}) (*http.R
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// For debugging: print the raw response
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
//fmt.Printf("Response: Status=%s, Body=%s\n", resp.Status, string(bodyBytes))
|
||||
|
||||
// Since we've read the body, we need to create a new reader for further processing
|
||||
//resp.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
// Read the body
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
// Handle error status codes
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
|
@ -23,18 +23,31 @@ func (m *Manager) importProjectCollaborators(
|
||||
) error {
|
||||
ownerInfo, err := m.getOwner(project)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get owner info: %w", err)
|
||||
utils.PrintWarning(fmt.Sprintf("Failed to get owner info for %s: %v, skipping collaborators", project.Name, err))
|
||||
return nil // Return nil instead of error to continue with migration
|
||||
}
|
||||
|
||||
// Safely extract username and type with defaults if missing
|
||||
ownerUsername, ok := ownerInfo["username"].(string)
|
||||
if !ok || ownerUsername == "" {
|
||||
utils.PrintWarning(fmt.Sprintf("Owner username missing for %s, skipping collaborators", project.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Safely extract owner type with default
|
||||
ownerType, _ := ownerInfo["type"].(string)
|
||||
if ownerType == "" {
|
||||
ownerType = "user" // Default to user type
|
||||
}
|
||||
|
||||
ownerUsername := ownerInfo["username"].(string)
|
||||
ownerType := ownerInfo["type"].(string)
|
||||
repoName := utils.CleanName(project.Name)
|
||||
|
||||
for _, collaborator := range collaborators {
|
||||
cleanUsername := utils.NormalizeUsername(collaborator.Username)
|
||||
|
||||
// Skip if the collaborator is the owner
|
||||
if ownerType == "user" && ownerUsername == cleanUsername {
|
||||
if cleanUsername == "" {
|
||||
utils.PrintWarning("Empty username for collaborator, skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -131,18 +131,36 @@ func (m *Manager) getOwner(project *gitlab.Project) (map[string]interface{}, err
|
||||
// Try to get as a user first
|
||||
var result map[string]interface{}
|
||||
err := m.giteaClient.Get("/users/"+namespacePath, &result)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
if err == nil && result != nil {
|
||||
// Verify required fields exist
|
||||
if username, ok := result["username"].(string); ok && username != "" {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get as an organization
|
||||
orgName := utils.CleanName(project.Namespace.Name)
|
||||
err = m.giteaClient.Get("/orgs/"+orgName, &result)
|
||||
if err == nil {
|
||||
if err == nil && result != nil {
|
||||
// Verify required fields exist
|
||||
if username, ok := result["username"].(string); ok && username != "" {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Create a placeholder user instead of failing
|
||||
utils.PrintWarning(fmt.Sprintf("Could not find owner for project %s, creating placeholder user", project.Name))
|
||||
if err := m.ImportPlaceholderUser(namespacePath); err != nil {
|
||||
return nil, fmt.Errorf("failed to create placeholder user: %w", err)
|
||||
}
|
||||
|
||||
// Try to get the newly created user
|
||||
err = m.giteaClient.Get("/users/"+namespacePath, &result)
|
||||
if err == nil && result != nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find owner for project: %w", err)
|
||||
return nil, fmt.Errorf("failed to find or create owner for project: %s", project.Path)
|
||||
}
|
||||
|
||||
// repoExists checks if a repository exists in Gitea
|
||||
|
@ -75,6 +75,7 @@ func (s *State) Save() error {
|
||||
// Reset clears the migration state
|
||||
func (s *State) Reset() error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
utils.PrintInfo("Clearing migration state...")
|
||||
|
||||
s.Users = []string{}
|
||||
@ -105,7 +106,16 @@ func (s *State) MarkUserImported(username string) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if !s.HasImportedUser(username) {
|
||||
// Check directly without calling HasImportedUser
|
||||
alreadyImported := false
|
||||
for _, u := range s.Users {
|
||||
if u == username {
|
||||
alreadyImported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyImported {
|
||||
s.Users = append(s.Users, username)
|
||||
}
|
||||
}
|
||||
@ -128,7 +138,16 @@ func (s *State) MarkGroupImported(group string) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if !s.HasImportedGroup(group) {
|
||||
// Check directly without calling HasImportedGroup
|
||||
alreadyImported := false
|
||||
for _, g := range s.Groups {
|
||||
if g == group {
|
||||
alreadyImported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyImported {
|
||||
s.Groups = append(s.Groups, group)
|
||||
}
|
||||
}
|
||||
@ -146,12 +165,20 @@ func (s *State) HasImportedProject(project string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MarkProjectImported marks a project as imported
|
||||
func (s *State) MarkProjectImported(project string) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if !s.HasImportedProject(project) {
|
||||
// Check directly without calling HasImportedProject
|
||||
alreadyImported := false
|
||||
for _, p := range s.Projects {
|
||||
if p == project {
|
||||
alreadyImported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyImported {
|
||||
s.Projects = append(s.Projects, project)
|
||||
}
|
||||
}
|
||||
@ -183,7 +210,19 @@ func (s *State) MarkCommentImported(issueKey, commentID string) {
|
||||
s.ImportedComments[issueKey] = []string{}
|
||||
}
|
||||
|
||||
if !s.HasImportedComment(issueKey, commentID) {
|
||||
// Check directly without calling HasImportedComment
|
||||
alreadyImported := false
|
||||
comments, exists := s.ImportedComments[issueKey]
|
||||
if exists {
|
||||
for _, id := range comments {
|
||||
if id == commentID {
|
||||
alreadyImported = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !alreadyImported {
|
||||
s.ImportedComments[issueKey] = append(s.ImportedComments[issueKey], commentID)
|
||||
}
|
||||
}
|
||||
|
@ -74,16 +74,21 @@ func (m *Manager) ImportUser(user *gitlab.User, notify bool) error {
|
||||
|
||||
utils.PrintInfo(fmt.Sprintf("User %s created as %s, temporary password: %s", user.Username, cleanUsername, tmpPassword))
|
||||
|
||||
utils.PrintHeader("Importing SSH keys...")
|
||||
// Import user's SSH keys
|
||||
keys, err := m.gitlabClient.GetUserKeys(user.ID)
|
||||
if err != nil {
|
||||
utils.PrintWarning(fmt.Sprintf("Failed to fetch keys for user %s: %v", user.Username, err))
|
||||
} else {
|
||||
utils.PrintInfo(fmt.Sprintf("Found %d keys for user %s", len(keys), user.Username))
|
||||
for _, key := range keys {
|
||||
utils.PrintInfo(fmt.Sprintf("Importing key %s for user %s", key.Title, cleanUsername))
|
||||
if err := m.importUserKey(cleanUsername, key); err != nil {
|
||||
utils.PrintWarning(fmt.Sprintf("Failed to import key for user %s: %v", user.Username, err))
|
||||
}
|
||||
utils.PrintInfo(fmt.Sprintf("Key %s imported for user %s", key.Title, cleanUsername))
|
||||
}
|
||||
utils.PrintSuccess(fmt.Sprintf("Imported %d keys for user %s", len(keys), cleanUsername))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
Reference in New Issue
Block a user