diff --git a/internal/github/fetch.go b/internal/github/fetch.go index eabce92..597072e 100644 --- a/internal/github/fetch.go +++ b/internal/github/fetch.go @@ -17,7 +17,7 @@ func ListOpenPRsMeta(repo string) ([]map[string]any, error) { out, err := exec.Command("gh", "pr", "list", "--repo", repo, "--state", "open", - "--json", "number,title,author,url,createdAt,additions,deletions,files,body,reviewRequests,headRefOid,headRefName,mergeStateStatus", + "--json", "number,title,author,url,createdAt,additions,deletions,files,body,reviewRequests,headRefOid,headRefName,mergeStateStatus,commits", "--limit", "50", ).Output() if err != nil { @@ -133,6 +133,7 @@ func FetchPRDetails(repo string, raw map[string]any) (*PR, error) { Body: body, HeadSHA: fmt.Sprintf("%v", raw["headRefOid"]), HeadRefName: fmt.Sprintf("%v", raw["headRefName"]), + LatestCommitAuthor: latestCommitAuthorFromRaw(raw), State: state, MergeStateStatus: mergeStateStatus, Checks: checks, @@ -307,7 +308,7 @@ func ListMergedPRsMeta(repo, currentUser string, since time.Time) ([]map[string] "--repo", repo, "--state", "merged", "--search", fmt.Sprintf("merged:>%s", sinceStr), - "--json", "number,title,author,url,createdAt,additions,deletions,files,body,reviewRequests,headRefOid,headRefName,mergeStateStatus,state", + "--json", "number,title,author,url,createdAt,additions,deletions,files,body,reviewRequests,headRefOid,headRefName,mergeStateStatus,state,commits", "--limit", "50", ).Output() if err != nil { @@ -335,7 +336,7 @@ func FetchPRMeta(repo string, number int) (map[string]any, error) { out, err := exec.Command("gh", "pr", "view", fmt.Sprintf("%d", number), "--repo", repo, - "--json", "number,title,author,url,createdAt,additions,deletions,files,body,reviewRequests,headRefOid,headRefName,mergeStateStatus,state", + "--json", "number,title,author,url,createdAt,additions,deletions,files,body,reviewRequests,headRefOid,headRefName,mergeStateStatus,state,commits", ).Output() if err != nil { return nil, fmt.Errorf("gh pr view %d: %w", number, err) @@ -347,6 +348,32 @@ func FetchPRMeta(repo string, number int) (map[string]any, error) { return raw, nil } +// latestCommitAuthorFromRaw extracts the login of the most recent commit author +// from a raw PR JSON object containing a "commits" field. Returns "" if absent. +// gh returns commits ordered oldest-first, so the last entry is the most recent. +func latestCommitAuthorFromRaw(raw map[string]any) string { + commits, ok := raw["commits"].([]any) + if !ok || len(commits) == 0 { + return "" + } + last, ok := commits[len(commits)-1].(map[string]any) + if !ok { + return "" + } + authors, ok := last["authors"].([]any) + if !ok || len(authors) == 0 { + return "" + } + first, ok := authors[0].(map[string]any) + if !ok { + return "" + } + if login, ok := first["login"].(string); ok { + return login + } + return "" +} + func getDiff(repo string, number int) (string, error) { out, err := exec.Command("gh", "pr", "diff", fmt.Sprintf("%d", number), "--repo", repo).Output() if err != nil { diff --git a/internal/github/types.go b/internal/github/types.go index 5cfa67a..a6672bc 100644 --- a/internal/github/types.go +++ b/internal/github/types.go @@ -41,6 +41,7 @@ type PR struct { Body string HeadSHA string HeadRefName string + LatestCommitAuthor string // login of the author of the most recent commit on the head ref State string // "OPEN", "MERGED", or "CLOSED" MergeStateStatus string // "CLEAN", "BLOCKED", "DIRTY", "BEHIND", "UNSTABLE", "UNKNOWN", etc. Checks []CheckStatus diff --git a/internal/tui/model_helpers.go b/internal/tui/model_helpers.go index f9eb3a4..310f9ba 100644 --- a/internal/tui/model_helpers.go +++ b/internal/tui/model_helpers.go @@ -239,7 +239,13 @@ func (m *Model) computeHasNewContent(card *PRCard) { fileNames, fileHunks := diff.FileHunkInfo(card.parsedFiles) commentDigests := diff.CommentDigestsFromPR(card.PR, m.app.CurrentUser) inc := reviewstate.ComputeIncremental(fileNames, fileHunks, commentDigests, state) - card.HasNewContent = inc.HasChanges || inc.HasNewComments + hasChanges := inc.HasChanges + if hasChanges && card.PR.LatestCommitAuthor == m.app.CurrentUser { + // The diff changed since last snapshot but the most recent commit is + // ours — don't resurface PRs because of our own pushes. + hasChanges = false + } + card.HasNewContent = hasChanges || inc.HasNewComments } // updateIncrementalState computes incremental review state and stores it on the @@ -261,6 +267,17 @@ func (m *Model) updateIncrementalState(card *PRCard) { fileNames, fileHunks := diff.FileHunkInfo(card.parsedFiles) commentDigests := diff.CommentDigestsFromPR(card.PR, m.app.CurrentUser) state := reviewstate.ComputeIncremental(fileNames, fileHunks, commentDigests, card.ReviewState) + if state.HasChanges && card.PR.LatestCommitAuthor == m.app.CurrentUser { + // Latest commit is ours — treat all new hunks as already-seen so we + // don't get nagged about our own pushes (visibility & diff badges). + for _, fileHunks := range state.HunkStatus { + for hi := range fileHunks { + fileHunks[hi] = reviewstate.StatusSeen + } + } + state.NewHunkCount = 0 + state.HasChanges = false + } card.HasNewContent = state.HasChanges || state.HasNewComments logger.Info("incremental state for PR #%d: %d new hunks, %d new comments, %d edited, mode=%v", card.PR.Number, state.NewHunkCount, state.NewCommentCount, state.EditedCommentCount,