1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-13 05:50:27 +00:00

Make recent-changes feeds able to look farther back for revisions.

Before, grouped feeds would only use the last 30 revisions.
If part of a group was outside these revisions, it would be handled incorrectly.
Now, grouped feeds contain the last 30 groups, and use as many revisions as needed for this.
This commit is contained in:
Elias Bomberger 2021-10-26 22:45:38 -04:00
parent 6cb4161dd8
commit 8ed6dd15bf
2 changed files with 104 additions and 50 deletions

View File

@ -12,14 +12,16 @@ import (
"github.com/gorilla/feeds" "github.com/gorilla/feeds"
) )
const changeGroupMaxSize = 100
func recentChangesFeed(opts FeedOptions) *feeds.Feed { func recentChangesFeed(opts FeedOptions) *feeds.Feed {
feed := &feeds.Feed{ feed := &feeds.Feed{
Title: cfg.WikiName + " (recent changes)", Title: cfg.WikiName + " (recent changes)",
Link: &feeds.Link{Href: cfg.URL}, Link: &feeds.Link{Href: cfg.URL},
Description: "List of 30 recent changes on the wiki", Description: fmt.Sprintf("List of %d recent changes on the wiki", changeGroupMaxSize),
Updated: time.Now(), Updated: time.Now(),
} }
revs := RecentChanges(30) revs := newRecentChangesStream()
groups := opts.grouping.Group(revs) groups := opts.grouping.Group(revs)
for _, grp := range groups { for _, grp := range groups {
item := grp.feedItem(opts) item := grp.feedItem(opts)
@ -43,6 +45,7 @@ func RecentChangesJSON(opts FeedOptions) (string, error) {
return recentChangesFeed(opts).ToJSON() return recentChangesFeed(opts).ToJSON()
} }
// revisionGroup is a slice of revisions, ordered most recent first.
type revisionGroup []Revision type revisionGroup []Revision
func newRevisionGroup(rev Revision) revisionGroup { func newRevisionGroup(rev Revision) revisionGroup {
@ -70,25 +73,34 @@ func groupRevisionsByMonth(revs []Revision) (res []revisionGroup) {
return res return res
} }
// groupRevisionsByPeriodFromNow groups close-together revisions. // groupRevisionsByPeriodFromNow groups close-together revisions and returns the first changeGroupMaxSize (30) groups.
// If two revisions happened within period of each other, they are put in the same group. // If two revisions happened within period of each other, they are put in the same group.
func groupRevisionsByPeriod(revs []Revision, period time.Duration) (res []revisionGroup) { func groupRevisionsByPeriod(revs recentChangesStream, period time.Duration) (res []revisionGroup) {
if len(revs) == 0 { nextRev := revs.iterator()
rev, empty := nextRev()
if empty {
return res return res
} }
currTime := revs[0].Time currTime := rev.Time
currGroup := newRevisionGroup(revs[0]) currGroup := newRevisionGroup(rev)
for _, rev := range revs[1:] { for {
rev, done := nextRev()
if done {
return append(res, currGroup)
}
if currTime.Sub(rev.Time) < period && currGroup[0].Username == rev.Username { if currTime.Sub(rev.Time) < period && currGroup[0].Username == rev.Username {
currGroup.addRevision(rev) currGroup.addRevision(rev)
} else { } else {
res = append(res, currGroup) res = append(res, currGroup)
if len(res) == changeGroupMaxSize {
return res
}
currGroup = newRevisionGroup(rev) currGroup = newRevisionGroup(rev)
} }
currTime = rev.Time currTime = rev.Time
} }
return res
} }
func (grp revisionGroup) feedItem(opts FeedOptions) feeds.Item { func (grp revisionGroup) feedItem(opts FeedOptions) feeds.Item {
@ -154,7 +166,7 @@ func ParseFeedOptions(query url.Values) (FeedOptions, error) {
} }
type FeedGrouping interface { type FeedGrouping interface {
Group([]Revision) []revisionGroup Group(recentChangesStream) []revisionGroup
} }
func parseFeedGrouping(query url.Values) (FeedGrouping, error) { func parseFeedGrouping(query url.Values) (FeedGrouping, error) {
@ -171,8 +183,8 @@ func parseFeedGrouping(query url.Values) (FeedGrouping, error) {
type NormalFeedGrouping struct{} type NormalFeedGrouping struct{}
func (NormalFeedGrouping) Group(revs []Revision) (res []revisionGroup) { func (NormalFeedGrouping) Group(revs recentChangesStream) (res []revisionGroup) {
for _, rev := range revs { for _, rev := range revs.next(changeGroupMaxSize) {
res = append(res, newRevisionGroup(rev)) res = append(res, newRevisionGroup(rev))
} }
return res return res
@ -182,7 +194,7 @@ type PeriodFeedGrouping struct {
Period time.Duration Period time.Duration
} }
func (g PeriodFeedGrouping) Group(revs []Revision) (res []revisionGroup) { func (g PeriodFeedGrouping) Group(revs recentChangesStream) (res []revisionGroup) {
return groupRevisionsByPeriod(revs, g.Period) return groupRevisionsByPeriod(revs, g.Period)
} }
@ -195,8 +207,7 @@ const (
func parseFeedGroupOrder(query url.Values) (FeedGroupOrder, error) { func parseFeedGroupOrder(query url.Values) (FeedGroupOrder, error) {
switch query.Get("order") { switch query.Get("order") {
case "old-to-new": case "", "old-to-new":
case "":
return OldToNew, nil return OldToNew, nil
case "new-to-old": case "new-to-old":
return NewToOld, nil return NewToOld, nil

View File

@ -21,53 +21,96 @@ type Revision struct {
hyphaeAffectedBuf []string hyphaeAffectedBuf []string
} }
// gitLog calls `git log` and parses the results.
func gitLog(args ...string) ([]Revision, error) {
args = append([]string{
"log", "--abbrev-commit", "--no-merges",
"--pretty=format:%h\t%ae\t%at\t%s",
}, args...)
out, err := silentGitsh(args...)
if err != nil {
return nil, err
}
outStr := out.String()
if outStr == "" {
// if there are no commits to return
return nil, nil
}
var revs []Revision
for _, line := range strings.Split(outStr, "\n") {
revs = append(revs, parseRevisionLine(line))
}
return revs, nil
}
type recentChangesStream struct {
currHash string
}
func newRecentChangesStream() recentChangesStream {
// next returns the next n revisions from the stream, ordered most recent first.
// If there are less than n revisions remaining, it will return only those.
return recentChangesStream{currHash: ""}
}
func (stream *recentChangesStream) next(n int) []Revision {
args := []string{"--max-count=" + strconv.Itoa(n)}
if stream.currHash == "" {
args = append(args, "HEAD")
} else {
// currHash is the last revision from the last call, so skip it
args = append(args, "--skip=1", stream.currHash)
}
// I don't think this can fail, so ignore the error
res, _ := gitLog(args...)
if len(res) != 0 {
stream.currHash = res[len(res)-1].Hash
}
return res
}
// recentChangesIterator returns a function that returns successive revisions from the stream.
// It buffers revisions to avoid calling git every time.
func (stream recentChangesStream) iterator() func() (Revision, bool) {
var buf []Revision
return func() (Revision, bool) {
if len(buf) == 0 {
// no real reason to choose 30, just needs some large number
buf = stream.next(30)
if len(buf) == 0 {
// revs has no revisions left
return Revision{}, true
}
}
rev := buf[0]
buf = buf[1:]
return rev, false
}
}
// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice, ordered most recent first. // RecentChanges gathers an arbitrary number of latest changes in form of revisions slice, ordered most recent first.
func RecentChanges(n int) []Revision { func RecentChanges(n int) []Revision {
var ( stream := newRecentChangesStream()
out, err = silentGitsh( revs := stream.next(n)
"log", "--oneline", "--no-merges",
"--pretty=format:%h\t%ae\t%at\t%s",
"--max-count="+strconv.Itoa(n),
)
revs []Revision
)
if err == nil {
for _, line := range strings.Split(out.String(), "\n") {
revs = append(revs, parseRevisionLine(line))
}
}
log.Printf("Found %d recent changes", len(revs)) log.Printf("Found %d recent changes", len(revs))
return revs return revs
} }
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
func Revisions(hyphaName string) ([]Revision, error) {
revs, err := gitLog("--", hyphaName+".*")
log.Printf("Found %d revisions for %s\n", len(revs), hyphaName)
return revs, err
}
// FileChanged tells you if the file has been changed since the last commit. // FileChanged tells you if the file has been changed since the last commit.
func FileChanged(path string) bool { func FileChanged(path string) bool {
_, err := gitsh("diff", "--exit-code", path) _, err := gitsh("diff", "--exit-code", path)
return err != nil return err != nil
} }
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
func Revisions(hyphaName string) ([]Revision, error) {
var (
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
// Hash, author email, author time, commit msg separated by tab
"--pretty=format:%h\t%ae\t%at\t%s",
"--", hyphaName+".*",
)
revs []Revision
)
if err == nil {
for _, line := range strings.Split(out.String(), "\n") {
if line != "" {
revs = append(revs, parseRevisionLine(line))
}
}
}
log.Printf("Found %d revisions for %s\n", len(revs), hyphaName)
return revs, err
}
// Return time like dd — 13:42 // Return time like dd — 13:42
func (rev *Revision) timeToDisplay() string { func (rev *Revision) timeToDisplay() string {
D := rev.Time.Day() D := rev.Time.Day()