dmitri.shuralyov.com/app/changes/...

Implement Prev/Next Commit buttons.

Remove ListCommitsOptions from ListCommits. Expected usage is to always
get all commits, then find the one you're interested in. This is what's
needed to implement prev/next commit buttons, and more closely matches
what's returned by 3rd party APIs.

The button styling can be improved; to be done later.
dmitshur committed 7 years ago commit a5b4a3feff60ac75bfac3013c9d101b04b8b3382
Collapse all
_data/change-files.html.tmpl
@@ -15,11 +15,20 @@
	<header class="list-entry-header">
		<div style="display: flex;">
			<pre style="flex-grow: 1;"><strong>{{.Subject}}</strong>{{with .Body}}

{{.}}{{end}}</pre>
			<span>Button</span>
			{{with .PrevSHA}}
				<a href="{{.}}">{{octicon "arrow-left"}}</a>
			{{else}}
				<span style="color: gray;">{{octicon "arrow-left"}}</span>
			{{end}}
			{{with .NextSHA}}
				<a href="{{.}}">{{octicon "arrow-right"}}</a>
			{{else}}
				<span style="color: gray;">{{octicon "arrow-right"}}</span>
			{{end}}
		</div>
	</header>
	<div class="list-entry-body">
		<span style="display: inline-block; vertical-align: bottom; margin-right: 5px;">{{.Avatar}}</span>{{/*
		*/}}<span style="display: inline-block;">{{.User}} committed {{.Time}}</span>
cmd/changesdev/main.go
@@ -41,11 +41,11 @@ var httpFlag = flag.String("http", ":8080", "Listen for HTTP connections on this

func main() {
	flag.Parse()

	var service changes.Service
	switch 2 {
	switch 0 {
	case 0:
		cacheTransport := httpcache.NewMemoryCacheTransport()
		gerrit, err := gerrit.NewClient("https://go-review.googlesource.com/", &http.Client{Transport: cacheTransport})
		if err != nil {
			log.Fatalln(err)
main.go
@@ -9,10 +9,11 @@ import (
	"html/template"
	"io"
	"log"
	"net/http"
	"net/url"
	"os"
	"sort"
	"strconv"
	"strings"
	"time"

@@ -341,11 +342,11 @@ func (h *handler) ChangeCommitsHandler(w http.ResponseWriter, req *http.Request,
	}
	state.Change, err = h.is.Get(req.Context(), state.RepoSpec, state.IssueID)
	if err != nil {
		return err
	}
	cs, err := h.is.ListCommits(req.Context(), state.RepoSpec, state.IssueID, nil)
	cs, err := h.is.ListCommits(req.Context(), state.RepoSpec, state.IssueID)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	err = h.static.ExecuteTemplate(w, "change-commits.html.tmpl", &state)
@@ -362,10 +363,12 @@ func (h *handler) ChangeCommitsHandler(w http.ResponseWriter, req *http.Request,
	}
	_, err = io.WriteString(w, `</body></html>`)
	return err
}

// ChangeFilesHandler is the handler for "/{changeID}/files" and "/{changeID}/files/{commitID}" endpoints.
// commitID is empty string for all files, or the SHA of a single commit for single-commit view.
func (h *handler) ChangeFilesHandler(w http.ResponseWriter, req *http.Request, changeID uint64, commitID string) error {
	if req.Method != http.MethodGet {
		return httperror.Method{Allowed: []string{http.MethodGet}}
	}
	state, err := h.state(req, changeID)
@@ -374,30 +377,38 @@ func (h *handler) ChangeFilesHandler(w http.ResponseWriter, req *http.Request, c
	}
	state.Change, err = h.is.Get(req.Context(), state.RepoSpec, state.IssueID)
	if err != nil {
		return err
	}
	var (
		opt    *changes.ListCommitsOptions
		commit commitMessage
	)
	var commit commitMessage
	if commitID != "" {
		opt = &changes.ListCommitsOptions{
			Commit: commitID,
		}
		cs, err := h.is.ListCommits(req.Context(), state.RepoSpec, state.IssueID, opt)
		cs, err := h.is.ListCommits(req.Context(), state.RepoSpec, state.IssueID)
		if err != nil {
			return err
		}
		subject, body := splitCommitMessage(cs[0].Message)
		i := commitIndex(cs, commitID)
		if i == -1 {
			return os.ErrNotExist
		}
		subject, body := splitCommitMessage(cs[i].Message)
		commit = commitMessage{
			CommitHash: cs[0].SHA,
			CommitHash: cs[i].SHA,
			Subject:    subject,
			Body:       body,
			Author:     cs[0].Author,
			AuthorTime: cs[0].AuthorTime,
			Author:     cs[i].Author,
			AuthorTime: cs[i].AuthorTime,
		}
		if prev := i - 1; prev >= 0 {
			commit.PrevSHA = cs[prev].SHA
		}
		if next := i + 1; next < len(cs) {
			commit.NextSHA = cs[next].SHA
		}
	}
	var opt *changes.ListCommitsOptions
	if commitID != "" {
		opt = &changes.ListCommitsOptions{Commit: commitID}
	}
	rawDiff, err := h.is.GetDiff(req.Context(), state.RepoSpec, state.IssueID, opt)
	if err != nil {
		return err
	}
@@ -424,10 +435,21 @@ func (h *handler) ChangeFilesHandler(w http.ResponseWriter, req *http.Request, c
	}
	_, err = io.WriteString(w, `</body></html>`)
	return err
}

// commitIndex returns the index of commit with SHA equal to commitID,
// or -1 if not found.
func commitIndex(cs []changes.Commit, commitID string) int {
	for i := range cs {
		if cs[i].SHA == commitID {
			return i
		}
	}
	return -1
}

func (h *handler) state(req *http.Request, changeID uint64) (state, error) {
	// TODO: Caller still does a lot of work outside to calculate req.URL.Path by
	//       subtracting BaseURI from full original req.URL.Path. We should be able
	//       to compute it here internally by using req.RequestURI and BaseURI.
	reqPath := req.URL.Path
xxx.go
@@ -94,10 +94,12 @@ type commitMessage struct {
	CommitHash string
	Subject    string
	Body       string
	Author     users.User
	AuthorTime time.Time

	PrevSHA, NextSHA string // Empty if none.
}

func (c commitMessage) Avatar() template.HTML {
	return template.HTML(htmlg.RenderComponentsString(issuescomponent.Avatar{User: c.Author, Size: 24}))
}