package gitea import ( "context" "encoding/json" "fmt" "net/url" ) type FileContents struct { Path string `json:"path"` Sha string `json:"sha"` Size int64 `json:"size"` Content string `json:"content"` Encoding string `json:"encoding"` } func (c *Client) GetFileContents(ctx context.Context, owner, repo, path, ref string) (*FileContents, error) { p := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, path) if ref != "" { p += "?ref=" + ref } body, status, err := c.GetJSON(ctx, p) if err != nil { return nil, err } if err := MapStatus(status, body); err != nil { return nil, err } // Array response means path is a directory — guide caller to dir_list. if len(body) > 0 && body[0] == '[' { return nil, fmt.Errorf("%w: path %q is a directory, not a file — use dir_list", ErrValidation, path) } var fc FileContents if err := json.Unmarshal(body, &fc); err != nil { return nil, err } return &fc, nil } type Branch struct { Name string `json:"name"` Commit struct { ID string `json:"id"` URL string `json:"url"` } `json:"commit"` } // BranchExists returns (true, nil) if the branch exists, (false, nil) on 404, (false, err) otherwise. func (c *Client) BranchExists(ctx context.Context, owner, repo, branch string) (bool, error) { p := fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", owner, repo, branch) body, status, err := c.GetJSON(ctx, p) if err != nil { return false, err } if status == 404 { return false, nil } if err := MapStatus(status, body); err != nil { return false, err } return true, nil } func (c *Client) CreateBranch(ctx context.Context, owner, repo, newBranch, oldBranch string) error { p := fmt.Sprintf("/api/v1/repos/%s/%s/branches", owner, repo) payload, err := json.Marshal(map[string]string{ "new_branch_name": newBranch, "old_branch_name": oldBranch, }) if err != nil { return err } body, status, err := c.PostJSON(ctx, p, payload) if err != nil { return err } return MapStatus(status, body) } type UpsertFileArgs struct { Branch string `json:"branch"` Content string `json:"content"` // already base64-encoded Message string `json:"message"` Sha string `json:"sha,omitempty"` } type FileWriteResult struct { Content struct { Path string `json:"path"` Sha string `json:"sha"` HTMLURL string `json:"html_url"` } `json:"content"` Commit struct { Sha string `json:"sha"` HTMLURL string `json:"html_url"` } `json:"commit"` } func (c *Client) ListBranches(ctx context.Context, owner, repo string, page, limit int) ([]Branch, error) { if page < 1 { page = 1 } if limit < 1 { limit = 30 } p := fmt.Sprintf("/api/v1/repos/%s/%s/branches?page=%d&limit=%d", owner, repo, page, limit) body, status, err := c.GetJSON(ctx, p) if err != nil { return nil, err } if err := MapStatus(status, body); err != nil { return nil, err } var branches []Branch if err := json.Unmarshal(body, &branches); err != nil { return nil, err } return branches, nil } func (c *Client) DeleteBranch(ctx context.Context, owner, repo, branch string) error { p := fmt.Sprintf("/api/v1/repos/%s/%s/branches/%s", owner, repo, branch) body, status, err := c.DeleteJSON(ctx, p) if err != nil { return err } return MapStatus(status, body) } type BranchProtection struct { Protected bool `json:"-"` RequiredApprovals int64 `json:"required_approvals"` PushWhitelist []string `json:"push_whitelist_usernames"` MergeWhitelist []string `json:"merge_whitelist_usernames"` } func (c *Client) GetBranchProtection(ctx context.Context, owner, repo, branch string) (*BranchProtection, error) { p := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, branch) body, status, err := c.GetJSON(ctx, p) if err != nil { return nil, err } if status == 404 { return &BranchProtection{Protected: false}, nil } if err := MapStatus(status, body); err != nil { return nil, err } var bp BranchProtection if err := json.Unmarshal(body, &bp); err != nil { return nil, err } bp.Protected = true return &bp, nil } type DirEntry struct { Name string `json:"name"` Path string `json:"path"` Type string `json:"type"` Sha string `json:"sha"` Size int64 `json:"size"` } func (c *Client) ListContents(ctx context.Context, owner, repo, path, ref string) ([]DirEntry, error) { p := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, path) if ref != "" { p += "?ref=" + url.QueryEscape(ref) } body, status, err := c.GetJSON(ctx, p) if err != nil { return nil, err } if err := MapStatus(status, body); err != nil { return nil, err } if len(body) > 0 && body[0] == '{' { return nil, fmt.Errorf("path is a file, not a directory — use file_read: %w", ErrValidation) } var entries []DirEntry if err := json.Unmarshal(body, &entries); err != nil { return nil, err } return entries, nil } type DeleteFileArgs struct { Branch string `json:"branch"` Message string `json:"message"` Sha string `json:"sha"` } func (c *Client) DeleteFile(ctx context.Context, owner, repo, path string, args DeleteFileArgs) (*FileWriteResult, error) { p := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, path) payload, err := json.Marshal(args) if err != nil { return nil, err } body, status, err := c.DeleteJSONBody(ctx, p, payload) if err != nil { return nil, err } if err := MapStatus(status, body); err != nil { return nil, err } var out FileWriteResult if err := json.Unmarshal(body, &out); err != nil { return nil, err } return &out, nil } // UpsertFile creates a file when args.Sha is empty (POST) or updates an existing // file when args.Sha is set (PUT). Gitea routes both operations by HTTP method on // the same /contents/{path} URL, and rejects PUT without a sha. func (c *Client) UpsertFile(ctx context.Context, owner, repo, path string, args UpsertFileArgs) (*FileWriteResult, error) { p := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, path) payload, err := json.Marshal(args) if err != nil { return nil, err } var ( body []byte status int ) if args.Sha == "" { body, status, err = c.PostJSON(ctx, p, payload) } else { body, status, err = c.PutJSON(ctx, p, payload) } if err != nil { return nil, err } if err := MapStatus(status, body); err != nil { return nil, err } var out FileWriteResult if err := json.Unmarshal(body, &out); err != nil { return nil, err } return &out, nil }