mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-14 03:00:17 -06:00
235 lines
6.7 KiB
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}
|
|
}
|