github.com/jancarloviray/community@v0.41.1-0.20170124221257-33a66c87cf2f/core/section/github/commits.go (about) 1 // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. 2 // 3 // This software (Documize Community Edition) is licensed under 4 // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html 5 // 6 // You can operate outside the AGPL restrictions by purchasing 7 // Documize Enterprise Edition and obtaining a commercial license 8 // by contacting <sales@documize.com>. 9 // 10 // https://documize.com 11 12 package github 13 14 import ( 15 "fmt" 16 "html/template" 17 "sort" 18 "time" 19 20 "github.com/documize/community/core/log" 21 22 gogithub "github.com/google/go-github/github" 23 ) 24 25 const commitTimeFormat = "January 2 2006, 15:04" 26 27 type githubCommit struct { 28 Owner string `json:"owner"` 29 Repo string `json:"repo"` 30 ShowRepo bool `json:"showRepo"` 31 Branch string `json:"branch"` 32 ShowBranch bool `json:"showBranch"` 33 Date string `json:"date"` 34 BinDate time.Time `json:"-"` // only used for sorting 35 ShowDate bool `json:"showDate"` 36 Login string `json:"login"` 37 Name string `json:"name"` 38 Avatar string `json:"avatar"` 39 Message string `json:"message"` 40 URL template.URL `json:"url"` 41 } 42 43 type githubAuthorStats struct { 44 Author string `json:"author"` 45 Login string `json:"login"` 46 Avatar string `json:"avatar"` 47 CommitCount int `json:"commitCount"` 48 Repos []string `json:"repos"` 49 OpenIssues int `json:"openIssues"` 50 ClosedIssues int `json:"closedIssues"` 51 } 52 53 // order commits in a way that makes sense of the table 54 type orderCommits []githubCommit 55 56 func (s orderCommits) Len() int { return len(s) } 57 func (s orderCommits) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 58 func (s orderCommits) Less(i, j int) bool { 59 if s[i].Repo == s[j].Repo { 60 if s[i].Branch == s[j].Branch { 61 if s[i].BinDate == s[j].BinDate { 62 return s[i].Name < s[j].Name 63 } 64 return s[i].BinDate.Before(s[j].BinDate) 65 } 66 return s[i].Branch < s[j].Branch 67 } 68 return s[i].Repo < s[j].Repo 69 } 70 71 // sort stats in order that that should be presented. 72 type asToSort []githubAuthorStats 73 74 func (s asToSort) Len() int { return len(s) } 75 func (s asToSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 76 func (s asToSort) Less(i, j int) bool { 77 return s[i].CommitCount > s[j].CommitCount 78 } 79 80 // sort branches in order that that should be presented. 81 type branchByID []githubBranch 82 83 func (s branchByID) Len() int { return len(s) } 84 func (s branchByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 85 func (s branchByID) Less(i, j int) bool { 86 return s[i].ID < s[j].ID 87 } 88 89 const tagCommitsData = "commitsData" 90 91 func getCommits(client *gogithub.Client, config *githubConfig) ([]githubCommit, []githubAuthorStats, error) { 92 93 if !config.ShowCommits { 94 return nil, nil, nil 95 } 96 97 // first make sure we've got all the branches 98 for _, orb := range config.Lists { 99 if orb.Included { 100 101 branches, _, err := client.Repositories.ListBranches(orb.Owner, orb.Repo, 102 &gogithub.ListOptions{PerPage: 100}) 103 if err == nil { 104 render := make([]githubBranch, len(branches)) 105 for kc, vb := range branches { 106 for _, existing := range config.Lists { 107 if orb.Owner == existing.Owner && orb.Repo == existing.Repo && orb.Name == *vb.Name { 108 goto found 109 } 110 } 111 render[kc] = githubBranch{ 112 Owner: orb.Owner, 113 Repo: orb.Repo, 114 Name: *vb.Name, 115 ID: fmt.Sprintf("%s:%s:%s", orb.Owner, orb.Repo, *vb.Name), 116 Included: true, 117 URL: "https://github.com/" + orb.Owner + "/" + orb.Repo + "/tree/" + *vb.Name, 118 } 119 found: 120 } 121 config.Lists = append(config.Lists, render...) 122 } 123 } 124 } 125 sort.Sort(branchByID(config.Lists)) 126 127 config.UserNames = make(map[string]string) 128 129 authorStats := make(map[string]githubAuthorStats) 130 131 contribBranch := make(map[string]map[string]struct{}) 132 133 overall := []githubCommit{} 134 135 for _, orb := range config.Lists { 136 if orb.Included { 137 138 opts := &gogithub.CommitsListOptions{ 139 SHA: orb.Name, 140 ListOptions: gogithub.ListOptions{PerPage: config.BranchLines}} 141 142 if config.SincePtr != nil { 143 opts.Since = *config.SincePtr 144 } 145 146 guff, _, err := client.Repositories.ListCommits(orb.Owner, orb.Repo, opts) 147 148 if err != nil { 149 return nil, nil, err 150 } 151 152 thisBranch := fmt.Sprintf("%s:%s", orb.Repo, orb.Name) 153 154 for _, v := range guff { 155 156 var d, m, u string 157 var bd time.Time 158 if v.Commit != nil { 159 if v.Commit.Committer.Date != nil { 160 d = v.Commit.Committer.Date.Format(commitTimeFormat) 161 bd = *v.Commit.Committer.Date 162 } 163 if v.Commit.Message != nil { 164 m = *v.Commit.Message 165 } 166 } 167 168 if v.HTMLURL != nil { 169 u = *v.HTMLURL 170 } 171 172 // author commits 173 al, an, aa := "", "", githubGravatar 174 if v.Author != nil { 175 if v.Author.Login != nil { 176 al = *v.Author.Login 177 an = getUserName(client, config, al) 178 } 179 180 if v.Author.AvatarURL != nil { 181 aa = *v.Author.AvatarURL 182 } 183 } 184 l := al // use author login 185 186 overall = append(overall, githubCommit{ 187 Owner: orb.Owner, 188 Repo: orb.Repo, 189 Branch: orb.Name, 190 Name: an, 191 Login: l, 192 Message: m, 193 Date: d, 194 BinDate: bd, 195 Avatar: aa, 196 URL: template.URL(u), 197 }) 198 199 if _, ok := contribBranch[l]; !ok { 200 contribBranch[l] = make(map[string]struct{}) 201 } 202 contribBranch[l][thisBranch] = struct{}{} 203 204 cum := authorStats[l] 205 cum.Login = l 206 cum.Author = an 207 cum.Avatar = aa 208 cum.CommitCount++ 209 // TODO review, this code removed as too slow 210 //cmt, _, err := client.Repositories.GetCommit(orb.Owner, orb.Repo, *v.SHA) 211 //if err == nil { 212 // if cmt.Stats != nil { 213 // if cmt.Stats.Total != nil { 214 // cum.TotalChanges += (*cmt.Stats.Total) 215 // } 216 // } 217 //} 218 // 219 authorStats[l] = cum 220 } 221 } 222 } 223 224 sort.Sort(orderCommits(overall)) 225 226 for k := range overall { 227 overall[k].ShowRepo = true 228 overall[k].ShowBranch = true 229 overall[k].ShowDate = true 230 if k > 0 { 231 if overall[k].Repo == overall[k-1].Repo { 232 overall[k].ShowRepo = false 233 if overall[k].Branch == overall[k-1].Branch { 234 overall[k].ShowBranch = false 235 if overall[k].Date == overall[k-1].Date { 236 overall[k].ShowDate = false 237 } 238 } 239 } 240 } 241 } 242 243 retStats := make([]githubAuthorStats, 0, len(authorStats)) 244 for _, v := range authorStats { 245 repos := contribBranch[v.Login] 246 v.Repos = make([]string, 0, len(repos)) 247 for r := range repos { 248 v.Repos = append(v.Repos, r) 249 } 250 sort.Strings(v.Repos) 251 retStats = append(retStats, v) 252 } 253 sort.Sort(asToSort(retStats)) 254 255 return overall, retStats, nil 256 257 } 258 259 func refreshCommits(gr *githubRender, config *githubConfig, client *gogithub.Client) (err error) { 260 261 if !config.ShowCommits { 262 return nil 263 } 264 265 gr.BranchCommits, gr.AuthorStats, err = getCommits(client, config) 266 if err != nil { 267 log.Error("github refreshCommits:", err) 268 return err 269 } 270 return nil 271 } 272 273 func renderCommits(payload *githubRender, c *githubConfig) error { 274 275 if !c.ShowCommits { 276 return nil 277 } 278 279 payload.CommitCount = 0 280 for range payload.BranchCommits { 281 payload.CommitCount++ 282 } 283 payload.HasCommits = payload.CommitCount > 0 284 285 for i := range payload.Issues { 286 var author int 287 for a := range payload.AuthorStats { 288 if payload.AuthorStats[a].Login == payload.Issues[i].Name || 289 (payload.AuthorStats[a].Login == "" && payload.Issues[i].Name == unassignedIssue) { 290 author = a 291 goto found 292 } 293 } 294 // no Author found for issue, so create one 295 payload.AuthorStats = append(payload.AuthorStats, githubAuthorStats{ 296 Author: payload.Issues[i].Name, 297 Avatar: payload.Issues[i].Avatar, 298 }) 299 author = len(payload.AuthorStats) - 1 300 found: 301 if payload.Issues[i].IsOpen { 302 payload.AuthorStats[author].OpenIssues++ 303 } else { 304 payload.AuthorStats[author].ClosedIssues++ 305 } 306 } 307 payload.HasAuthorStats = len(payload.AuthorStats) > 0 308 sort.Sort(asToSort(payload.AuthorStats)) 309 310 payload.NumContributors = len(payload.AuthorStats) - 1 311 312 return nil 313 } 314 315 func init() { 316 reports[tagCommitsData] = report{refreshCommits, renderCommits, commitsTemplate} 317 }