Add repo-detail panel and /api/repo endpoint
New <repo-detail> component listens for repo:select and shows a repo's remotes, local branches (current + upstream), and recent commits. Backed by GET /api/repo (restricted to indexed repos) and read-only git readers LocalBranches/RecentCommits/RemoteDetails via repos.BuildDetail. <repo-list> highlights the selection; page docks the two panels left/right. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -119,3 +119,106 @@ func (c *CLI) Fetch(ctx context.Context, dir string) error {
|
||||
_, err := c.run(ctx, dir, "fetch", "--quiet", "--all")
|
||||
return err
|
||||
}
|
||||
|
||||
// Branch is a local branch and its upstream, if any.
|
||||
type Branch struct {
|
||||
Name string `json:"name"`
|
||||
Current bool `json:"current"`
|
||||
Upstream string `json:"upstream,omitempty"`
|
||||
}
|
||||
|
||||
// Commit is a single log entry.
|
||||
type Commit struct {
|
||||
Short string `json:"short"`
|
||||
Author string `json:"author"`
|
||||
Date string `json:"date"`
|
||||
Subject string `json:"subject"`
|
||||
}
|
||||
|
||||
// Remote is a named remote and its fetch URL.
|
||||
type Remote struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// LocalBranches lists local branches (refs/heads), flagging the current one and
|
||||
// including each branch's upstream when set.
|
||||
func (c *CLI) LocalBranches(ctx context.Context, dir string) ([]Branch, error) {
|
||||
const format = "%(refname:short)%09%(HEAD)%09%(upstream:short)"
|
||||
out, err := c.run(ctx, dir, "for-each-ref", "--format="+format, "refs/heads")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var branches []Branch
|
||||
for _, line := range splitLines(out) {
|
||||
f := strings.Split(line, "\t")
|
||||
if len(f) < 1 || f[0] == "" {
|
||||
continue
|
||||
}
|
||||
b := Branch{Name: f[0]}
|
||||
if len(f) > 1 {
|
||||
b.Current = f[1] == "*"
|
||||
}
|
||||
if len(f) > 2 {
|
||||
b.Upstream = f[2]
|
||||
}
|
||||
branches = append(branches, b)
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
||||
// RecentCommits returns the newest n commits reachable from HEAD.
|
||||
func (c *CLI) RecentCommits(ctx context.Context, dir string, n int) ([]Commit, error) {
|
||||
// Fields separated by TAB (%x09); records by newline.
|
||||
const format = "%h%x09%an%x09%ad%x09%s"
|
||||
out, err := c.run(ctx, dir, "log", "-n", strconv.Itoa(n), "--date=short", "--pretty=format:"+format)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var commits []Commit
|
||||
for _, line := range splitLines(out) {
|
||||
f := strings.SplitN(line, "\t", 4)
|
||||
if len(f) < 4 {
|
||||
continue
|
||||
}
|
||||
commits = append(commits, Commit{Short: f[0], Author: f[1], Date: f[2], Subject: f[3]})
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// RemoteDetails returns each remote with its fetch URL.
|
||||
func (c *CLI) RemoteDetails(ctx context.Context, dir string) ([]Remote, error) {
|
||||
out, err := c.run(ctx, dir, "remote", "-v")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seen := make(map[string]struct{})
|
||||
var remotes []Remote
|
||||
for _, line := range splitLines(out) {
|
||||
// Format: "<name>\t<url> (fetch|push)"
|
||||
f := strings.Fields(line)
|
||||
if len(f) < 3 || f[2] != "(fetch)" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[f[0]]; ok {
|
||||
continue
|
||||
}
|
||||
seen[f[0]] = struct{}{}
|
||||
remotes = append(remotes, Remote{Name: f[0], URL: f[1]})
|
||||
}
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
// splitLines splits on newlines, dropping empty lines.
|
||||
func splitLines(s string) []string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
var out []string
|
||||
for _, line := range strings.Split(s, "\n") {
|
||||
if line != "" {
|
||||
out = append(out, line)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user