mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-21 02:57:46 -05:00
add date to commit and init
* add --date param support to commit * add --date param support to init
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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"),
|
||||
|
||||
Reference in New Issue
Block a user