1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-11 21:10:26 +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"
)
const changeGroupMaxSize = 100
func recentChangesFeed(opts FeedOptions) *feeds.Feed {
feed := &feeds.Feed{
Title: cfg.WikiName + " (recent changes)",
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(),
}
revs := RecentChanges(30)
revs := newRecentChangesStream()
groups := opts.grouping.Group(revs)
for _, grp := range groups {
item := grp.feedItem(opts)
@ -43,6 +45,7 @@ func RecentChangesJSON(opts FeedOptions) (string, error) {
return recentChangesFeed(opts).ToJSON()
}
// revisionGroup is a slice of revisions, ordered most recent first.
type revisionGroup []Revision
func newRevisionGroup(rev Revision) revisionGroup {
@ -70,25 +73,34 @@ func groupRevisionsByMonth(revs []Revision) (res []revisionGroup) {
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.
func groupRevisionsByPeriod(revs []Revision, period time.Duration) (res []revisionGroup) {
if len(revs) == 0 {
func groupRevisionsByPeriod(revs recentChangesStream, period time.Duration) (res []revisionGroup) {
nextRev := revs.iterator()
rev, empty := nextRev()
if empty {
return res
}
currTime := revs[0].Time
currGroup := newRevisionGroup(revs[0])
for _, rev := range revs[1:] {
currTime := rev.Time
currGroup := newRevisionGroup(rev)
for {
rev, done := nextRev()
if done {
return append(res, currGroup)
}
if currTime.Sub(rev.Time) < period && currGroup[0].Username == rev.Username {
currGroup.addRevision(rev)
} else {
res = append(res, currGroup)
if len(res) == changeGroupMaxSize {
return res
}
currGroup = newRevisionGroup(rev)
}
currTime = rev.Time
}
return res
}
func (grp revisionGroup) feedItem(opts FeedOptions) feeds.Item {
@ -154,7 +166,7 @@ func ParseFeedOptions(query url.Values) (FeedOptions, error) {
}
type FeedGrouping interface {
Group([]Revision) []revisionGroup
Group(recentChangesStream) []revisionGroup
}
func parseFeedGrouping(query url.Values) (FeedGrouping, error) {
@ -171,8 +183,8 @@ func parseFeedGrouping(query url.Values) (FeedGrouping, error) {
type NormalFeedGrouping struct{}
func (NormalFeedGrouping) Group(revs []Revision) (res []revisionGroup) {
for _, rev := range revs {
func (NormalFeedGrouping) Group(revs recentChangesStream) (res []revisionGroup) {
for _, rev := range revs.next(changeGroupMaxSize) {
res = append(res, newRevisionGroup(rev))
}
return res
@ -182,7 +194,7 @@ type PeriodFeedGrouping struct {
Period time.Duration
}
func (g PeriodFeedGrouping) Group(revs []Revision) (res []revisionGroup) {
func (g PeriodFeedGrouping) Group(revs recentChangesStream) (res []revisionGroup) {
return groupRevisionsByPeriod(revs, g.Period)
}
@ -195,8 +207,7 @@ const (
func parseFeedGroupOrder(query url.Values) (FeedGroupOrder, error) {
switch query.Get("order") {
case "old-to-new":
case "":
case "", "old-to-new":
return OldToNew, nil
case "new-to-old":
return NewToOld, nil

View File

@ -21,53 +21,96 @@ type Revision struct {
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.
func RecentChanges(n int) []Revision {
var (
out, err = silentGitsh(
"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))
}
}
stream := newRecentChangesStream()
revs := stream.next(n)
log.Printf("Found %d recent changes", len(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.
func FileChanged(path string) bool {
_, err := gitsh("diff", "--exit-code", path)
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
func (rev *Revision) timeToDisplay() string {
D := rev.Time.Day()