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 }