Files
kandev/apps/backend/internal/agentctl/server/api/git.go
T
2026-02-03 04:37:49 +00:00

399 lines
9.8 KiB
Go

package api
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/kandev/kandev/internal/agentctl/server/process"
"go.uber.org/zap"
)
// GitPullRequest for POST /api/v1/git/pull
type GitPullRequest struct {
Rebase bool `json:"rebase"`
}
// GitPushRequest for POST /api/v1/git/push
type GitPushRequest struct {
Force bool `json:"force"`
SetUpstream bool `json:"set_upstream"`
}
// GitRebaseRequest for POST /api/v1/git/rebase
type GitRebaseRequest struct {
BaseBranch string `json:"base_branch"`
}
// GitMergeRequest for POST /api/v1/git/merge
type GitMergeRequest struct {
BaseBranch string `json:"base_branch"`
}
// GitAbortRequest for POST /api/v1/git/abort
type GitAbortRequest struct {
Operation string `json:"operation"` // "merge" or "rebase"
}
// GitCommitRequest for POST /api/v1/git/commit
type GitCommitRequest struct {
Message string `json:"message"`
StageAll bool `json:"stage_all"`
}
// GitStageRequest for POST /api/v1/git/stage
type GitStageRequest struct {
Paths []string `json:"paths"` // Empty = stage all
}
// GitUnstageRequest for POST /api/v1/git/unstage
type GitUnstageRequest struct {
Paths []string `json:"paths"` // Empty = unstage all
}
// GitDiscardRequest for POST /api/v1/git/discard
type GitDiscardRequest struct {
Paths []string `json:"paths"` // Required - files to discard
}
// GitShowCommitRequest for GET /api/v1/git/commit/:sha
type GitShowCommitRequest struct {
CommitSHA string `uri:"sha" binding:"required"`
}
// GitCreatePRRequest for POST /api/v1/git/create-pr
type GitCreatePRRequest struct {
Title string `json:"title"`
Body string `json:"body"`
BaseBranch string `json:"base_branch"`
Draft bool `json:"draft"`
}
// handleGitPull handles POST /api/v1/git/pull
func (s *Server) handleGitPull(c *gin.Context) {
var req GitPullRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "pull",
Error: "invalid request: " + err.Error(),
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Pull(c.Request.Context(), req.Rebase)
if err != nil {
s.handleGitError(c, "pull", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitPush handles POST /api/v1/git/push
func (s *Server) handleGitPush(c *gin.Context) {
var req GitPushRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "push",
Error: "invalid request: " + err.Error(),
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Push(c.Request.Context(), req.Force, req.SetUpstream)
if err != nil {
s.handleGitError(c, "push", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitRebase handles POST /api/v1/git/rebase
func (s *Server) handleGitRebase(c *gin.Context) {
var req GitRebaseRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "rebase",
Error: "invalid request: " + err.Error(),
})
return
}
if req.BaseBranch == "" {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "rebase",
Error: "base_branch is required",
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Rebase(c.Request.Context(), req.BaseBranch)
if err != nil {
s.handleGitError(c, "rebase", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitMerge handles POST /api/v1/git/merge
func (s *Server) handleGitMerge(c *gin.Context) {
var req GitMergeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "merge",
Error: "invalid request: " + err.Error(),
})
return
}
if req.BaseBranch == "" {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "merge",
Error: "base_branch is required",
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Merge(c.Request.Context(), req.BaseBranch)
if err != nil {
s.handleGitError(c, "merge", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitAbort handles POST /api/v1/git/abort
func (s *Server) handleGitAbort(c *gin.Context) {
var req GitAbortRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "abort",
Error: "invalid request: " + err.Error(),
})
return
}
if req.Operation != "merge" && req.Operation != "rebase" {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "abort",
Error: "operation must be 'merge' or 'rebase'",
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Abort(c.Request.Context(), req.Operation)
if err != nil {
s.handleGitError(c, "abort", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitCommit handles POST /api/v1/git/commit
func (s *Server) handleGitCommit(c *gin.Context) {
var req GitCommitRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "commit",
Error: "invalid request: " + err.Error(),
})
return
}
if req.Message == "" {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "commit",
Error: "message is required",
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Commit(c.Request.Context(), req.Message, req.StageAll)
if err != nil {
s.handleGitError(c, "commit", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitStage handles POST /api/v1/git/stage
func (s *Server) handleGitStage(c *gin.Context) {
var req GitStageRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "stage",
Error: "invalid request: " + err.Error(),
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Stage(c.Request.Context(), req.Paths)
if err != nil {
s.handleGitError(c, "stage", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitUnstage handles POST /api/v1/git/unstage
func (s *Server) handleGitUnstage(c *gin.Context) {
var req GitUnstageRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "unstage",
Error: "invalid request: " + err.Error(),
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Unstage(c.Request.Context(), req.Paths)
if err != nil {
s.handleGitError(c, "unstage", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitDiscard handles POST /api/v1/git/discard
func (s *Server) handleGitDiscard(c *gin.Context) {
var req GitDiscardRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "discard",
Error: "invalid request: " + err.Error(),
})
return
}
if len(req.Paths) == 0 {
c.JSON(http.StatusBadRequest, process.GitOperationResult{
Success: false,
Operation: "discard",
Error: "paths are required",
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.Discard(c.Request.Context(), req.Paths)
if err != nil {
s.handleGitError(c, "discard", err)
return
}
c.JSON(http.StatusOK, result)
}
// handleGitCreatePR handles POST /api/v1/git/create-pr
func (s *Server) handleGitCreatePR(c *gin.Context) {
var req GitCreatePRRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, process.PRCreateResult{
Success: false,
Error: "invalid request: " + err.Error(),
})
return
}
if req.Title == "" {
c.JSON(http.StatusBadRequest, process.PRCreateResult{
Success: false,
Error: "title is required",
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.CreatePR(c.Request.Context(), req.Title, req.Body, req.BaseBranch, req.Draft)
if err != nil {
if errors.Is(err, process.ErrOperationInProgress) {
c.JSON(http.StatusConflict, process.PRCreateResult{
Success: false,
Error: "another git operation is already in progress",
})
return
}
s.logger.Error("git create-pr failed", zap.Error(err))
c.JSON(http.StatusInternalServerError, process.PRCreateResult{
Success: false,
Error: err.Error(),
})
return
}
c.JSON(http.StatusOK, result)
}
// handleGitShowCommit handles GET /api/v1/git/commit/:sha
func (s *Server) handleGitShowCommit(c *gin.Context) {
var req GitShowCommitRequest
if err := c.ShouldBindUri(&req); err != nil {
c.JSON(http.StatusBadRequest, process.CommitDiffResult{
Success: false,
Error: "invalid request: " + err.Error(),
})
return
}
gitOp := s.procMgr.GitOperator()
result, err := gitOp.ShowCommit(c.Request.Context(), req.CommitSHA)
if err != nil {
s.logger.Error("git show commit failed", zap.String("commit_sha", req.CommitSHA), zap.Error(err))
c.JSON(http.StatusInternalServerError, process.CommitDiffResult{
Success: false,
CommitSHA: req.CommitSHA,
Error: err.Error(),
})
return
}
c.JSON(http.StatusOK, result)
}
// handleGitError handles errors from git operations.
func (s *Server) handleGitError(c *gin.Context, operation string, err error) {
if errors.Is(err, process.ErrOperationInProgress) {
c.JSON(http.StatusConflict, process.GitOperationResult{
Success: false,
Operation: operation,
Error: "another git operation is already in progress",
})
return
}
s.logger.Error("git operation failed", zap.String("operation", operation), zap.Error(err))
c.JSON(http.StatusInternalServerError, process.GitOperationResult{
Success: false,
Operation: operation,
Error: err.Error(),
})
}