diff --git a/database/db.go b/database/db.go index 44aa327..5f631da 100644 --- a/database/db.go +++ b/database/db.go @@ -99,6 +99,14 @@ func (db *DB) migrate() error { return err } } + // Incremental migrations — ignore "duplicate column" errors for idempotency. + alterStmts := []string{ + "ALTER TABLE repositories ADD COLUMN auth_type VARCHAR(20) NOT NULL DEFAULT 'none'", + "ALTER TABLE repositories ADD COLUMN credential TEXT", + } + for _, s := range alterStmts { + db.conn.Exec(s) // ignore error (column already exists) + } return nil } diff --git a/handlers/repos.go b/handlers/repos.go index a5a38c8..4549dac 100644 --- a/handlers/repos.go +++ b/handlers/repos.go @@ -149,8 +149,9 @@ func (h *ReposHandler) PullRepo(c *gin.Context) { } id := c.Param("id") - var localPath string - err := h.db.QueryRow(`SELECT local_path FROM repositories WHERE id = ? AND user_id = ?`, id, user.ID).Scan(&localPath) + var localPath, authType string + var credential sql.NullString + err := h.db.QueryRow(`SELECT local_path, auth_type, credential FROM repositories WHERE id = ? AND user_id = ?`, id, user.ID).Scan(&localPath, &authType, &credential) if err == sql.ErrNoRows { c.JSON(http.StatusNotFound, gin.H{"error": "repository not found"}) return @@ -160,7 +161,19 @@ func (h *ReposHandler) PullRepo(c *gin.Context) { return } - result, err := services.PullRepo(localPath) + // Parse stored credentials + var username, password string + if authType == "basic" && credential.Valid { + var cred struct { + Username string `json:"username"` + Password string `json:"password"` + } + json.Unmarshal([]byte(credential.String), &cred) + username = cred.Username + password = cred.Password + } + + result, err := services.PullRepo(localPath, username, password) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -238,10 +251,23 @@ func (h *ReposHandler) CloneRepo(c *gin.Context) { return } + // Determine auth type and credential for persistence + authType := "none" + var credential *string + if req.Username != "" || req.Password != "" { + authType = "basic" + credJSON, _ := json.Marshal(map[string]string{ + "username": req.Username, + "password": req.Password, + }) + s := string(credJSON) + credential = &s + } + // Save to database with user_id now := time.Now().Format("2006-01-02 15:04:05") - res, err := h.db.Exec(`INSERT INTO repositories (user_id, url, local_path, size_bytes, cloned_at, last_used) VALUES (?, ?, ?, ?, ?, ?)`, - user.ID, req.URL, repoDir, result.SizeBytes, now, now) + res, err := h.db.Exec(`INSERT INTO repositories (user_id, url, local_path, size_bytes, cloned_at, last_used, auth_type, credential) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + user.ID, req.URL, repoDir, result.SizeBytes, now, now, authType, credential) if err != nil { sendEvent("error", map[string]interface{}{"message": "save to db: " + err.Error()}) return diff --git a/models/repository.go b/models/repository.go index 141a21c..1dfb701 100644 --- a/models/repository.go +++ b/models/repository.go @@ -3,10 +3,12 @@ package models import "time" type Repository struct { - ID int64 `json:"id"` - URL string `json:"url"` - LocalPath string `json:"local_path"` - SizeBytes int64 `json:"size_bytes"` - ClonedAt time.Time `json:"cloned_at"` - LastUsed time.Time `json:"last_used"` + ID int64 `json:"id"` + URL string `json:"url"` + LocalPath string `json:"local_path"` + SizeBytes int64 `json:"size_bytes"` + ClonedAt time.Time `json:"cloned_at"` + LastUsed time.Time `json:"last_used"` + AuthType string `json:"auth_type"` + Credential string `json:"-"` // never expose in JSON responses } diff --git a/services/git.go b/services/git.go index 56f40a4..484297b 100644 --- a/services/git.go +++ b/services/git.go @@ -442,7 +442,8 @@ func GetBranchCommits(repoPath, branchName string, maxCommits int) ([]CommitInfo } // PullRepo fetches and merges latest changes from origin into the current branch. -func PullRepo(repoPath string) (*CloneResult, error) { +// username and password are optional; when non-empty they authenticate the pull. +func PullRepo(repoPath, username, password string) (*CloneResult, error) { repo, err := OpenRepo(repoPath) if err != nil { return nil, err @@ -453,7 +454,15 @@ func PullRepo(repoPath string) (*CloneResult, error) { return nil, fmt.Errorf("worktree: %w", err) } - err = w.Pull(&git.PullOptions{}) + pullOpts := &git.PullOptions{} + if username != "" { + pullOpts.Auth = &http.BasicAuth{ + Username: username, + Password: password, + } + } + + err = w.Pull(pullOpts) if err != nil && err != git.NoErrAlreadyUpToDate { return nil, fmt.Errorf("git pull: %w", err) } diff --git a/templates/pages/index.html b/templates/pages/index.html index 914c463..6a4a240 100644 --- a/templates/pages/index.html +++ b/templates/pages/index.html @@ -14,13 +14,31 @@