github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/benchplot/git.go (about)

     1  // Copyright 2016 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"log"
     9  	"os"
    10  	"os/exec"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  )
    15  
    16  type CommitInfo struct {
    17  	Hash, Subject, Branch  string
    18  	AuthorDate, CommitDate time.Time
    19  
    20  	Parents, Children []string
    21  }
    22  
    23  func Commits(repo string, revs ...string) (commits []CommitInfo) {
    24  	args := []string{"-C", repo, "log", "-s",
    25  		"--format=format:%H %aI %cI %P\n%s\n"}
    26  	if len(revs) == 0 {
    27  		args = append(args, "--all")
    28  	} else {
    29  		args = append(append(args, "--"), revs...)
    30  	}
    31  	cmd := exec.Command("git", args...)
    32  	cmd.Stderr = os.Stderr
    33  	out, err := cmd.Output()
    34  	if err != nil {
    35  		log.Fatal("git show failed: ", err)
    36  	}
    37  	for _, line := range strings.Split(string(out), "\n\n") {
    38  		parts := strings.Split(line, "\n")
    39  		subject := parts[1]
    40  		parts = strings.Split(parts[0], " ")
    41  
    42  		adate, err := time.Parse(time.RFC3339, parts[1])
    43  		if err != nil {
    44  			log.Fatal("cannot parse author date: ", err)
    45  		}
    46  		cdate, err := time.Parse(time.RFC3339, parts[2])
    47  		if err != nil {
    48  			log.Fatal("cannot parse commit date: ", err)
    49  		}
    50  
    51  		commits = append(commits, CommitInfo{
    52  			parts[0], subject, "", adate, cdate,
    53  			parts[3:], nil,
    54  		})
    55  	}
    56  
    57  	// Compute hash indexes.
    58  	hashset := make(map[string]*CommitInfo)
    59  	for i := range commits {
    60  		hashset[commits[i].Hash] = &commits[i]
    61  	}
    62  
    63  	// Compute children hashes.
    64  	for h, ci := range hashset {
    65  		for _, parent := range ci.Parents {
    66  			if ci2, ok := hashset[parent]; ok {
    67  				ci2.Children = append(ci2.Children, h)
    68  			}
    69  		}
    70  	}
    71  
    72  	// Compute branch names.
    73  	var branchRe = regexp.MustCompile(`^\[[^] ]+\] `)
    74  	var branchOf func(ci *CommitInfo) string
    75  	branchOf = func(ci *CommitInfo) string {
    76  		subject := ci.Subject
    77  		if strings.HasPrefix(subject, "[") {
    78  			m := branchRe.FindString(subject)
    79  			if m != "" {
    80  				return m[1 : len(m)-2]
    81  			}
    82  		}
    83  		if strings.HasPrefix(subject, "Merge") || strings.HasPrefix(subject, "Revert") {
    84  			// Walk children looking for a branch name.
    85  			for _, child := range ci.Children {
    86  				if ci2 := hashset[child]; ci2 != nil {
    87  					branch := branchOf(ci2)
    88  					if branch != "master" {
    89  						return branch
    90  					}
    91  				}
    92  			}
    93  		}
    94  		return "master"
    95  	}
    96  	for _, ci := range hashset {
    97  		ci.Branch = branchOf(ci)
    98  	}
    99  	// Clean up missing branch tags: if all parents and children
   100  	// of a commit have the same non-master branch, that commit
   101  	// must also have been from that branch.
   102  cleanBranches:
   103  	for _, ci := range hashset {
   104  		if ci.Branch == "master" {
   105  			alt := ""
   106  			for _, child := range ci.Children {
   107  				if ci2 := hashset[child]; ci2 != nil {
   108  					if alt == "" {
   109  						alt = ci2.Branch
   110  					} else if ci2.Branch != alt {
   111  						continue cleanBranches
   112  					}
   113  				}
   114  			}
   115  			for _, parent := range ci.Parents {
   116  				if ci2 := hashset[parent]; ci2 != nil {
   117  					if alt == "" {
   118  						alt = ci2.Branch
   119  					} else if ci2.Branch != alt {
   120  						continue cleanBranches
   121  					}
   122  				}
   123  			}
   124  			if alt != "" {
   125  				ci.Branch = alt
   126  			}
   127  		}
   128  	}
   129  
   130  	return
   131  }