internal/gitutil/messages.go

4bfcb3893d5ad29d5c6ba0725fd2291d20003e30
gitbay/internal/gitutil/messages.go history · blame · raw

109 lines · 3115 bytes

  1package gitutil
  2
  3import (
  4	"fmt"
  5	"os/exec"
  6	"strings"
  7)
  8
  9const zeroSHA = "0000000000000000000000000000000000000000"
 10
 11type CommitAuthor struct {
 12	SHA   string
 13	Email string
 14	Day   string // author date, YYYY-MM-DD
 15}
 16
 17// RevListAuthors returns sha, author email, and author date for commits
 18// reachable from new but not old, capped at max. Empty or zero old lists
 19// from new alone.
 20func RevListAuthors(dir, old, new string, max int) ([]CommitAuthor, error) {
 21	args := []string{"-C", dir, "log", fmt.Sprintf("--max-count=%d", max), "--format=%H%x00%ae%x00%as", new}
 22	if old != "" && old != zeroSHA {
 23		args = append(args, "^"+old)
 24	}
 25	out, err := exec.Command("git", args...).Output()
 26	if err != nil {
 27		return nil, fmt.Errorf("rev-list authors: %w", err)
 28	}
 29	var authors []CommitAuthor
 30	for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
 31		parts := strings.SplitN(line, "\x00", 3)
 32		if len(parts) != 3 {
 33			continue
 34		}
 35		authors = append(authors, CommitAuthor{SHA: parts[0], Email: parts[1], Day: parts[2]})
 36	}
 37	return authors, nil
 38}
 39
 40// Parents returns a commit's parent shas.
 41func Parents(dir, sha string) []string {
 42	out, err := exec.Command("git", "-C", dir, "log", "-1", "--format=%P", sha).Output()
 43	if err != nil {
 44		return nil
 45	}
 46	return strings.Fields(string(out))
 47}
 48
 49// LastCommitDate returns the committer date (YYYY-MM-DD) of the ref tip,
 50// or "" for empty repos.
 51func LastCommitDate(dir, ref string) string {
 52	out, err := exec.Command("git", "-C", dir, "log", "-1", "--format=%cs", ref).Output()
 53	if err != nil {
 54		return ""
 55	}
 56	return strings.TrimSpace(string(out))
 57}
 58
 59// HasCommit reports whether sha names a commit object present in dir.
 60func HasCommit(dir, sha string) bool {
 61	return exec.Command("git", "-C", dir, "cat-file", "-e", sha+"^{commit}").Run() == nil
 62}
 63
 64type CommitMsg struct {
 65	SHA         string
 66	Message     string
 67	AuthorName  string
 68	AuthorEmail string
 69}
 70
 71// RevListMessages returns sha and full message for commits reachable from
 72// new but not old, newest first, capped at max. An empty or zero old (new
 73// branch) lists from new alone, still capped.
 74func RevListMessages(dir, old, new string, max int) ([]CommitMsg, error) {
 75	args := []string{"-C", dir, "rev-list", fmt.Sprintf("-n%d", max), "--format=%an%x01%ae%x01%B%x00", new}
 76	if old != "" && old != zeroSHA {
 77		args = append(args, "^"+old)
 78	}
 79	out, err := exec.Command("git", args...).Output()
 80	if err != nil {
 81		return nil, fmt.Errorf("rev-list messages: %w", err)
 82	}
 83	var msgs []CommitMsg
 84	for _, chunk := range strings.Split(string(out), "\x00") {
 85		chunk = strings.TrimLeft(chunk, "\n")
 86		if chunk == "" {
 87			continue
 88		}
 89		header, body, ok := strings.Cut(chunk, "\n")
 90		if !ok || !strings.HasPrefix(header, "commit ") {
 91			continue
 92		}
 93		name, rest, ok := strings.Cut(body, "\x01")
 94		if !ok {
 95			continue
 96		}
 97		email, message, ok := strings.Cut(rest, "\x01")
 98		if !ok {
 99			continue
100		}
101		msgs = append(msgs, CommitMsg{
102			SHA:         strings.TrimPrefix(header, "commit "),
103			Message:     strings.TrimSpace(message),
104			AuthorName:  name,
105			AuthorEmail: email,
106		})
107	}
108	return msgs, nil
109}