2020-08-19 18:54:23 +00:00
|
|
|
|
package history
|
|
|
|
|
|
2021-05-11 10:14:00 +00:00
|
|
|
|
// information.go
|
|
|
|
|
// Things related to gathering existing information.
|
2020-08-19 18:54:23 +00:00
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2021-05-03 18:31:19 +00:00
|
|
|
|
"log"
|
2020-08-19 18:54:23 +00:00
|
|
|
|
"regexp"
|
2020-10-03 12:59:53 +00:00
|
|
|
|
"strconv"
|
2020-08-19 18:54:23 +00:00
|
|
|
|
"strings"
|
2020-11-29 17:06:45 +00:00
|
|
|
|
"time"
|
2020-09-26 18:19:17 +00:00
|
|
|
|
|
2021-06-19 04:51:10 +00:00
|
|
|
|
"github.com/bouncepaw/mycorrhiza/cfg"
|
|
|
|
|
"github.com/bouncepaw/mycorrhiza/files"
|
|
|
|
|
|
2020-12-08 15:15:32 +00:00
|
|
|
|
"github.com/gorilla/feeds"
|
2020-08-19 18:54:23 +00:00
|
|
|
|
)
|
|
|
|
|
|
2020-12-08 15:15:32 +00:00
|
|
|
|
func recentChangesFeed() *feeds.Feed {
|
|
|
|
|
feed := &feeds.Feed{
|
|
|
|
|
Title: "Recent changes",
|
2021-05-09 09:36:39 +00:00
|
|
|
|
Link: &feeds.Link{Href: cfg.URL},
|
2020-12-08 15:15:32 +00:00
|
|
|
|
Description: "List of 30 recent changes on the wiki",
|
|
|
|
|
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
|
|
|
|
|
Updated: time.Now(),
|
|
|
|
|
}
|
|
|
|
|
var (
|
2021-05-03 18:31:19 +00:00
|
|
|
|
out, err = silentGitsh(
|
2020-12-08 15:15:32 +00:00
|
|
|
|
"log", "--oneline", "--no-merges",
|
|
|
|
|
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
|
|
|
|
"--max-count=30",
|
|
|
|
|
)
|
|
|
|
|
revs []Revision
|
|
|
|
|
)
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, line := range strings.Split(out.String(), "\n") {
|
|
|
|
|
revs = append(revs, parseRevisionLine(line))
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-03 18:31:19 +00:00
|
|
|
|
log.Printf("Found %d recent changes", len(revs))
|
2020-12-08 15:15:32 +00:00
|
|
|
|
for _, rev := range revs {
|
|
|
|
|
feed.Add(&feeds.Item{
|
|
|
|
|
Title: rev.Message,
|
|
|
|
|
Author: &feeds.Author{Name: rev.Username},
|
|
|
|
|
Id: rev.Hash,
|
|
|
|
|
Description: rev.descriptionForFeed(),
|
|
|
|
|
Created: rev.Time,
|
|
|
|
|
Updated: rev.Time,
|
2021-05-09 09:36:39 +00:00
|
|
|
|
Link: &feeds.Link{Href: cfg.URL + rev.bestLink()},
|
2020-12-08 15:15:32 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return feed
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-01 17:12:16 +00:00
|
|
|
|
// RecentChangesRSS creates recent changes feed in RSS format.
|
2020-12-08 15:15:32 +00:00
|
|
|
|
func RecentChangesRSS() (string, error) {
|
|
|
|
|
return recentChangesFeed().ToRss()
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-01 17:12:16 +00:00
|
|
|
|
// RecentChangesAtom creates recent changes feed in Atom format.
|
2020-12-08 15:15:32 +00:00
|
|
|
|
func RecentChangesAtom() (string, error) {
|
|
|
|
|
return recentChangesFeed().ToAtom()
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-01 17:12:16 +00:00
|
|
|
|
// RecentChangesJSON creates recent changes feed in JSON format.
|
2020-12-08 15:15:32 +00:00
|
|
|
|
func RecentChangesJSON() (string, error) {
|
|
|
|
|
return recentChangesFeed().ToJSON()
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-01 17:12:16 +00:00
|
|
|
|
// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice.
|
2021-02-20 16:50:25 +00:00
|
|
|
|
func RecentChanges(n int) []Revision {
|
2020-09-26 18:19:17 +00:00
|
|
|
|
var (
|
2021-05-03 18:31:19 +00:00
|
|
|
|
out, err = silentGitsh(
|
2020-09-26 18:19:17 +00:00
|
|
|
|
"log", "--oneline", "--no-merges",
|
2020-11-18 13:27:18 +00:00
|
|
|
|
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
2020-10-03 12:59:53 +00:00
|
|
|
|
"--max-count="+strconv.Itoa(n),
|
2020-09-26 18:19:17 +00:00
|
|
|
|
)
|
|
|
|
|
revs []Revision
|
|
|
|
|
)
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, line := range strings.Split(out.String(), "\n") {
|
|
|
|
|
revs = append(revs, parseRevisionLine(line))
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-03 18:31:19 +00:00
|
|
|
|
log.Printf("Found %d recent changes", len(revs))
|
2021-02-20 16:50:25 +00:00
|
|
|
|
return revs
|
2020-09-26 18:19:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 14:21:34 +00:00
|
|
|
|
// FileChanged tells you if the file has been changed.
|
|
|
|
|
func FileChanged(path string) bool {
|
|
|
|
|
_, err := gitsh("diff", "--exit-code", path)
|
|
|
|
|
return err != nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-29 17:54:57 +00:00
|
|
|
|
// Revisions returns slice of revisions for the given hypha name.
|
2020-08-28 19:10:46 +00:00
|
|
|
|
func Revisions(hyphaName string) ([]Revision, error) {
|
2020-08-19 18:54:23 +00:00
|
|
|
|
var (
|
2021-05-03 18:31:19 +00:00
|
|
|
|
out, err = silentGitsh(
|
2020-08-19 18:54:23 +00:00
|
|
|
|
"log", "--oneline", "--no-merges",
|
2020-11-29 17:06:45 +00:00
|
|
|
|
// Hash, author email, author time, commit msg separated by tab
|
|
|
|
|
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
2020-10-25 13:50:14 +00:00
|
|
|
|
"--", hyphaName+".*",
|
2020-08-19 18:54:23 +00:00
|
|
|
|
)
|
|
|
|
|
revs []Revision
|
|
|
|
|
)
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, line := range strings.Split(out.String(), "\n") {
|
2020-09-29 15:04:22 +00:00
|
|
|
|
if line != "" {
|
|
|
|
|
revs = append(revs, parseRevisionLine(line))
|
|
|
|
|
}
|
2020-08-19 18:54:23 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-05-03 18:31:19 +00:00
|
|
|
|
log.Printf("Found %d revisions for ‘%s’\n", len(revs), hyphaName)
|
2020-08-19 18:54:23 +00:00
|
|
|
|
return revs, err
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-01 17:12:16 +00:00
|
|
|
|
// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
|
|
|
|
|
func WithRevisions(hyphaName string, revs []Revision) (html string) {
|
2020-11-29 17:06:45 +00:00
|
|
|
|
var (
|
|
|
|
|
currentYear int
|
|
|
|
|
currentMonth time.Month
|
|
|
|
|
)
|
|
|
|
|
for i, rev := range revs {
|
|
|
|
|
if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear {
|
|
|
|
|
currentYear = rev.Time.Year()
|
|
|
|
|
currentMonth = rev.Time.Month()
|
|
|
|
|
if i != 0 {
|
|
|
|
|
html += `
|
|
|
|
|
</ul>
|
|
|
|
|
</section>`
|
|
|
|
|
}
|
|
|
|
|
html += fmt.Sprintf(`
|
|
|
|
|
<section class="history__month">
|
|
|
|
|
<a href="#%[1]d-%[2]d" class="history__month-anchor">
|
|
|
|
|
<h2 id="%[1]d-%[2]d" class="history__month-title">%[3]s</h2>
|
|
|
|
|
</a>
|
|
|
|
|
<ul class="history__entries">`,
|
|
|
|
|
currentYear, currentMonth,
|
|
|
|
|
strconv.Itoa(currentYear)+" "+rev.Time.Month().String())
|
|
|
|
|
}
|
|
|
|
|
html += rev.asHistoryEntry(hyphaName)
|
|
|
|
|
}
|
|
|
|
|
return html
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (rev *Revision) asHistoryEntry(hyphaName string) (html string) {
|
|
|
|
|
author := ""
|
|
|
|
|
if rev.Username != "anon" {
|
|
|
|
|
author = fmt.Sprintf(`
|
2021-06-14 07:13:29 +00:00
|
|
|
|
<span class="history-entry__author">by <a href="/hypha/%[1]s/%[2]s" rel="author">%[2]s</span>`, cfg.UserHypha, rev.Username)
|
2020-11-29 17:06:45 +00:00
|
|
|
|
}
|
|
|
|
|
return fmt.Sprintf(`
|
|
|
|
|
<li class="history__entry">
|
|
|
|
|
<a class="history-entry" href="/rev/%[3]s/%[1]s">
|
|
|
|
|
<time class="history-entry__time">%[2]s</time>
|
2021-03-14 15:01:32 +00:00
|
|
|
|
<span class="history-entry__hash"><a href="/primitive-diff/%[3]s/%[1]s">%[3]s</a></span>
|
2020-11-29 17:06:45 +00:00
|
|
|
|
<span class="history-entry__msg">%[4]s</span>
|
|
|
|
|
</a>%[5]s
|
|
|
|
|
</li>
|
2021-01-21 13:49:22 +00:00
|
|
|
|
`, hyphaName, rev.timeToDisplay(), rev.Hash, rev.Message, author)
|
2020-11-29 17:06:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-01-21 13:49:22 +00:00
|
|
|
|
// Return time like mm-dd 13:42
|
|
|
|
|
func (rev *Revision) timeToDisplay() string {
|
|
|
|
|
D := rev.Time.Day()
|
2020-11-29 17:06:45 +00:00
|
|
|
|
h, m, _ := rev.Time.Clock()
|
2021-01-21 13:49:22 +00:00
|
|
|
|
return fmt.Sprintf("%02d — %02d:%02d", D, h, m)
|
2020-11-29 17:06:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-19 18:54:23 +00:00
|
|
|
|
// This regex is wrapped in "". For some reason, these quotes appear at some time and we have to get rid of them.
|
|
|
|
|
var revisionLinePattern = regexp.MustCompile("\"(.*)\t(.*)@.*\t(.*)\t(.*)\"")
|
|
|
|
|
|
2020-08-29 17:54:57 +00:00
|
|
|
|
func parseRevisionLine(line string) Revision {
|
|
|
|
|
results := revisionLinePattern.FindStringSubmatch(line)
|
|
|
|
|
return Revision{
|
|
|
|
|
Hash: results[1],
|
|
|
|
|
Username: results[2],
|
|
|
|
|
Time: *unixTimestampAsTime(results[3]),
|
|
|
|
|
Message: results[4],
|
|
|
|
|
}
|
2020-08-19 18:54:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-11 10:14:00 +00:00
|
|
|
|
// FileAtRevision shows how the file with the given file path looked at the commit with the hash. It may return an error if git fails.
|
2020-08-20 17:20:13 +00:00
|
|
|
|
func FileAtRevision(filepath, hash string) (string, error) {
|
2021-06-19 04:51:10 +00:00
|
|
|
|
out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, files.HyphaeDir()+"/"))
|
2021-05-11 10:14:00 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
2020-08-20 17:20:13 +00:00
|
|
|
|
return out.String(), err
|
2020-08-19 18:54:23 +00:00
|
|
|
|
}
|
2021-03-14 15:01:32 +00:00
|
|
|
|
|
2021-05-11 10:14:00 +00:00
|
|
|
|
// PrimitiveDiffAtRevision generates a plain-text diff for the given filepath at the commit with the given hash. It may return an error if git fails.
|
2021-03-14 15:01:32 +00:00
|
|
|
|
func PrimitiveDiffAtRevision(filepath, hash string) (string, error) {
|
2021-05-03 18:31:19 +00:00
|
|
|
|
out, err := silentGitsh("diff", "--unified=1", "--no-color", hash+"~", hash, "--", filepath)
|
2021-05-11 10:14:00 +00:00
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
2021-03-14 15:01:32 +00:00
|
|
|
|
return out.String(), err
|
|
|
|
|
}
|