Skip to content

Commit 89c4b18

Browse files
julianknutsenclaude
andcommitted
Distinguish upstream vs fork in wl me dashboard
wl me now fetches both remotes and queries via AS OF to show where each item lives: "On upstream (master DB)" vs "On origin only (your fork)". Items on wl/* branches still show as "In-flight branches". Adds commons.FetchRemote for non-destructive ref updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d7821ff commit 89c4b18

2 files changed

Lines changed: 123 additions & 70 deletions

File tree

cmd/wl/cmd_me.go

Lines changed: 110 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ func newMeCmd(stdout, stderr io.Writer) *cobra.Command {
1717
Long: `Show your personal dashboard: claimed items, items awaiting your review,
1818
in-flight branch work (PR mode), and recent completions.
1919
20-
Syncs with upstream first, then queries your local clone.
21-
Also scans wl/<handle>/* branches for PR-mode items not yet on main.
20+
Fetches both upstream (master DB) and origin (your fork), then shows
21+
where each item lives. Also scans wl/<handle>/* branches for PR-mode
22+
items not yet on main.
2223
2324
Examples:
2425
wl me`,
@@ -39,38 +40,65 @@ func runMe(cmd *cobra.Command, stdout, _ io.Writer) error {
3940
return err
4041
}
4142

42-
fmt.Fprintf(stdout, "Syncing with upstream...\n")
43-
if err := commons.PullUpstream(cfg.LocalDir); err != nil {
44-
return fmt.Errorf("pulling upstream: %w", err)
45-
}
46-
4743
handle := cfg.RigHandle
44+
dbDir := cfg.LocalDir
45+
46+
// Fetch both remotes (non-destructive — no merge into local main).
47+
fmt.Fprintf(stdout, "Fetching upstream and origin...\n")
48+
upstreamOK := commons.FetchRemote(dbDir, "upstream") == nil
49+
originOK := commons.FetchRemote(dbDir, "origin") == nil
50+
4851
printed := false
4952

50-
// My claimed items (on main)
51-
claimedCSV, err := commons.DoltSQLQuery(cfg.LocalDir, fmt.Sprintf(
52-
"SELECT id, title, status, priority, effort_level FROM wanted WHERE claimed_by = '%s' AND status IN ('claimed','in_review') ORDER BY priority ASC",
53-
commons.EscapeSQL(handle),
54-
))
55-
if err != nil {
56-
return fmt.Errorf("querying claimed items: %w", err)
57-
}
58-
claimedRows := wlParseCSV(claimedCSV)
59-
if len(claimedRows) > 1 {
60-
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("My claimed items:"))
61-
for _, row := range claimedRows[1:] {
62-
if len(row) < 5 {
63-
continue
53+
// Items on upstream/main (the canonical master DB).
54+
if upstreamOK {
55+
upstreamItems := queryClaimedAsOf(dbDir, handle, "upstream/main")
56+
if len(upstreamItems) > 0 {
57+
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("On upstream (master DB):"))
58+
for _, row := range upstreamItems {
59+
fmt.Fprintf(stdout, " %-12s %-30s %-12s %-4s %s\n", row[0], row[1], row[2], wlFormatPriority(row[3]), row[4])
6460
}
65-
pri := wlFormatPriority(row[3])
66-
fmt.Fprintf(stdout, " %-12s %-30s %-12s %-4s %s\n", row[0], row[1], row[2], pri, row[4])
61+
printed = true
62+
}
63+
}
64+
65+
// Items on origin/main but NOT on upstream/main (fork only).
66+
if originOK {
67+
upstreamIDs := map[string]bool{}
68+
if upstreamOK {
69+
for _, row := range queryClaimedAsOf(dbDir, handle, "upstream/main") {
70+
upstreamIDs[row[0]] = true
71+
}
72+
}
73+
originItems := queryClaimedAsOf(dbDir, handle, "origin/main")
74+
var forkOnly [][]string
75+
for _, row := range originItems {
76+
if !upstreamIDs[row[0]] {
77+
forkOnly = append(forkOnly, row)
78+
}
79+
}
80+
if len(forkOnly) > 0 {
81+
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("On origin only (your fork — not yet on upstream):"))
82+
for _, row := range forkOnly {
83+
fmt.Fprintf(stdout, " %-12s %-30s %-12s %-4s %s\n", row[0], row[1], row[2], wlFormatPriority(row[3]), row[4])
84+
}
85+
printed = true
6786
}
68-
printed = true
6987
}
7088

71-
// In-flight branches (PR-mode items not yet on main)
72-
mainIDs := mainClaimedIDs(claimedRows)
73-
branchItems := listBranchItems(cfg.LocalDir, handle, mainIDs)
89+
// In-flight branches (PR-mode items on wl/<handle>/* branches).
90+
seenIDs := map[string]bool{}
91+
if upstreamOK {
92+
for _, row := range queryClaimedAsOf(dbDir, handle, "upstream/main") {
93+
seenIDs[row[0]] = true
94+
}
95+
}
96+
if originOK {
97+
for _, row := range queryClaimedAsOf(dbDir, handle, "origin/main") {
98+
seenIDs[row[0]] = true
99+
}
100+
}
101+
branchItems := listBranchItems(dbDir, handle, seenIDs)
74102
if len(branchItems) > 0 {
75103
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("In-flight branches (not yet on main):"))
76104
for _, bi := range branchItems {
@@ -79,44 +107,46 @@ func runMe(cmd *cobra.Command, stdout, _ io.Writer) error {
79107
printed = true
80108
}
81109

82-
// Awaiting my review
83-
reviewCSV, err := commons.DoltSQLQuery(cfg.LocalDir, fmt.Sprintf(
84-
"SELECT id, title, claimed_by FROM wanted WHERE posted_by = '%s' AND status = 'in_review' ORDER BY priority ASC",
85-
commons.EscapeSQL(handle),
110+
// Awaiting my review (on upstream, the canonical source).
111+
ref := "upstream/main"
112+
if !upstreamOK {
113+
ref = "main"
114+
}
115+
reviewCSV, err := commons.DoltSQLQuery(dbDir, fmt.Sprintf(
116+
"SELECT id, title, claimed_by FROM wanted AS OF '%s' WHERE posted_by = '%s' AND status = 'in_review' ORDER BY priority ASC",
117+
commons.EscapeSQL(ref), commons.EscapeSQL(handle),
86118
))
87-
if err != nil {
88-
return fmt.Errorf("querying review items: %w", err)
89-
}
90-
reviewRows := wlParseCSV(reviewCSV)
91-
if len(reviewRows) > 1 {
92-
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("Awaiting my review:"))
93-
for _, row := range reviewRows[1:] {
94-
if len(row) < 3 {
95-
continue
119+
if err == nil {
120+
reviewRows := wlParseCSV(reviewCSV)
121+
if len(reviewRows) > 1 {
122+
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("Awaiting my review:"))
123+
for _, row := range reviewRows[1:] {
124+
if len(row) < 3 {
125+
continue
126+
}
127+
fmt.Fprintf(stdout, " %-12s %-30s claimed by %s\n", row[0], row[1], row[2])
96128
}
97-
fmt.Fprintf(stdout, " %-12s %-30s claimed by %s\n", row[0], row[1], row[2])
129+
printed = true
98130
}
99-
printed = true
100131
}
101132

102-
// Recent completions
103-
completedCSV, err := commons.DoltSQLQuery(cfg.LocalDir, fmt.Sprintf(
104-
"SELECT id, title FROM wanted WHERE claimed_by = '%s' AND status = 'completed' ORDER BY updated_at DESC LIMIT 5",
105-
commons.EscapeSQL(handle),
133+
// Recent completions (on upstream).
134+
completedCSV, err := commons.DoltSQLQuery(dbDir, fmt.Sprintf(
135+
"SELECT id, title FROM wanted AS OF '%s' WHERE claimed_by = '%s' AND status = 'completed' ORDER BY updated_at DESC LIMIT 5",
136+
commons.EscapeSQL(ref), commons.EscapeSQL(handle),
106137
))
107-
if err != nil {
108-
return fmt.Errorf("querying completed items: %w", err)
109-
}
110-
completedRows := wlParseCSV(completedCSV)
111-
if len(completedRows) > 1 {
112-
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("Recent completions:"))
113-
for _, row := range completedRows[1:] {
114-
if len(row) < 2 {
115-
continue
138+
if err == nil {
139+
completedRows := wlParseCSV(completedCSV)
140+
if len(completedRows) > 1 {
141+
fmt.Fprintf(stdout, "\n%s\n", style.Bold.Render("Recent completions:"))
142+
for _, row := range completedRows[1:] {
143+
if len(row) < 2 {
144+
continue
145+
}
146+
fmt.Fprintf(stdout, " %-12s %s\n", row[0], row[1])
116147
}
117-
fmt.Fprintf(stdout, " %-12s %s\n", row[0], row[1])
148+
printed = true
118149
}
119-
printed = true
120150
}
121151

122152
if !printed {
@@ -126,26 +156,36 @@ func runMe(cmd *cobra.Command, stdout, _ io.Writer) error {
126156
return nil
127157
}
128158

159+
// queryClaimedAsOf queries claimed/in_review items for a handle on a specific ref.
160+
// Returns data rows (no header) with columns: id, title, status, priority, effort_level.
161+
func queryClaimedAsOf(dbDir, handle, ref string) [][]string {
162+
csv, err := commons.DoltSQLQuery(dbDir, fmt.Sprintf(
163+
"SELECT id, title, status, priority, effort_level FROM wanted AS OF '%s' WHERE claimed_by = '%s' AND status IN ('claimed','in_review') ORDER BY priority ASC",
164+
commons.EscapeSQL(ref), commons.EscapeSQL(handle),
165+
))
166+
if err != nil {
167+
return nil
168+
}
169+
rows := wlParseCSV(csv)
170+
if len(rows) <= 1 {
171+
return nil
172+
}
173+
var data [][]string
174+
for _, row := range rows[1:] {
175+
if len(row) >= 5 {
176+
data = append(data, row)
177+
}
178+
}
179+
return data
180+
}
181+
129182
// branchItem represents a wanted item found on a wl/* branch.
130183
type branchItem struct {
131184
id string
132185
title string
133186
branch string
134187
}
135188

136-
// mainClaimedIDs extracts IDs already shown in the "My claimed items" section
137-
// so we can deduplicate against branch results.
138-
func mainClaimedIDs(claimedRows [][]string) map[string]bool {
139-
ids := make(map[string]bool)
140-
for i, row := range claimedRows {
141-
if i == 0 || len(row) < 1 { // skip header
142-
continue
143-
}
144-
ids[row[0]] = true
145-
}
146-
return ids
147-
}
148-
149189
// listBranchItems scans wl/<handle>/* branches and returns items that differ
150190
// from main — i.e., PR-mode work not yet merged.
151191
func listBranchItems(dbDir, handle string, skipIDs map[string]bool) []branchItem {

internal/commons/dolt.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ func PullUpstream(dbDir string) error {
7676
return pullRemote(dbDir, "upstream")
7777
}
7878

79+
// FetchRemote fetches the latest refs from a named remote without merging.
80+
func FetchRemote(dbDir, remote string) error {
81+
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
82+
defer cancel()
83+
cmd := exec.CommandContext(ctx, "dolt", "fetch", remote)
84+
cmd.Dir = dbDir
85+
output, err := cmd.CombinedOutput()
86+
if err != nil {
87+
return fmt.Errorf("dolt fetch %s: %w (%s)", remote, err, strings.TrimSpace(string(output)))
88+
}
89+
return nil
90+
}
91+
7992
// doltSQLScript executes a SQL script against a dolt database directory.
8093
func doltSQLScript(dbDir, script string) error {
8194
tmpFile, err := os.CreateTemp("", "dolt-script-*.sql")

0 commit comments

Comments
 (0)