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  }