Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/commit-chronicle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func parseFlags() (*app.Config, error) {
fs.StringVar(&c.User, "user", "", "GitHub login for PR discovery (default: gh user)")
fs.StringVar(&c.Repos, "repos", "", "comma-separated repo paths (overrides config)")
fs.StringVar(&c.Root, "root", "", "comma-separated dirs to scan for git repos, e.g. ~/work")
fs.StringVar(&c.Project, "project", "", "limit to the repo with this name, e.g. saas-super-admin")
fs.StringVar(&c.Out, "out", "", "output path (default: Downloads, timestamped)")
fs.StringVar(&c.Format, "format", "md", "output format: md | json")
fs.BoolVar(&c.NoEdit, "no-edit", false, "skip the editor step")
Expand Down Expand Up @@ -90,6 +91,7 @@ OPTIONS:
--user <login> GitHub login for PR discovery (default: gh user)
--repos a,b,c comma-separated repo paths (overrides config)
--root ~/work comma-separated dirs to auto-discover git repos under
--project <name> limit to the repo with this name, e.g. saas-super-admin
--out <path> output path (default: Downloads, timestamped)
--format md|json output format (default: md)
--all select everything (skip the picker)
Expand Down
23 changes: 23 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
type Config struct {
Since, From, To, Month, Date string
Author, User, Repos, Root string
Project string // limit to the repo with this name
Out, Format string
NoEdit, All, Copy, NoPR bool
Setup bool // force the guided first-run setup
Expand Down Expand Up @@ -62,6 +63,15 @@ func Run(c Config) error {
return err
}

// --project narrows the resolved set to a single repo by name, so the
// worklog (and every commit's project/branch labelling) covers just it.
if c.Project != "" {
repos = filterByProject(repos, c.Project)
if len(repos) == 0 {
return fmt.Errorf("no configured repo named %q (matched against repo directory names)", c.Project)
}
}

author := c.Author
if author == "" {
author = gitConfigName(repos[0])
Expand Down Expand Up @@ -229,6 +239,19 @@ func resolveRange(c Config, interactive bool) (model.Range, error) {
return model.Preset(model.PresetNames[idx]), nil
}

// filterByProject keeps only the repos whose directory name matches project
// (case-insensitive), letting the user scope a worklog to one project.
func filterByProject(repos []string, project string) []string {
want := strings.ToLower(strings.TrimSpace(project))
var out []string
for _, r := range repos {
if strings.ToLower(filepath.Base(r)) == want {
out = append(out, r)
}
}
return out
}

func splitCSV(s string) []string {
if s == "" {
return nil
Expand Down
35 changes: 29 additions & 6 deletions internal/collect/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,13 @@ func gitCommits(repos []string, author string, r model.Range) []model.Item {
name := filepath.Base(repo)
base := originURL(repo)

// --source tags each commit with the ref it was reached from, exposed via
// %S; with --all that's the branch (or remote/tag) carrying the commit.
args := []string{
"-C", repo, "log", "--all", "--no-merges",
"-C", repo, "log", "--all", "--source", "--no-merges",
"--author=" + author, "--regexp-ignore-case",
"--date=short",
"--pretty=format:%h" + fieldSep + "%H" + fieldSep + "%ad" + fieldSep + "%s",
"--pretty=format:%h" + fieldSep + "%H" + fieldSep + "%ad" + fieldSep + "%S" + fieldSep + "%s",
}
if r.Since != "" {
args = append(args, "--since="+anchorMidnight(r.Since))
Expand All @@ -75,11 +77,11 @@ func gitCommits(repos []string, author string, r model.Range) []model.Item {
if strings.TrimSpace(line) == "" {
continue
}
p := strings.SplitN(line, fieldSep, 4)
if len(p) != 4 {
p := strings.SplitN(line, fieldSep, 5)
if len(p) != 5 {
continue
}
if isNoiseSubject(p[3]) {
if isNoiseSubject(p[4]) {
continue
}
url := ""
Expand All @@ -94,13 +96,34 @@ func gitCommits(repos []string, author string, r model.Range) []model.Item {
URL: url,
Hash: p[1],
ShortHash: p[0],
Title: model.CleanText(p[3]),
Branch: shortRef(p[3]),
Title: model.CleanText(p[4]),
})
}
}
return items
}

// shortRef turns a fully-qualified git ref (as emitted by %S under --source)
// into a bare branch name: refs/heads/x → x, refs/remotes/origin/x → x,
// refs/tags/x → x. Anything else is returned trimmed and unchanged.
func shortRef(ref string) string {
ref = strings.TrimSpace(ref)
switch {
case strings.HasPrefix(ref, "refs/heads/"):
return strings.TrimPrefix(ref, "refs/heads/")
case strings.HasPrefix(ref, "refs/remotes/"):
rest := strings.TrimPrefix(ref, "refs/remotes/")
if i := strings.IndexByte(rest, '/'); i >= 0 {
return rest[i+1:] // drop the remote name, keep the branch
}
return rest
case strings.HasPrefix(ref, "refs/tags/"):
return strings.TrimPrefix(ref, "refs/tags/")
}
return ref
}

// Preview returns `git show --stat` for a commit item (for the picker pane).
func Preview(it model.Item) string {
if it.Kind != model.KindCommit || it.Hash == "" {
Expand Down
1 change: 1 addition & 0 deletions internal/model/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type Item struct {
// Commit-only
Hash string
ShortHash string
Branch string // branch the commit was reached from (git source ref)

// PR/Review-only
Number int
Expand Down
12 changes: 9 additions & 3 deletions internal/render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,13 @@ func line(it model.Item) string {
return fmt.Sprintf("- %s **%s** %s — %s (PR %s) · %s\n",
icon, link(it.Ref()), it.Title, verdict, strings.ToLower(it.State), it.RepoName)
default: // commit
return fmt.Sprintf("- %s _(%s)_\n", it.Title,
link(it.RepoName+"@"+it.ShortHash))
// Lead with the branch (linked to the commit) and keep the repo as
// context; fall back to repo@hash when the branch is unknown.
meta := link(it.RepoName + "@" + it.ShortHash)
if it.Branch != "" {
meta = link(it.Branch) + " · " + it.RepoName
}
return fmt.Sprintf("- %s _(%s)_\n", it.Title, meta)
}
}

Expand All @@ -122,6 +127,7 @@ type jsonItem struct {
Kind string `json:"kind"`
Date string `json:"date"`
Repo string `json:"repo"`
Branch string `json:"branch,omitempty"`
Title string `json:"title"`
URL string `json:"url"`
Hash string `json:"hash,omitempty"`
Expand All @@ -135,7 +141,7 @@ func JSON(items []model.Item, _ Meta) string {
out := make([]jsonItem, 0, len(items))
for _, it := range items {
out = append(out, jsonItem{
Kind: it.Tag(), Date: it.Date, Repo: it.RepoName,
Kind: it.Tag(), Date: it.Date, Repo: it.RepoName, Branch: it.Branch,
Title: it.Title, URL: it.URL, Hash: it.Hash,
Number: it.Number, State: it.State, ReviewState: it.ReviewState,
})
Expand Down
6 changes: 3 additions & 3 deletions internal/tui/picker.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (m *pickerModel) applyFilter() {
tokens := strings.Fields(q)
m.filtered = m.filtered[:0]
for i, it := range m.items {
hay := strings.ToLower(it.Tag() + " " + it.Date + " " + it.RepoName + " " + it.Ref() + " " + it.Title)
hay := strings.ToLower(it.Tag() + " " + it.Date + " " + it.RepoName + " " + it.Branch + " " + it.Ref() + " " + it.Title)
ok := true
for _, t := range tokens {
if !strings.Contains(hay, t) {
Expand Down Expand Up @@ -254,8 +254,8 @@ func (m pickerModel) View() string {
box = "[x]"
}
// Plain text first so truncation counts real characters.
line := fmt.Sprintf("%s %-7s %s %-16s %-8s %s",
box, it.Tag(), it.Date, truncate(it.RepoName, 16), it.Ref(), it.Title)
line := fmt.Sprintf("%s %-7s %s %-16s %-8s %-14s %s",
box, it.Tag(), it.Date, truncate(it.RepoName, 16), it.Ref(), truncate(it.Branch, 14), it.Title)
line = truncate(line, listW-3)
switch {
case i == m.cursor:
Expand Down
Loading