func (ev *SyncEventMetadata) UnmarshalJSON(b []byte) error {
	type data SyncEventMetadata
	err := json.Unmarshal(b, (*data)(ev))
	if err != nil {
		return err
	}
	if ev.Commits == nil {
		ev.Commits = make([]Commit, len(ev.Revs))
		for i, rev := range ev.Revs {
			ev.Commits[i].Revision = rev
		}
	}
	return nil
}
Account for old events, which used the revisions field rather than commits
package event
import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"time"
	"github.com/fluxcd/flux/pkg/resource"
	"github.com/fluxcd/flux/pkg/update"
	"github.com/pkg/errors"
)
const (
	EventCommit       = "commit"
	EventSync         = "sync"
	EventRelease      = "release"
	EventAutoRelease  = "autorelease"
	EventAutomate     = "automate"
	EventDeautomate   = "deautomate"
	EventLock         = "lock"
	EventUnlock       = "unlock"
	EventUpdatePolicy = "update_policy"
	NoneOfTheAbove = "other"
	LogLevelDebug = "debug"
	LogLevelInfo  = "info"
	LogLevelWarn  = "warn"
	LogLevelError = "error"
)
type EventID int64
type Event struct {
	ID EventID `json:"id"`
	ServiceIDs []resource.ID `json:"serviceIDs"`
	Type string `json:"type"`
	StartedAt time.Time `json:"startedAt"`
	EndedAt time.Time `json:"endedAt"`
	LogLevel string `json:"logLevel"`
	Message string `json:"message,omitempty"`
	Metadata EventMetadata `json:"metadata,omitempty"`
}
type EventWriter interface {
	LogEvent(Event) error
}
func (e Event) WorkloadIDStrings() []string {
	var strWorkloadIDs []string
	for _, workloadID := range e.ServiceIDs {
		strWorkloadIDs = append(strWorkloadIDs, workloadID.String())
	}
	sort.Strings(strWorkloadIDs)
	return strWorkloadIDs
}
func (e Event) String() string {
	if e.Message != "" {
		return e.Message
	}
	strWorkloadIDs := e.WorkloadIDStrings()
	switch e.Type {
	case EventRelease:
		metadata := e.Metadata.(*ReleaseEventMetadata)
		strImageIDs := metadata.Result.ChangedImages()
		if len(strImageIDs) == 0 {
			strImageIDs = []string{"no image changes"}
		}
		if metadata.Spec.Type == "" || metadata.Spec.Type == ReleaseImageSpecType {
			for _, spec := range metadata.Spec.ReleaseImageSpec.ServiceSpecs {
				if spec == update.ResourceSpecAll {
					strWorkloadIDs = []string{"all workloads"}
					break
				}
			}
		}
		if len(strWorkloadIDs) == 0 {
			strWorkloadIDs = []string{"no workloads"}
		}
		var user string
		if metadata.Cause.User != "" {
			user = fmt.Sprintf(", by %s", metadata.Cause.User)
		}
		var msg string
		if metadata.Cause.Message != "" {
			msg = fmt.Sprintf(", with message %q", metadata.Cause.Message)
		}
		return fmt.Sprintf(
			"Released: %s to %s%s%s",
			strings.Join(strImageIDs, ", "),
			strings.Join(strWorkloadIDs, ", "),
			user,
			msg,
		)
	case EventAutoRelease:
		metadata := e.Metadata.(*AutoReleaseEventMetadata)
		strImageIDs := metadata.Result.ChangedImages()
		if len(strImageIDs) == 0 {
			strImageIDs = []string{"no image changes"}
		}
		return fmt.Sprintf(
			"Automated release of %s",
			strings.Join(strImageIDs, ", "),
		)
	case EventCommit:
		metadata := e.Metadata.(*CommitEventMetadata)
		svcStr := "<no changes>"
		if len(strWorkloadIDs) > 0 {
			svcStr = strings.Join(strWorkloadIDs, ", ")
		}
		return fmt.Sprintf("Commit: %s, %s", shortRevision(metadata.Revision), svcStr)
	case EventSync:
		metadata := e.Metadata.(*SyncEventMetadata)
		revStr := "<no revision>"
		if 0 < len(metadata.Commits) && len(metadata.Commits) <= 2 {
			revStr = shortRevision(metadata.Commits[0].Revision)
		} else if len(metadata.Commits) > 2 {
			revStr = fmt.Sprintf(
				"%s..%s",
				shortRevision(metadata.Commits[len(metadata.Commits)-1].Revision),
				shortRevision(metadata.Commits[0].Revision),
			)
		}
		svcStr := "no workloads changed"
		if len(strWorkloadIDs) > 0 {
			svcStr = strings.Join(strWorkloadIDs, ", ")
		}
		return fmt.Sprintf("Sync: %s, %s", revStr, svcStr)
	case EventAutomate:
		return fmt.Sprintf("Automated: %s", strings.Join(strWorkloadIDs, ", "))
	case EventDeautomate:
		return fmt.Sprintf("Deautomated: %s", strings.Join(strWorkloadIDs, ", "))
	case EventLock:
		return fmt.Sprintf("Locked: %s", strings.Join(strWorkloadIDs, ", "))
	case EventUnlock:
		return fmt.Sprintf("Unlocked: %s", strings.Join(strWorkloadIDs, ", "))
	case EventUpdatePolicy:
		return fmt.Sprintf("Updated policies: %s", strings.Join(strWorkloadIDs, ", "))
	default:
		return fmt.Sprintf("Unknown event: %s", e.Type)
	}
}
func shortRevision(rev string) string {
	if len(rev) <= 7 {
		return rev
	}
	return rev[:7]
}
type CommitEventMetadata struct {
	Revision string        `json:"revision,omitempty"`
	Spec     *update.Spec  `json:"spec"`
	Result   update.Result `json:"result,omitempty"`
}
func (c CommitEventMetadata) ShortRevision() string {
	return shortRevision(c.Revision)
}
type Commit struct {
	Revision string `json:"revision"`
	Message  string `json:"message"`
}
type ResourceError struct {
	ID    resource.ID
	Path  string
	Error string
}
type SyncEventMetadata struct {
	Revs    []string `json:"revisions,omitempty"`
	Commits []Commit `json:"commits,omitempty"`
	Includes map[string]bool `json:"includes,omitempty"`
	Errors []ResourceError `json:"errors,omitempty"`
	InitialSync bool `json:"initialSync,omitempty"`
}