Files
dolt/go/store/datas/commit_meta.go

235 lines
6.7 KiB
Go

// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package datas
import (
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/sirupsen/logrus"
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
"github.com/dolthub/dolt/go/store/types"
)
const (
commitMetaNameKey = "name"
commitMetaEmailKey = "email"
commitMetaDescKey = "desc"
commitMetaTimestampKey = "timestamp"
commitMetaUserTSKey = "user_timestamp"
commitMetaVersionKey = "metaversion"
commitMetaStName = "metadata"
commitMetaVersion = "1.0"
)
const defaultInitialCommitMessage = "Initialize data repository"
var ErrNameNotConfigured = errors.New("Aborting commit due to empty committer name. Is your config set?")
var ErrEmailNotConfigured = errors.New("Aborting commit due to empty committer email. Is your config set?")
var ErrEmptyCommitMessage = errors.New("Aborting commit due to empty commit message.")
// CommitterDate is the function used to get the committer time when creating commits.
var CommitterDate = time.Now
var CommitLoc = time.Local
var AuthorDate = time.Now
var CustomAuthorDate bool
var AuthorLoc = time.Local
// CommitMeta contains all the metadata that is associated with a commit within a data repo.
type CommitMeta struct {
Name string
Email string
Timestamp uint64
Description string
UserTimestamp int64
}
// NewCommitMeta creates a CommitMeta instance from a name, email, and description and uses the current time for the
// timestamp
func NewCommitMeta(name, email, desc string) (*CommitMeta, error) {
return NewCommitMetaWithUserTS(name, email, desc, AuthorDate())
}
func init() {
committerDate := os.Getenv(dconfig.EnvDoltCommitterDate)
if committerDate != "" {
committerDate, err := dconfig.ParseDate(committerDate)
if err != nil {
logrus.Warnf("Unable to parse value for %s: %s. System time will be used instead.",
dconfig.EnvDoltCommitterDate, err.Error())
} else {
CommitterDate = func() time.Time {
return committerDate
}
}
}
authorDate := os.Getenv(dconfig.EnvDoltAuthorDate)
if authorDate != "" {
authorDate, err := dconfig.ParseDate(authorDate)
if err != nil {
logrus.Warnf("Unable to parse value for %s: %s. System time will be used instead.",
dconfig.EnvDoltAuthorDate, err.Error())
} else {
AuthorDate = func() time.Time {
return authorDate
}
CustomAuthorDate = true
}
}
}
// NewCommitMetaWithUserTS creates a user metadata
func NewCommitMetaWithUserTS(name, email, desc string, userTS time.Time) (*CommitMeta, error) {
n := strings.TrimSpace(name)
e := strings.TrimSpace(email)
d := strings.TrimSpace(desc)
if n == "" {
return nil, ErrNameNotConfigured
}
if e == "" {
return nil, ErrEmailNotConfigured
}
if d == "" {
return nil, ErrEmptyCommitMessage
}
committerDateMillis := uint64(CommitterDate().UnixMilli())
authorDateMillis := userTS.UnixMilli()
return &CommitMeta{n, e, committerDateMillis, d, authorDateMillis}, nil
}
func getRequiredFromSt(st types.Struct, k string) (types.Value, error) {
if v, ok, err := st.MaybeGet(k); err != nil {
return nil, err
} else if ok {
return v, nil
}
return nil, errors.New("Missing required field \"" + k + "\".")
}
func CommitMetaFromNomsSt(st types.Struct) (*CommitMeta, error) {
e, err := getRequiredFromSt(st, commitMetaEmailKey)
if err != nil {
return nil, err
}
n, err := getRequiredFromSt(st, commitMetaNameKey)
if err != nil {
return nil, err
}
d, err := getRequiredFromSt(st, commitMetaDescKey)
if err != nil {
return nil, err
}
ts, err := getRequiredFromSt(st, commitMetaTimestampKey)
if err != nil {
return nil, err
}
userTS, ok, err := st.MaybeGet(commitMetaUserTSKey)
if err != nil {
return nil, err
} else if !ok {
userTS = types.Int(int64(uint64(ts.(types.Uint))))
}
return &CommitMeta{
string(n.(types.String)),
string(e.(types.String)),
uint64(ts.(types.Uint)),
string(d.(types.String)),
int64(userTS.(types.Int)),
}, nil
}
func (cm *CommitMeta) toNomsStruct(nbf *types.NomsBinFormat) (types.Struct, error) {
metadata := types.StructData{
commitMetaNameKey: types.String(cm.Name),
commitMetaEmailKey: types.String(cm.Email),
commitMetaDescKey: types.String(cm.Description),
commitMetaTimestampKey: types.Uint(cm.Timestamp),
commitMetaVersionKey: types.String(commitMetaVersion),
commitMetaUserTSKey: types.Int(cm.UserTimestamp),
}
return types.NewStruct(nbf, commitMetaStName, metadata)
}
// Time returns the time at which the commit occurred
func (cm *CommitMeta) Time() time.Time {
return time.UnixMilli(cm.UserTimestamp)
}
// FormatTS takes the internal timestamp and turns it into a human readable string in the time.RubyDate format
// which looks like: "Mon Jan 02 15:04:05 -0700 2006"
func (cm *CommitMeta) FormatTS() string {
return cm.Time().In(CommitLoc).Format(time.RubyDate)
}
// String returns the human readable string representation of the commit data
func (cm *CommitMeta) String() string {
return fmt.Sprintf("name: %s, email: %s, timestamp: %s, description: %s", cm.Name, cm.Email, cm.FormatTS(), cm.Description)
}
// CommitMetaGenerator is an interface that generates a sequence of CommitMeta structs, and implements a predicate to check whether
// a proposed commit is acceptable.
type CommitMetaGenerator interface {
Next() (*CommitMeta, error)
IsGoodCommit(*Commit) bool
}
// The default implementation of CommitMetaGenerator, which generates a single commit which is always acceptable.
type simpleCommitMetaGenerator struct {
name, email string
timestamp time.Time
message string
alreadyGenerated bool
}
func (g *simpleCommitMetaGenerator) Next() (*CommitMeta, error) {
if g.alreadyGenerated {
return nil, fmt.Errorf("Called simpleCommitMetaGenerator.Next twice. This should never happen.")
}
g.alreadyGenerated = true
return NewCommitMetaWithUserTS(g.name, g.email, g.message, g.timestamp)
}
func (*simpleCommitMetaGenerator) IsGoodCommit(*Commit) bool {
return true
}
func MakeCommitMetaGenerator(name, email string, timestamp time.Time) CommitMetaGenerator {
return &simpleCommitMetaGenerator{name: name, email: email, timestamp: timestamp, message: defaultInitialCommitMessage, alreadyGenerated: false}
}