func (r *Revision) Title() string {
	title := r.Comment
	if l := len(title); l >= 72 {
		title = title[:72]
		if l > 72 {
			title += "..."
		}
	}
	if i := bytes.IndexByte([]byte(title), '\n'); i > 0 {
		title = title[:i]
	}
	return title
}
Title will extract the title from the comment of the current Revision. First,
this will truncate the title to being 72 characters. If the comment was longer
than 72 characters, then the title will be suffixed with "...". If a LF
character can be found in the title, then the title will be truncated again
up to where that LF character occurs.
package mgrt
import (
	"bufio"
	"bytes"
	"database/sql"
	"errors"
	"io"
	"os"
	"strings"
	"time"
)
type node struct {
	val   int64
	rev   *Revision
	left  *node
	right *node
}
type Errors []error
type Revision struct {
	ID          string     
	Category    string     
	Author      string     
	Comment     string     
	SQL         string     
	PerformedAt time.Time  
}
type RevisionError struct {
	ID  string  
	Err error   
}
type Collection struct {
	len  int
	root *node
}
var (
	revisionIdFormat = "20060102150405"
	ErrInvalid = errors.New("revision id invalid")
	ErrPerformed = errors.New("revision performed")
	ErrNotFound = errors.New("revision not found")
)
func insertNode(n **node, val int64, r *Revision) {
	if (*n) == nil {
		(*n) = &node{
			val: val,
			rev: r,
		}
		return
	}
	if val < (*n).val {
		insertNode(&(*n).left, val, r)
		return
	}
	insertNode(&(*n).right, val, r)
}
func NewRevision(author, comment string) *Revision {
	return &Revision{
		ID:      time.Now().Format(revisionIdFormat),
		Author:  author,
		Comment: comment,
	}
}
func NewRevisionCategory(category, author, comment string) *Revision {
	rev := NewRevision(author, comment)
	rev.Category = category
	return rev
}
func RevisionPerformed(db *DB, rev *Revision) error {
	var count int64
	if _, err := time.Parse(revisionIdFormat, rev.ID); err != nil {
		return ErrInvalid
	}
	q := db.Parameterize("SELECT COUNT(id) FROM mgrt_revisions WHERE (id = ?)")
	if err := db.QueryRow(q, rev.Slug()).Scan(&count); err != nil {
		return &RevisionError{
			ID:  rev.Slug(),
			Err: err,
		}
	}
	if count > 0 {
		return &RevisionError{
			ID:  rev.Slug(),
			Err: ErrPerformed,
		}
	}
	return nil
}
func GetRevision(db *DB, id string) (*Revision, error) {
	var (
		rev Revision
		sec int64
	)
	q := "SELECT id, author, comment, sql, performed_at FROM mgrt_revisions WHERE (id = ?)"
	row := db.QueryRow(db.Parameterize(q), id)
	var categoryid string
	if err := row.Scan(&categoryid, &rev.Author, &rev.Comment, &rev.SQL, &sec); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, &RevisionError{
				ID:  categoryid,
				Err: ErrNotFound,
			}
		}
		return nil, err
	}
	parts := strings.Split(categoryid, "/")
	end := len(parts) - 1
	rev.ID = parts[end]
	rev.Category = strings.Join(parts[:end], "/")
	rev.PerformedAt = time.Unix(sec, 0)
	return &rev, nil
}
func GetRevisions(db *DB, n int) ([]*Revision, error) {
	count := int64(n)
	if n <= 0 {
		q0 := "SELECT COUNT(id) FROM mgrt_revisions"
		if err := db.QueryRow(q0).Scan(&count); err != nil {
			return nil, err
		}
	}
	revs := make([]*Revision, 0, int(count))
	q := "SELECT id, author, comment, sql, performed_at FROM mgrt_revisions ORDER BY id DESC LIMIT ?"
	rows, err := db.Query(db.Parameterize(q), count)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var (
			rev        Revision
			sec        int64
			categoryid string
		)
		err = rows.Scan(&categoryid, &rev.Author, &rev.Comment, &rev.SQL, &sec)
		if err != nil {
			return nil, err
		}
		parts := strings.Split(categoryid, "/")
		end := len(parts) - 1
		rev.ID = parts[end]
		rev.Category = strings.Join(parts[:end], "/")
		rev.PerformedAt = time.Unix(sec, 0)
		revs = append(revs, &rev)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return revs, nil
}
func PerformRevisions(db *DB, revs0 ...*Revision) error {
	var c Collection
	for _, rev := range revs0 {
		c.Put(rev)
	}
	errs := Errors(make([]error, 0, len(revs0)))
	revs := c.Slice()
	for _, rev := range revs {
		if err := rev.Perform(db); err != nil {
			if errors.Is(err, ErrPerformed) {
				errs = append(errs, err)
				continue
			}
			return err
		}
	}
	return errs.err()
}
func OpenRevision(path string) (*Revision, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return UnmarshalRevision(f)
}
func UnmarshalRevision(r io.Reader) (*Revision, error) {
	br := bufio.NewReader(r)
	rev := &Revision{}
	var (
		buf     []rune = make([]rune, 0)
		r0      rune
		inBlock bool
	)
	for {
		r, _, err := br.ReadRune()
		if err != nil {
			if err != io.EOF {
				return nil, err
			}
			rev.SQL = strings.TrimSpace(string(buf))
			break
		}
		if r == '*' {
			if r0 == '/' {
				inBlock = true
				continue
			}
		}
		if r == '/' {
			if r0 == '*' {
				rev.Comment = strings.TrimSpace(string(buf))
				buf = buf[0:0]
				inBlock = false
				continue
			}
		}
		if inBlock {
			if r == '\n' {
				if r0 == '\n' {
					if rev.ID == "" && rev.Author == "" {
						buf = buf[0:0]
					}
					goto cont
				}
				pos := -1
				for i, r := range buf {
					if r == ':' {
						pos = i
						break
					}
				}
				if pos < 0 {
					goto cont
				}
				if string(buf[pos-6:pos]) == "Author" {
					rev.Author = strings.TrimSpace(string(buf[pos+1:]))
					buf = buf[0:0]
					continue
				}
				if string(buf[pos-8:pos]) == "Revision" {
					rev.ID = strings.TrimSpace(string(buf[pos+1:]))
					buf = buf[0:0]
					continue
				}
			}
		}
		if r == '*' {
			peek, _, err := br.ReadRune()
			if err != nil {
				if err != io.EOF {
					return nil, err
				}
				continue
			}
			if peek == '/' {
				br.UnreadRune()
				r0 = r
				continue
			}
		}
	cont:
		buf = append(buf, r)
		r0 = r
	}
	parts := strings.Split(rev.ID, "/")
	end := len(parts)-1
	rev.ID = parts[len(parts)-1]
	rev.Category = strings.Join(parts[:end], "/")
	if _, err := time.Parse(revisionIdFormat, rev.ID); err != nil {
		return nil, ErrInvalid
	}
	return rev, nil
}
func (n *node) walk(visit func(*Revision)) {
	if n.left != nil {
		n.left.walk(visit)
	}
	visit(n.rev)
	if n.right != nil {
		n.right.walk(visit)
	}
}
func (e Errors) err() error {
	if len(e) == 0 {
		return nil
	}
	return e
}
func (e Errors) Error() string {
	var buf bytes.Buffer
	for _, err := range e {
		buf.WriteString(err.Error() + "\n")
	}
	return buf.String()
}
func (c *Collection) Put(r *Revision) error {
	if r.ID == "" {
		return ErrInvalid
	}
	t, err := time.Parse(revisionIdFormat, r.ID)
	if err != nil {
		return ErrInvalid
	}
	insertNode(&c.root, t.Unix(), r)
	c.len++
	return nil
}
func (c *Collection) Len() int { return c.len }
func (c *Collection) Slice() []*Revision {
	revs := make([]*Revision, 0, c.len)
	c.root.walk(func(r *Revision) {
		revs = append(revs, r)
	})
	return revs
}
func (e *RevisionError) Error() string {
	return "revision error " + e.ID + ": " + e.Err.Error()
}
func (e *RevisionError) Unwrap() error { return e.Err }
func (r *Revision) Slug() string {
	if r.Category != "" {
		return r.Category + "/" + r.ID
	}
	return r.ID
}
func (r *Revision) Perform(db *DB) error {
	if r.SQL == "" {
		return nil
	}
	if err := RevisionPerformed(db, r); err != nil {
		return err
	}
	if _, err := db.Exec(r.SQL); err != nil {
		return &RevisionError{
			ID: r.Slug(),
			Err: err,
		}
	}
	q := db.Parameterize("INSERT INTO mgrt_revisions (id, author, comment, sql, performed_at) VALUES (?, ?, ?, ?, ?)")
	if _, err := db.Exec(q, r.Slug(), r.Author, r.Comment, r.SQL, time.Now().Unix()); err != nil {
		return &RevisionError{
			ID: r.Slug(),
			Err: err,
		}
	}
	return nil
}