hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

git.go (5567B)

    1 // Copyright 2017-present The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package releaser
   15 
   16 import (
   17 	"fmt"
   18 	"regexp"
   19 	"sort"
   20 	"strconv"
   21 	"strings"
   22 
   23 	"github.com/gohugoio/hugo/common/hexec"
   24 )
   25 
   26 var issueRe = regexp.MustCompile(`(?i)(?:Updates?|Closes?|Fix.*|See) #(\d+)`)
   27 
   28 type changeLog struct {
   29 	Version string
   30 	Notes   gitInfos
   31 	All     gitInfos
   32 	Docs    gitInfos
   33 
   34 	// Overall stats
   35 	Repo             *gitHubRepo
   36 	ContributorCount int
   37 	ThemeCount       int
   38 }
   39 
   40 func newChangeLog(infos, docInfos gitInfos) *changeLog {
   41 	log := &changeLog{
   42 		Docs: docInfos,
   43 	}
   44 
   45 	for _, info := range infos {
   46 		// TODO(bep) improve
   47 		if regexp.MustCompile("(?i)deprecate|note").MatchString(info.Subject) {
   48 			log.Notes = append(log.Notes, info)
   49 		}
   50 
   51 		log.All = append(log.All, info)
   52 		info.Subject = strings.TrimSpace(info.Subject)
   53 
   54 	}
   55 
   56 	return log
   57 }
   58 
   59 type gitInfo struct {
   60 	Hash    string
   61 	Author  string
   62 	Subject string
   63 	Body    string
   64 
   65 	GitHubCommit *gitHubCommit
   66 }
   67 
   68 func (g gitInfo) Issues() []int {
   69 	return extractIssues(g.Body)
   70 }
   71 
   72 func (g gitInfo) AuthorID() string {
   73 	if g.GitHubCommit != nil {
   74 		return g.GitHubCommit.Author.Login
   75 	}
   76 	return g.Author
   77 }
   78 
   79 func extractIssues(body string) []int {
   80 	var i []int
   81 	m := issueRe.FindAllStringSubmatch(body, -1)
   82 	for _, mm := range m {
   83 		issueID, err := strconv.Atoi(mm[1])
   84 		if err != nil {
   85 			continue
   86 		}
   87 		i = append(i, issueID)
   88 	}
   89 	return i
   90 }
   91 
   92 type gitInfos []gitInfo
   93 
   94 func git(args ...string) (string, error) {
   95 	cmd, _ := hexec.SafeCommand("git", args...)
   96 	out, err := cmd.CombinedOutput()
   97 	if err != nil {
   98 		return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args)
   99 	}
  100 	return string(out), nil
  101 }
  102 
  103 func getGitInfos(tag, repo, repoPath string, remote bool) (gitInfos, error) {
  104 	return getGitInfosBefore("HEAD", tag, repo, repoPath, remote)
  105 }
  106 
  107 type countribCount struct {
  108 	Author       string
  109 	GitHubAuthor gitHubAuthor
  110 	Count        int
  111 }
  112 
  113 func (c countribCount) AuthorLink() string {
  114 	if c.GitHubAuthor.HTMLURL != "" {
  115 		return fmt.Sprintf("[@%s](%s)", c.GitHubAuthor.Login, c.GitHubAuthor.HTMLURL)
  116 	}
  117 
  118 	if !strings.Contains(c.Author, "@") {
  119 		return c.Author
  120 	}
  121 
  122 	return c.Author[:strings.Index(c.Author, "@")]
  123 }
  124 
  125 type contribCounts []countribCount
  126 
  127 func (c contribCounts) Less(i, j int) bool { return c[i].Count > c[j].Count }
  128 func (c contribCounts) Len() int           { return len(c) }
  129 func (c contribCounts) Swap(i, j int)      { c[i], c[j] = c[j], c[i] }
  130 
  131 func (g gitInfos) ContribCountPerAuthor() contribCounts {
  132 	var c contribCounts
  133 
  134 	counters := make(map[string]countribCount)
  135 
  136 	for _, gi := range g {
  137 		authorID := gi.AuthorID()
  138 		if count, ok := counters[authorID]; ok {
  139 			count.Count = count.Count + 1
  140 			counters[authorID] = count
  141 		} else {
  142 			var ghA gitHubAuthor
  143 			if gi.GitHubCommit != nil {
  144 				ghA = gi.GitHubCommit.Author
  145 			}
  146 			authorCount := countribCount{Count: 1, Author: gi.Author, GitHubAuthor: ghA}
  147 			counters[authorID] = authorCount
  148 		}
  149 	}
  150 
  151 	for _, v := range counters {
  152 		c = append(c, v)
  153 	}
  154 
  155 	sort.Sort(c)
  156 	return c
  157 }
  158 
  159 func getGitInfosBefore(ref, tag, repo, repoPath string, remote bool) (gitInfos, error) {
  160 	client := newGitHubAPI(repo)
  161 	var g gitInfos
  162 
  163 	log, err := gitLogBefore(ref, tag, repoPath)
  164 	if err != nil {
  165 		return g, err
  166 	}
  167 
  168 	log = strings.Trim(log, "\n\x1e'")
  169 	entries := strings.Split(log, "\x1e")
  170 
  171 	for _, entry := range entries {
  172 		items := strings.Split(entry, "\x1f")
  173 		gi := gitInfo{}
  174 
  175 		if len(items) > 0 {
  176 			gi.Hash = items[0]
  177 		}
  178 		if len(items) > 1 {
  179 			gi.Author = items[1]
  180 		}
  181 		if len(items) > 2 {
  182 			gi.Subject = items[2]
  183 		}
  184 		if len(items) > 3 {
  185 			gi.Body = items[3]
  186 		}
  187 
  188 		if remote && gi.Hash != "" {
  189 			gc, err := client.fetchCommit(gi.Hash)
  190 			if err == nil {
  191 				gi.GitHubCommit = &gc
  192 			}
  193 		}
  194 		g = append(g, gi)
  195 	}
  196 
  197 	return g, nil
  198 }
  199 
  200 // Ignore autogenerated commits etc. in change log. This is a regexp.
  201 const ignoredCommits = "snapcraft:|Merge commit|Squashed"
  202 
  203 func gitLogBefore(ref, tag, repoPath string) (string, error) {
  204 	var prevTag string
  205 	var err error
  206 	if tag != "" {
  207 		prevTag = tag
  208 	} else {
  209 		prevTag, err = gitVersionTagBefore(ref)
  210 		if err != nil {
  211 			return "", err
  212 		}
  213 	}
  214 
  215 	defaultArgs := []string{"log", "-E", fmt.Sprintf("--grep=%s", ignoredCommits), "--invert-grep", "--pretty=format:%x1e%h%x1f%aE%x1f%s%x1f%b", "--abbrev-commit", prevTag + ".." + ref}
  216 
  217 	var args []string
  218 
  219 	if repoPath != "" {
  220 		args = append([]string{"-C", repoPath}, defaultArgs...)
  221 	} else {
  222 		args = defaultArgs
  223 	}
  224 
  225 	log, err := git(args...)
  226 	if err != nil {
  227 		return ",", err
  228 	}
  229 
  230 	return log, err
  231 }
  232 
  233 func gitVersionTagBefore(ref string) (string, error) {
  234 	return gitShort("describe", "--tags", "--abbrev=0", "--always", "--match", "v[0-9]*", ref+"^")
  235 }
  236 
  237 func gitShort(args ...string) (output string, err error) {
  238 	output, err = git(args...)
  239 	return strings.Replace(strings.Split(output, "\n")[0], "'", "", -1), err
  240 }
  241 
  242 func tagExists(tag string) (bool, error) {
  243 	out, err := git("tag", "-l", tag)
  244 	if err != nil {
  245 		return false, err
  246 	}
  247 
  248 	if strings.Contains(out, tag) {
  249 		return true, nil
  250 	}
  251 
  252 	return false, nil
  253 }