add date to commit and init

* add --date param support to commit
* add --date param support to init
This commit is contained in:
Brian Hendriks
2019-12-06 15:01:46 -08:00
committed by GitHub
parent bc6347917b
commit 860e690aff
12 changed files with 181 additions and 33 deletions
+3 -1
View File
@@ -24,7 +24,9 @@ import (
"github.com/liquidata-inc/dolt/go/libraries/utils/argparser"
)
const allParam = "all"
const (
allParam = "all"
)
var addShortDesc = `Add table contents to the list of staged tables`
var addLongDesc = `This command updates the list of tables using the current content found in the working root, to prepare the content staged for the next commit. It adds the current content of existing tables as a whole or remove tables that do not exist in the working root anymore.
+3 -3
View File
@@ -55,12 +55,12 @@ type blameInfo struct {
Description string
// Timestamp is the timestamp of the commit which last modified the row
Timestamp uint64
Timestamp int64
}
// TimestampTime returns a time.Time object representing the blameInfo timestamp
func (bi *blameInfo) TimestampTime() time.Time {
return time.Unix(int64(bi.Timestamp/1000), 0)
return time.Unix(bi.Timestamp/1000, 0)
}
// TimestampString returns a string representing the blameInfo timestamp
@@ -465,7 +465,7 @@ func (bg *blameGraph) AssignBlame(rowPK types.Value, nbf *types.NomsBinFormat, c
CommitHash: commitHash.String(),
Author: meta.Name,
Description: meta.Description,
Timestamp: meta.Timestamp,
Timestamp: meta.UserTimestamp,
}
return nil
+52 -7
View File
@@ -17,8 +17,10 @@ package commands
import (
"bytes"
"context"
"errors"
"os"
"strings"
"time"
"github.com/fatih/color"
@@ -30,6 +32,12 @@ import (
"github.com/liquidata-inc/dolt/go/libraries/utils/editor"
)
const (
allowEmptyFlag = "allow-empty"
dateParam = "date"
commitMessageArg = "message"
)
var commitShortDesc = `Record changes to the repository`
var commitLongDesc = "Stores the current contents of the staged tables in a new commit along with a log message from the " +
"user describing the changes.\n" +
@@ -38,20 +46,20 @@ var commitLongDesc = "Stores the current contents of the staged tables in a new
"before using the commit command (Note: even modified files must be \"added\");" +
"\n" +
"The log message can be added with the parameter -m <msg>. If the -m parameter is not provided an editor will be " +
"opened where you can review the commit and provide a log message.\n"
"opened where you can review the commit and provide a log message.\n" +
"\n" +
"The commit timestamp can be modified using the --date parameter. Dates can be specified in the formats YYYY-MM-DD " +
"YYYY-MM-DDTHH:MM:SS, or YYYY-MM-DDTHH:MM:SSZ07:00 (where 07:00 is the time zone offset)."
var commitSynopsis = []string{
"[-m <msg>]",
"[options]",
}
func Commit(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int {
const (
allowEmptyFlag = "allow-empty"
commitMessageArg = "message"
)
ap := argparser.NewArgParser()
ap.SupportsString(commitMessageArg, "m", "msg", "Use the given <msg> as the commit message.")
ap.SupportsFlag(allowEmptyFlag, "", "Allow recording a commit that has the exact same data as its sole parent. This is usually a mistake, so it is disabled by default. This option bypasses that safety.")
ap.SupportsString(dateParam, "", "date", "Specify the date used in the commit. If not specified the current system time is used.")
help, usage := cli.HelpAndUsagePrinters(commandStr, commitShortDesc, commitLongDesc, commitSynopsis, ap)
apr := cli.ParseArgs(ap, args, help)
@@ -60,7 +68,17 @@ func Commit(ctx context.Context, commandStr string, args []string, dEnv *env.Dol
msg = getCommitMessageFromEditor(ctx, dEnv)
}
err := actions.CommitStaged(ctx, dEnv, msg, apr.Contains(allowEmptyFlag))
t := time.Now()
if commitTimeStr, ok := apr.GetValue(dateParam); ok {
var err error
t, err = parseDate(commitTimeStr)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: invalid date").AddCause(err).Build(), usage)
}
}
err := actions.CommitStaged(ctx, dEnv, msg, t, apr.Contains(allowEmptyFlag))
if err == nil {
// if the commit was successful, print it out using the log command
return Log(ctx, "log", []string{"-n=1"}, dEnv)
@@ -69,6 +87,33 @@ func Commit(ctx context.Context, commandStr string, args []string, dEnv *env.Dol
return handleCommitErr(err, usage)
}
// we are more permissive than what is documented.
var supportedLayouts = []string{
"2006/01/02",
"2006/01/02T15:04:05",
"2006/01/02T15:04:05Z07:00",
"2006.01.02",
"2006.01.02T15:04:05",
"2006.01.02T15:04:05Z07:00",
"2006-01-02",
"2006-01-02T15:04:05",
"2006-01-02T15:04:05Z07:00",
}
func parseDate(dateStr string) (time.Time, error) {
for _, layout := range supportedLayouts {
t, err := time.Parse(layout, dateStr)
if err == nil {
return t, nil
}
}
return time.Time{}, errors.New("error: '" + dateStr + "' is not in a supported format.")
}
func handleCommitErr(err error, usage cli.UsagePrinter) int {
if err == nil {
return 0
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2019 Liquidata, 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 commands
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseDate(t *testing.T) {
tests := []struct {
dateStr string
expTime time.Time
expErr bool
}{
{"1901/09/30", time.Date(1901, 9, 30, 0, 0, 0, 0, time.UTC), false},
{"2019/01/20", time.Date(2019, 1, 20, 0, 0, 0, 0, time.UTC), false},
{"2019-1-20", time.Date(2019, 1, 20, 0, 0, 0, 0, time.UTC), true},
{"2019.01.20", time.Date(2019, 1, 20, 0, 0, 0, 0, time.UTC), false},
{"2019/01/20T13:49:59", time.Date(2019, 1, 20, 13, 49, 59, 0, time.UTC), false},
{"2019-01-20T13:49:59", time.Date(2019, 1, 20, 13, 49, 59, 0, time.UTC), false},
{"2019.01.20T13:49:59", time.Date(2019, 1, 20, 13, 49, 59, 0, time.UTC), false},
{"2019.01.20T13:49", time.Date(2019, 1, 20, 13, 49, 59, 0, time.UTC), true},
{"2019.01.20T13", time.Date(2019, 1, 20, 13, 49, 59, 0, time.UTC), true},
{"2019.01", time.Date(2019, 1, 20, 13, 49, 59, 0, time.UTC), true},
}
for _, test := range tests {
t.Run(test.dateStr, func(t *testing.T) {
result, err := parseDate(test.dateStr)
if test.expErr {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, result, test.expTime)
}
})
}
}
+2 -2
View File
@@ -93,8 +93,8 @@ In order to filter which diffs are displayed <b>--where key=value</b> can be use
`
var diffSynopsis = []string{
"[options] [options] [<commit>] [<tables>...]",
"[options] [options] <commit> <commit> [<tables>...]",
"[options] [<commit>] [<tables>...]",
"[options] <commit> <commit> [<tables>...]",
}
type diffArgs struct {
+13 -1
View File
@@ -16,6 +16,7 @@ package commands
import (
"context"
"time"
"github.com/fatih/color"
@@ -45,6 +46,7 @@ func Init(ctx context.Context, commandStr string, args []string, dEnv *env.DoltE
ap := argparser.NewArgParser()
ap.SupportsString(usernameParamName, "", "name", "The name used in commits to this repo. If not provided will be taken from \""+env.UserNameKey+"\" in the global config.")
ap.SupportsString(emailParamName, "", "email", "The email address used. If not provided will be taken from \""+env.UserEmailKey+"\" in the global config.")
ap.SupportsString(dateParam, "", "date", "Specify the date used in the initial commit. If not specified the current system time is used.")
help, usage := cli.HelpAndUsagePrinters(commandStr, initShortDesc, initLongDesc, initSynopsis, ap)
apr := cli.ParseArgs(ap, args, help)
@@ -74,7 +76,17 @@ func Init(ctx context.Context, commandStr string, args []string, dEnv *env.DoltE
return 1
}
err := dEnv.InitRepo(context.Background(), types.Format_Default, name, email)
t := time.Now()
if commitTimeStr, ok := apr.GetValue(dateParam); ok {
var err error
t, err = parseDate(commitTimeStr)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: invalid date").AddCause(err).Build(), usage)
}
}
err := dEnv.InitRepoWithTime(context.Background(), types.Format_Default, name, email, t)
if err != nil {
cli.PrintErrln(color.RedString("Failed to initialize directory as a data repo. %s", err.Error()))
+31 -11
View File
@@ -28,6 +28,7 @@ const (
commitMetaEmailKey = "email"
commitMetaDescKey = "desc"
commitMetaTimestampKey = "timestamp"
commitMetaUserTSKey = "user_timestamp"
commitMetaVersionKey = "metaversion"
metaVersion = "1.0"
@@ -38,18 +39,25 @@ var CommitLoc = 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
Name string
Email string
Timestamp uint64
Description string
UserTimestamp int64
}
var milliToNano = uint64(time.Millisecond / time.Nanosecond)
var secToMilli = uint64(time.Second / time.Millisecond)
var uMilliToNano = uint64(time.Millisecond / time.Nanosecond)
var milliToNano = int64(time.Millisecond / time.Nanosecond)
var secToMilli = int64(time.Second / time.Millisecond)
// 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, CommitNowFunc())
}
// 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)
@@ -59,9 +67,11 @@ func NewCommitMeta(name, email, desc string) (*CommitMeta, error) {
}
ns := uint64(CommitNowFunc().UnixNano())
ms := ns / milliToNano
ms := ns / uMilliToNano
return &CommitMeta{n, e, ms, d}, nil
userMS := userTS.UnixNano() / milliToNano
return &CommitMeta{n, e, ms, d, userMS}, nil
}
func getRequiredFromSt(st types.Struct, k string) (types.Value, error) {
@@ -99,11 +109,20 @@ func commitMetaFromNomsSt(st types.Struct) (*CommitMeta, error) {
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
}
@@ -114,6 +133,7 @@ func (cm *CommitMeta) toNomsStruct(nbf *types.NomsBinFormat) (types.Struct, erro
commitMetaDescKey: types.String(cm.Description),
commitMetaTimestampKey: types.Uint(cm.Timestamp),
commitMetaVersionKey: types.String(metaVersion),
commitMetaUserTSKey: types.Int(cm.UserTimestamp),
}
return types.NewStruct(nbf, "metadata", metadata)
@@ -121,9 +141,9 @@ func (cm *CommitMeta) toNomsStruct(nbf *types.NomsBinFormat) (types.Struct, erro
// Time returns the time at which the commit occurred
func (cm *CommitMeta) Time() time.Time {
seconds := cm.Timestamp / secToMilli
nanos := (cm.Timestamp % secToMilli) * milliToNano
return time.Unix(int64(seconds), int64(nanos))
seconds := cm.UserTimestamp / secToMilli
nanos := (cm.UserTimestamp % secToMilli) * milliToNano
return time.Unix(seconds, nanos)
}
// FormatTS takes the internal timestamp and turns it into a human readable string in the time.RubyDate format
+6 -1
View File
@@ -19,6 +19,7 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/liquidata-inc/dolt/go/libraries/doltcore/dbfactory"
"github.com/liquidata-inc/dolt/go/libraries/doltcore/ref"
@@ -97,6 +98,10 @@ func LoadDoltDBWithParams(ctx context.Context, nbf *types.NomsBinFormat, urlStr
// WriteEmptyRepo will create initialize the given db with a master branch which points to a commit which has valid
// metadata for the creation commit, and an empty RootValue.
func (ddb *DoltDB) WriteEmptyRepo(ctx context.Context, name, email string) error {
return ddb.WriteEmptyRepoWithCommitTime(ctx, name, email, CommitNowFunc())
}
func (ddb *DoltDB) WriteEmptyRepoWithCommitTime(ctx context.Context, name, email string, t time.Time) error {
// precondition checks
name = strings.TrimSpace(name)
email = strings.TrimSpace(email)
@@ -127,7 +132,7 @@ func (ddb *DoltDB) WriteEmptyRepo(ctx context.Context, name, email string) error
return err
}
cm, _ := NewCommitMeta(name, email, "Initialize data repository")
cm, _ := NewCommitMetaWithUserTS(name, email, "Initialize data repository", t)
parentSet, err := types.NewSet(ctx, ddb.db)
+4 -3
View File
@@ -17,6 +17,7 @@ package actions
import (
"context"
"sort"
"time"
"github.com/pkg/errors"
@@ -50,7 +51,7 @@ func getNameAndEmail(cfg *env.DoltCliConfig) (string, string, error) {
return name, email, nil
}
func CommitStaged(ctx context.Context, dEnv *env.DoltEnv, msg string, allowEmpty bool) error {
func CommitStaged(ctx context.Context, dEnv *env.DoltEnv, msg string, date time.Time, allowEmpty bool) error {
staged, notStaged, err := GetTableDiffs(ctx, dEnv)
if msg == "" {
@@ -94,7 +95,7 @@ func CommitStaged(ctx context.Context, dEnv *env.DoltEnv, msg string, allowEmpty
return err
}
meta, noCommitMsgErr := doltdb.NewCommitMeta(name, email, msg)
meta, noCommitMsgErr := doltdb.NewCommitMetaWithUserTS(name, email, msg, date)
if noCommitMsgErr != nil {
return ErrEmptyCommitMessage
}
@@ -144,7 +145,7 @@ func TimeSortedCommits(ctx context.Context, ddb *doltdb.DoltDB, commit *doltdb.C
return false
}
return metaI.Timestamp > metaJ.Timestamp
return metaI.UserTimestamp > metaJ.UserTimestamp
})
if sortErr != nil {
+1 -1
View File
@@ -74,7 +74,7 @@ func (q *q) AddPendingIfUnseen(ctx context.Context, id hash.Hash) error {
if err != nil {
return err
}
if pendingMeta.Timestamp > commitMeta.Timestamp {
if pendingMeta.UserTimestamp > commitMeta.UserTimestamp {
break
}
}
+10 -2
View File
@@ -174,6 +174,10 @@ func (dEnv *DoltEnv) bestEffortDeleteAll(dir string) {
// InitRepo takes an empty directory and initializes it with a .dolt directory containing repo state, and creates a noms
// database with dolt structure.
func (dEnv *DoltEnv) InitRepo(ctx context.Context, nbf *types.NomsBinFormat, name, email string) error { // should remove name and email args
return dEnv.InitRepoWithTime(ctx, nbf, name, email, doltdb.CommitNowFunc())
}
func (dEnv *DoltEnv) InitRepoWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email string, t time.Time) error { // should remove name and email args
doltDir, err := dEnv.createDirectories(".")
if err != nil {
@@ -183,7 +187,7 @@ func (dEnv *DoltEnv) InitRepo(ctx context.Context, nbf *types.NomsBinFormat, nam
err = dEnv.configureRepo(doltDir)
if err == nil {
err = dEnv.initDBAndState(ctx, nbf, name, email)
err = dEnv.initDBAndStateWithTime(ctx, nbf, name, email, t)
}
if err != nil {
@@ -239,6 +243,10 @@ func (dEnv *DoltEnv) configureRepo(doltDir string) error {
}
func (dEnv *DoltEnv) initDBAndState(ctx context.Context, nbf *types.NomsBinFormat, name, email string) error {
return dEnv.initDBAndStateWithTime(ctx, nbf, name, email, doltdb.CommitNowFunc())
}
func (dEnv *DoltEnv) initDBAndStateWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email string, t time.Time) error {
var err error
dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr)
@@ -246,7 +254,7 @@ func (dEnv *DoltEnv) initDBAndState(ctx context.Context, nbf *types.NomsBinForma
return err
}
err = dEnv.DoltDB.WriteEmptyRepo(ctx, name, email)
err = dEnv.DoltDB.WriteEmptyRepoWithCommitTime(ctx, name, email, t)
if err != nil {
return doltdb.ErrNomsIO
@@ -707,7 +707,7 @@ var BasicSelectTests = []SelectTest{
Name: "select * from log system table",
Query: "select * from dolt_log",
ExpectedRows: []row.Row{mustRow(row.New(types.Format_7_18, LogSchema, row.TaggedValues{
0: types.String("uq724j7bn8u01u8j6mdgr0bhsq0bpead"),
0: types.String("73aupasq0va8lic1t5703nacn6n6kb8g"),
1: types.String("billy bob"),
2: types.String("bigbillieb@fake.horse"),
3: types.String("Thu Jan 01 00:00:00 +0000 1970"),