extend timeout

This commit is contained in:
eyedeekay
2025-04-20 23:55:35 -04:00
parent c4c5aafbcf
commit d5445172d9
7 changed files with 144 additions and 60 deletions

5
.gitignore vendored
View File

@ -24,4 +24,7 @@ go.work.sum
# env file
.env
/migrate
/unmigrate
/unmigrate
/migration_state.json
log.log
err.log

View File

@ -44,7 +44,7 @@ func main() {
// Get current user (admin)
var currentUser map[string]interface{}
err = giteaClient.Get("/user", &currentUser)
err = giteaClient.Get("/api/v1/user", &currentUser)
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
}

View File

@ -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 {

View File

@ -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
}

View File

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

View File

@ -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)
}
}

View File

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