← back to jmcampanini__grove-cli

Function bodies 172 total

All specs Real LLM only Function bodies
github.parseCommentEvent function · go · L377-L390 (14 LOC)
internal/github/github_cli.go
func parseCommentEvent(raw json.RawMessage) (*TimelineEvent, error) {
	var v struct {
		Author    struct{ Login string } `json:"author"`
		CreatedAt time.Time              `json:"createdAt"`
	}
	if err := json.Unmarshal(raw, &v); err != nil {
		return nil, err
	}
	return &TimelineEvent{
		Actor:     v.Author.Login,
		CreatedAt: v.CreatedAt,
		Type:      TimelineEventCommented,
	}, nil
}
github.parseActorEvent function · go · L392-L405 (14 LOC)
internal/github/github_cli.go
func parseActorEvent(raw json.RawMessage, eventType TimelineEventType) (*TimelineEvent, error) {
	var v struct {
		Actor     struct{ Login string } `json:"actor"`
		CreatedAt time.Time              `json:"createdAt"`
	}
	if err := json.Unmarshal(raw, &v); err != nil {
		return nil, err
	}
	return &TimelineEvent{
		Actor:     v.Actor.Login,
		CreatedAt: v.CreatedAt,
		Type:      eventType,
	}, nil
}
github.parseCommitEvent function · go · L407-L426 (20 LOC)
internal/github/github_cli.go
func parseCommitEvent(raw json.RawMessage) (*TimelineEvent, error) {
	var v struct {
		Commit struct {
			Author struct {
				User struct{ Login string } `json:"user"`
			} `json:"author"`
			CommittedDate   time.Time `json:"committedDate"`
			MessageHeadline string    `json:"messageHeadline"`
		} `json:"commit"`
	}
	if err := json.Unmarshal(raw, &v); err != nil {
		return nil, err
	}
	return &TimelineEvent{
		Actor:     v.Commit.Author.User.Login,
		CreatedAt: v.Commit.CommittedDate,
		Details:   v.Commit.MessageHeadline,
		Type:      TimelineEventCommitted,
	}, nil
}
github.parseLabeledEvent function · go · L428-L443 (16 LOC)
internal/github/github_cli.go
func parseLabeledEvent(raw json.RawMessage) (*TimelineEvent, error) {
	var v struct {
		Actor     struct{ Login string } `json:"actor"`
		CreatedAt time.Time              `json:"createdAt"`
		Label     struct{ Name string }  `json:"label"`
	}
	if err := json.Unmarshal(raw, &v); err != nil {
		return nil, err
	}
	return &TimelineEvent{
		Actor:     v.Actor.Login,
		CreatedAt: v.CreatedAt,
		Details:   v.Label.Name,
		Type:      TimelineEventLabeled,
	}, nil
}
github.parseReviewRequestedEvent function · go · L445-L460 (16 LOC)
internal/github/github_cli.go
func parseReviewRequestedEvent(raw json.RawMessage) (*TimelineEvent, error) {
	var v struct {
		Actor             struct{ Login string } `json:"actor"`
		CreatedAt         time.Time              `json:"createdAt"`
		RequestedReviewer struct{ Login string } `json:"requestedReviewer"`
	}
	if err := json.Unmarshal(raw, &v); err != nil {
		return nil, err
	}
	return &TimelineEvent{
		Actor:     v.Actor.Login,
		CreatedAt: v.CreatedAt,
		Details:   v.RequestedReviewer.Login,
		Type:      TimelineEventReviewRequested,
	}, nil
}
github.PRState.IsValid method · go · L24-L30 (7 LOC)
internal/github/pull_request.go
func (s PRState) IsValid() bool {
	switch s {
	case PRStateOpen, PRStateClosed, PRStateMerged, PRStateDraft:
		return true
	}
	return false
}
github.PRQuery.ToSearchQuery method · go · L44-L77 (34 LOC)
internal/github/pull_request.go
func (q PRQuery) ToSearchQuery() string {
	state := q.State
	if state == "" {
		state = PRStateOpen
	}

	var parts []string

	switch state {
	case PRStateOpen:
		parts = append(parts, "is:pr", "is:open", "draft:false")
	case PRStateDraft:
		parts = append(parts, "is:pr", "is:open", "draft:true")
	case PRStateClosed:
		parts = append(parts, "is:pr", "is:closed", "is:unmerged")
		if q.ClosedWithinDays > 0 {
			cutoff := time.Now().AddDate(0, 0, -q.ClosedWithinDays)
			parts = append(parts, fmt.Sprintf("closed:>=%s", cutoff.Format("2006-01-02")))
		}
	case PRStateMerged:
		parts = append(parts, "is:pr", "is:merged")
		if q.MergedWithinDays > 0 {
			cutoff := time.Now().AddDate(0, 0, -q.MergedWithinDays)
			parts = append(parts, fmt.Sprintf("merged:>=%s", cutoff.Format("2006-01-02")))
		}
	}

	if q.UpdatedWithinDays > 0 {
		cutoff := time.Now().AddDate(0, 0, -q.UpdatedWithinDays)
		parts = append(parts, fmt.Sprintf("updated:>=%s", cutoff.Format("2006-01-02")))
	}

	return strings.Join(part
Repobility · severity-and-effort ranking · https://repobility.com
github.ParseRepoFromURL function · go · L194-L204 (11 LOC)
internal/github/pull_request.go
func ParseRepoFromURL(prURL string) (owner, repo string, err error) {
	u, err := url.Parse(prURL)
	if err != nil {
		return "", "", fmt.Errorf("invalid PR URL %q: %w", prURL, err)
	}
	parts := strings.Split(strings.Trim(u.Path, "/"), "/")
	if len(parts) < 4 || parts[2] != "pull" {
		return "", "", fmt.Errorf("unexpected PR URL format %q: expected /owner/repo/pull/number", prURL)
	}
	return parts[0], parts[1], nil
}
github.PullRequest.UnmarshalJSON method · go · L208-L328 (121 LOC)
internal/github/pull_request.go
func (pr *PullRequest) UnmarshalJSON(data []byte) error {
	type rawReview struct {
		Author struct {
			Login string `json:"login"`
		} `json:"author"`
		State       string    `json:"state"`
		SubmittedAt time.Time `json:"submittedAt"`
	}
	// gh CLI returns two shapes in statusCheckRollup:
	//   CheckRun:      {"__typename":"CheckRun", "name":"ci/test", "status":"COMPLETED", "conclusion":"success"}
	//   StatusContext:  {"__typename":"StatusContext", "context":"ci/deploy", "state":"SUCCESS"}
	type rawStatusCheckRollupEntry struct {
		Conclusion string `json:"conclusion"`
		Context    string `json:"context"`
		DetailsURL string `json:"detailsUrl"`
		Name       string `json:"name"`
		State      string `json:"state"`
		Status     string `json:"status"`
		TargetURL  string `json:"targetUrl"`
		TypeName   string `json:"__typename"`
	}
	type rawPR struct {
		Additions int `json:"additions"`
		Author    struct {
			Login string `json:"login"`
			Name  string `json:"name"`
		} `json:"author"`
naming.NewLocalBranchNamer function · go · L17-L31 (15 LOC)
internal/naming/local_branch.go
func NewLocalBranchNamer(localBranchCfg config.LocalBranchConfig, slugCfg config.SlugifyConfig) *LocalBranchNamer {
	return &LocalBranchNamer{
		branchPrefix:      localBranchCfg.BranchPrefix,
		stripBranchPrefix: localBranchCfg.StripBranchPrefix,
		worktreePrefix:    localBranchCfg.WorktreePrefix,
		slugifyOpts: SlugifyOptions{
			CollapseDashes:     slugCfg.CollapseDashes,
			HashLength:         slugCfg.HashLength,
			Lowercase:          slugCfg.Lowercase,
			MaxLength:          slugCfg.MaxLength,
			ReplaceNonAlphaNum: slugCfg.ReplaceNonAlphanum,
			TrimDashes:         slugCfg.TrimDashes,
		},
	}
}
naming.LocalBranchNamer.GenerateBranchName method · go · L33-L39 (7 LOC)
internal/naming/local_branch.go
func (n *LocalBranchNamer) GenerateBranchName(phrase string) string {
	slug := Slugify(phrase, n.slugifyOpts)
	if slug == "" {
		return ""
	}
	return n.branchPrefix + slug
}
naming.LocalBranchNamer.GenerateWorktreeName method · go · L41-L60 (20 LOC)
internal/naming/local_branch.go
func (n *LocalBranchNamer) GenerateWorktreeName(branchName string) string {
	if branchName == "" {
		return ""
	}

	name := branchName
	for _, prefix := range n.stripBranchPrefix {
		if strings.HasPrefix(name, prefix) {
			name = strings.TrimPrefix(name, prefix)
			break
		}
	}

	slug := Slugify(name, n.slugifyOpts)
	if slug == "" {
		return ""
	}

	return n.worktreePrefix + slug
}
naming.LocalBranchNamer.ExtractFromAbsolutePath method · go · L62-L68 (7 LOC)
internal/naming/local_branch.go
func (n *LocalBranchNamer) ExtractFromAbsolutePath(absPath string) string {
	basename := filepath.Base(absPath)
	if strings.HasPrefix(basename, n.worktreePrefix) {
		return strings.TrimPrefix(basename, n.worktreePrefix)
	}
	return basename
}
naming.NewPullRequestNamer function · go · L27-L55 (29 LOC)
internal/naming/pull_request.go
func NewPullRequestNamer(prCfg config.PullRequestConfig, slugCfg config.SlugifyConfig) (*PullRequestNamer, error) {
	tmpl, err := template.New("branch").Parse(prCfg.BranchTemplate)
	if err != nil {
		return nil, fmt.Errorf("invalid branch_template: %w", err)
	}

	var buf bytes.Buffer
	testData := PullRequestTemplateData{BranchName: "test/branch", Number: 1}
	if err := tmpl.Execute(&buf, testData); err != nil {
		return nil, fmt.Errorf("branch_template uses invalid field: %w", err)
	}

	if ok, reason := isValidBranchName(buf.String()); !ok {
		return nil, fmt.Errorf("branch_template produces invalid branch name: %s", reason)
	}

	return &PullRequestNamer{
		branchTemplate: tmpl,
		worktreePrefix: prCfg.WorktreePrefix,
		slugifyOpts: SlugifyOptions{
			CollapseDashes:     slugCfg.CollapseDashes,
			HashLength:         slugCfg.HashLength,
			Lowercase:          slugCfg.Lowercase,
			MaxLength:          slugCfg.MaxLength,
			ReplaceNonAlphaNum: slugCfg.ReplaceNonAlphanum,
			TrimDashes:   
naming.PullRequestNamer.GenerateBranchName method · go · L58-L64 (7 LOC)
internal/naming/pull_request.go
func (n *PullRequestNamer) GenerateBranchName(pr PullRequestTemplateData) (string, error) {
	var buf bytes.Buffer
	if err := n.branchTemplate.Execute(&buf, pr); err != nil {
		return "", fmt.Errorf("failed to generate branch name: %w", err)
	}
	return buf.String(), nil
}
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
naming.PullRequestNamer.GenerateWorktreeName method · go · L68-L80 (13 LOC)
internal/naming/pull_request.go
func (n *PullRequestNamer) GenerateWorktreeName(branchName string) string {
	slug := Slugify(branchName, n.slugifyOpts)
	if slug == "" {
		return ""
	}

	// Smart detection: skip prefix if slug already starts with it
	if strings.HasPrefix(slug, n.worktreePrefix) {
		return slug
	}

	return n.worktreePrefix + slug
}
naming.isValidBranchName function · go · L86-L106 (21 LOC)
internal/naming/pull_request.go
func isValidBranchName(name string) (bool, string) {
	if name == "" {
		return false, "empty"
	}

	if strings.HasPrefix(name, "-") {
		return false, "starts with '-'"
	}

	if strings.Contains(name, "..") {
		return false, "contains '..'"
	}

	for _, r := range name {
		if r < 32 || r == 127 {
			return false, "contains control character"
		}
	}

	return true, ""
}
naming.Slugify function · go · L38-L71 (34 LOC)
internal/naming/slugify.go
func Slugify(input string, opts SlugifyOptions) string {
	if input == "" {
		return ""
	}

	result := input

	if opts.Lowercase {
		result = strings.ToLower(result)
	}

	if opts.ReplaceNonAlphaNum {
		result = nonAlphaNumRegex.ReplaceAllString(result, "-")
	}

	if opts.CollapseDashes {
		result = consecutiveDashRegex.ReplaceAllString(result, "-")
	}

	if opts.TrimDashes {
		result = strings.Trim(result, "-")
	}

	if result == "" {
		return ""
	}

	if opts.MaxLength > 0 && len(result) > opts.MaxLength {
		hash := computeHash(input, opts.HashLength)
		result = truncateWithHash(result, hash, opts.MaxLength, opts.HashLength)
	}

	return result
}
naming.computeHash function · go · L74-L93 (20 LOC)
internal/naming/slugify.go
func computeHash(input string, length int) string {
	if length <= 0 {
		return ""
	}

	h := fnv.New64a()
	h.Write([]byte(input))
	hashValue := h.Sum64()

	const base36Chars = "0123456789abcdefghijklmnopqrstuvwxyz"
	var result strings.Builder
	result.Grow(length)

	for i := 0; i < length; i++ {
		result.WriteByte(base36Chars[hashValue%36])
		hashValue /= 36
	}

	return result.String()
}
naming.truncateWithHash function · go · L96-L119 (24 LOC)
internal/naming/slugify.go
func truncateWithHash(result, hash string, maxLength, hashLength int) string {
	prefixLength := maxLength - hashLength - 1 // include dash separator
	if prefixLength < 1 {
		if len(hash) > maxLength {
			return hash[:maxLength]
		}
		return hash
	}

	prefix := result
	if len(prefix) > prefixLength {
		prefix = prefix[:prefixLength]
	}

	prefix = strings.TrimRight(prefix, "-")
	if prefix == "" {
		if len(hash) > maxLength {
			return hash[:maxLength]
		}
		return hash
	}

	return prefix + "-" + hash
}
pr.Matcher.MatchAll method · go · L35-L44 (10 LOC)
internal/pr/matcher.go
func (m *Matcher) MatchAll(prs []github.PullRequest, worktrees []git.Worktree) []Match {
	result := make([]Match, len(prs))
	for i, p := range prs {
		result[i] = Match{PR: p}
		if wt := m.FindWorktreeForPR(p, worktrees); wt != nil {
			result[i].WorktreePath = wt.AbsolutePath
		}
	}
	return result
}
pr.Matcher.FindWorktreeForPR method · go · L51-L78 (28 LOC)
internal/pr/matcher.go
func (m *Matcher) FindWorktreeForPR(pr github.PullRequest, worktrees []git.Worktree) *git.Worktree {
	// Apply template to get expected local branch name
	prData := naming.PullRequestTemplateData{
		BranchName: pr.BranchName,
		Number:     pr.Number,
	}
	expectedBranch, err := m.namer.GenerateBranchName(prData)
	if err != nil {
		// TODO: replace with structured debug logging when a logging framework is added
		fmt.Fprintf(os.Stderr, "warning: branch name template failed, using direct match only: %v\n", err)
		expectedBranch = ""
	}

	// Search worktrees for matching branch
	for i := range worktrees {
		if branch, ok := worktrees[i].Ref.FullBranch(); ok {
			// Match 1: Template-generated branch name (grove pr checkout)
			if expectedBranch != "" && branch.Name == expectedBranch {
				return &worktrees[i]
			}
			// Match 2: PR's remote branch name directly (manual worktrees)
			if branch.Name == pr.BranchName {
				return &worktrees[i]
			}
		}
	}
	return nil
}
‹ prevpage 4 / 4