668 lines
17 KiB
Go
668 lines
17 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"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"`
|
|
Amend bool `json:"amend"`
|
|
}
|
|
|
|
// GitRenameBranchRequest for POST /api/v1/git/rename-branch
|
|
type GitRenameBranchRequest struct {
|
|
NewName string `json:"new_name"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// GitRevertCommitRequest for POST /api/v1/git/revert-commit
|
|
type GitRevertCommitRequest struct {
|
|
CommitSHA string `json:"commit_sha"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// GitResetRequest for POST /api/v1/git/reset
|
|
type GitResetRequest struct {
|
|
CommitSHA string `json:"commit_sha"`
|
|
Mode string `json:"mode"` // "soft", "mixed", or "hard"
|
|
}
|
|
|
|
// GitLogRequest for GET /api/v1/git/log
|
|
type GitLogRequest struct {
|
|
Since string `form:"since"` // Base commit SHA (exclusive)
|
|
Limit int `form:"limit"` // Max commits to return
|
|
}
|
|
|
|
// GitCumulativeDiffRequest for GET /api/v1/git/cumulative-diff
|
|
type GitCumulativeDiffRequest struct {
|
|
Base string `form:"base" binding:"required"` // Base commit SHA
|
|
}
|
|
|
|
// 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, req.Amend)
|
|
if err != nil {
|
|
s.handleGitError(c, "commit", err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// handleGitRenameBranch handles POST /api/v1/git/rename-branch
|
|
func (s *Server) handleGitRenameBranch(c *gin.Context) {
|
|
var req GitRenameBranchRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "rename_branch",
|
|
Error: "invalid request: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.NewName == "" {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "rename_branch",
|
|
Error: "new_name is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
gitOp := s.procMgr.GitOperator()
|
|
result, err := gitOp.RenameBranch(c.Request.Context(), req.NewName)
|
|
if err != nil {
|
|
s.handleGitError(c, "rename_branch", 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)
|
|
}
|
|
|
|
// handleGitRevertCommit handles POST /api/v1/git/revert-commit
|
|
func (s *Server) handleGitRevertCommit(c *gin.Context) {
|
|
var req GitRevertCommitRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "revert_commit",
|
|
Error: "invalid request: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.CommitSHA == "" {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "revert_commit",
|
|
Error: "commit_sha is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
gitOp := s.procMgr.GitOperator()
|
|
result, err := gitOp.RevertCommit(c.Request.Context(), req.CommitSHA)
|
|
if err != nil {
|
|
s.handleGitError(c, "revert_commit", err)
|
|
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)
|
|
}
|
|
|
|
// handleGitReset handles POST /api/v1/git/reset
|
|
func (s *Server) handleGitReset(c *gin.Context) {
|
|
var req GitResetRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "reset",
|
|
Error: "invalid request: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.CommitSHA == "" {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "reset",
|
|
Error: "commit_sha is required",
|
|
})
|
|
return
|
|
}
|
|
|
|
if req.Mode == "" {
|
|
req.Mode = "mixed"
|
|
}
|
|
validModes := map[string]bool{"soft": true, "mixed": true, "hard": true}
|
|
if !validModes[req.Mode] {
|
|
c.JSON(http.StatusBadRequest, process.GitOperationResult{
|
|
Success: false,
|
|
Operation: "reset",
|
|
Error: fmt.Sprintf("invalid reset mode: %s (must be soft, mixed, or hard)", req.Mode),
|
|
})
|
|
return
|
|
}
|
|
|
|
gitOp := s.procMgr.GitOperator()
|
|
result, err := gitOp.Reset(c.Request.Context(), req.CommitSHA, req.Mode)
|
|
if err != nil {
|
|
s.handleGitError(c, "reset", err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// handleGitLog handles GET /api/v1/git/log
|
|
func (s *Server) handleGitLog(c *gin.Context) {
|
|
var req GitLogRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, process.GitLogResult{
|
|
Success: false,
|
|
Error: "invalid request: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Bound limit to prevent expensive history scans
|
|
const (
|
|
defaultLogLimit = 100
|
|
maxLogLimit = 500
|
|
)
|
|
limit := req.Limit
|
|
if limit <= 0 {
|
|
limit = defaultLogLimit
|
|
} else if limit > maxLogLimit {
|
|
c.JSON(http.StatusBadRequest, process.GitLogResult{
|
|
Success: false,
|
|
Error: fmt.Sprintf("limit must be between 1 and %d", maxLogLimit),
|
|
})
|
|
return
|
|
}
|
|
|
|
gitOp := s.procMgr.GitOperator()
|
|
result, err := gitOp.GetLog(c.Request.Context(), req.Since, limit)
|
|
if err != nil {
|
|
s.logger.Error("git log failed", zap.Error(err))
|
|
c.JSON(http.StatusInternalServerError, process.GitLogResult{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// handleGitCumulativeDiff handles GET /api/v1/git/cumulative-diff
|
|
func (s *Server) handleGitCumulativeDiff(c *gin.Context) {
|
|
var req GitCumulativeDiffRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, process.CumulativeDiffResult{
|
|
Success: false,
|
|
Error: "invalid request: " + err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
gitOp := s.procMgr.GitOperator()
|
|
result, err := gitOp.GetCumulativeDiff(c.Request.Context(), req.Base)
|
|
if err != nil {
|
|
s.logger.Error("git cumulative diff failed", zap.String("base", req.Base), zap.Error(err))
|
|
c.JSON(http.StatusInternalServerError, process.CumulativeDiffResult{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, result)
|
|
}
|
|
|
|
// GitStatusResult represents the result of a git status query.
|
|
type GitStatusResult struct {
|
|
Success bool `json:"success"`
|
|
Branch string `json:"branch"`
|
|
RemoteBranch string `json:"remote_branch"`
|
|
HeadCommit string `json:"head_commit"`
|
|
BaseCommit string `json:"base_commit"` // Merge-base with origin branch
|
|
Ahead int `json:"ahead"`
|
|
Behind int `json:"behind"`
|
|
Modified []string `json:"modified"`
|
|
Added []string `json:"added"`
|
|
Deleted []string `json:"deleted"`
|
|
Untracked []string `json:"untracked"`
|
|
Renamed []string `json:"renamed"`
|
|
Files map[string]interface{} `json:"files"`
|
|
Timestamp string `json:"timestamp"`
|
|
BranchAdditions int `json:"branch_additions,omitempty"`
|
|
BranchDeletions int `json:"branch_deletions,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// handleGitStatus handles GET /api/v1/git/status
|
|
func (s *Server) handleGitStatus(c *gin.Context) {
|
|
wt := s.procMgr.GetWorkspaceTracker()
|
|
if wt == nil {
|
|
c.JSON(http.StatusInternalServerError, GitStatusResult{
|
|
Success: false,
|
|
Error: "workspace tracker not available",
|
|
})
|
|
return
|
|
}
|
|
|
|
status, err := wt.GetCurrentGitStatus(c.Request.Context())
|
|
if err != nil {
|
|
s.logger.Error("git status failed", zap.Error(err))
|
|
c.JSON(http.StatusInternalServerError, GitStatusResult{
|
|
Success: false,
|
|
Error: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Convert Files map to interface{} map for JSON serialization
|
|
filesMap := make(map[string]interface{}, len(status.Files))
|
|
for k, v := range status.Files {
|
|
filesMap[k] = v
|
|
}
|
|
|
|
c.JSON(http.StatusOK, GitStatusResult{
|
|
Success: true,
|
|
Branch: status.Branch,
|
|
RemoteBranch: status.RemoteBranch,
|
|
HeadCommit: status.HeadCommit,
|
|
BaseCommit: status.BaseCommit,
|
|
Ahead: status.Ahead,
|
|
Behind: status.Behind,
|
|
Modified: status.Modified,
|
|
Added: status.Added,
|
|
Deleted: status.Deleted,
|
|
Untracked: status.Untracked,
|
|
Renamed: status.Renamed,
|
|
Files: filesMap,
|
|
Timestamp: status.Timestamp.Format("2006-01-02T15:04:05.000Z07:00"),
|
|
BranchAdditions: status.BranchAdditions,
|
|
BranchDeletions: status.BranchDeletions,
|
|
})
|
|
}
|
|
|
|
// 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(),
|
|
})
|
|
}
|