From 4e7b072f82b07d40b6c6945942ee26d83c635fc0 Mon Sep 17 00:00:00 2001 From: agent-company Date: Mon, 20 Apr 2026 15:06:10 +0000 Subject: [PATCH] feat: add GetChangedFiles() method to Gitea client for PR file diffs Add ChangedFile type and GetChangedFiles() method that calls the Gitea API endpoint GET /repos/{owner}/{repo}/pulls/{index}/files to retrieve the list of files changed in a pull request. This is a prerequisite for displaying changed files in the PR detail view (#189). Includes unit tests for success and error cases. Closes leeworks-agents/gitea-mobile#205 Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/gitea/client.go | 27 ++++++++++++++++ internal/gitea/client_test.go | 60 +++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/internal/gitea/client.go b/internal/gitea/client.go index 11fc61c..908e5a1 100644 --- a/internal/gitea/client.go +++ b/internal/gitea/client.go @@ -760,6 +760,33 @@ func (c *Client) GetPull(ctx context.Context, token, owner, repo string, index i return &pr, nil } +// ChangedFile represents a file changed in a pull request. +type ChangedFile struct { + Filename string `json:"filename"` + Status string `json:"status"` // "added", "modified", "removed", "renamed" + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + PreviousFilename string `json:"previous_filename,omitempty"` +} + +// GetChangedFiles fetches the list of files changed in a pull request. +func (c *Client) GetChangedFiles(ctx context.Context, token, owner, repo string, index int64) ([]ChangedFile, error) { + path := fmt.Sprintf("/repos/%s/%s/pulls/%d/files?limit=50", owner, repo, index) + resp, err := c.doRequest(ctx, token, http.MethodGet, path, nil) + if err != nil { + return nil, fmt.Errorf("fetching changed files: %w", err) + } + defer resp.Body.Close() + + var files []ChangedFile + if err := json.NewDecoder(resp.Body).Decode(&files); err != nil { + return nil, fmt.Errorf("decoding changed files: %w", err) + } + + return files, nil +} + // GetIssueComments fetches comments for an issue or pull request. func (c *Client) GetIssueComments(ctx context.Context, token, owner, repo string, index int64) ([]Comment, error) { path := fmt.Sprintf("/repos/%s/%s/issues/%d/comments?limit=50", owner, repo, index) diff --git a/internal/gitea/client_test.go b/internal/gitea/client_test.go index efbea64..87b1196 100644 --- a/internal/gitea/client_test.go +++ b/internal/gitea/client_test.go @@ -1456,3 +1456,63 @@ func TestRetryDelay_ExponentialBackoff(t *testing.T) { } } } + +func TestGetChangedFiles(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("expected GET, got %s", r.Method) + } + if r.URL.Path != "/api/v1/repos/owner1/repo1/pulls/5/files" { + t.Errorf("unexpected path: %s", r.URL.Path) + } + if r.Header.Get("Authorization") != "token test-token" { + t.Error("missing or wrong Authorization header") + } + + files := []ChangedFile{ + {Filename: "main.go", Status: "modified", Additions: 10, Deletions: 3, Changes: 13}, + {Filename: "new_file.go", Status: "added", Additions: 25, Deletions: 0, Changes: 25}, + {Filename: "old_file.go", Status: "removed", Additions: 0, Deletions: 15, Changes: 15}, + } + json.NewEncoder(w).Encode(files) + })) + defer server.Close() + + c := NewClient(server.URL) + files, err := c.GetChangedFiles(context.Background(), "test-token", "owner1", "repo1", 5) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(files) != 3 { + t.Fatalf("got %d files, want 3", len(files)) + } + if files[0].Filename != "main.go" { + t.Errorf("files[0].Filename = %q, want %q", files[0].Filename, "main.go") + } + if files[0].Status != "modified" { + t.Errorf("files[0].Status = %q, want %q", files[0].Status, "modified") + } + if files[1].Status != "added" { + t.Errorf("files[1].Status = %q, want %q", files[1].Status, "added") + } + if files[2].Status != "removed" { + t.Errorf("files[2].Status = %q, want %q", files[2].Status, "removed") + } +} + +func TestGetChangedFiles_Error(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + fmt.Fprintln(w, `{"message":"pull request not found"}`) + })) + defer server.Close() + + c := NewClient(server.URL) + _, err := c.GetChangedFiles(context.Background(), "test-token", "owner1", "repo1", 999) + if err == nil { + t.Fatal("expected error for 404 response, got nil") + } + if !strings.Contains(err.Error(), "404") { + t.Errorf("error should contain status code 404, got: %v", err) + } +}