Merge pull request #6740 from dolthub/zachmu/timestamp-commit2

Support for `DOLT_COMMITTER_DATE` and `DOLT_AUTHOR_DATE` environment vars
This commit is contained in:
Zach Musgrave
2023-09-28 16:34:19 -07:00
committed by GitHub
21 changed files with 276 additions and 57 deletions

View File

@@ -20,6 +20,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
)
func TestParseDate(t *testing.T) {
@@ -42,7 +44,7 @@ func TestParseDate(t *testing.T) {
for _, test := range tests {
t.Run(test.dateStr, func(t *testing.T) {
result, err := ParseDate(test.dateStr)
result, err := dconfig.ParseDate(test.dateStr)
if test.expErr {
assert.Error(t, err)

View File

@@ -19,7 +19,6 @@ import (
"fmt"
"regexp"
"strings"
"time"
"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
@@ -27,34 +26,6 @@ import (
const VerboseFlag = "verbose"
// 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",
}
// Parses a date string. Used by multiple commands.
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.")
}
// Parses the author flag for the commit method.
func ParseAuthor(authorStr string) (string, string, error) {
if len(authorStr) == 0 {

View File

@@ -23,6 +23,7 @@ import (
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/store/datas"
@@ -147,7 +148,7 @@ func (cmd InitCmd) Exec(ctx context.Context, commandStr string, args []string, d
t := time.Now()
if commitTimeStr, ok := apr.GetValue(cli.DateParam); ok {
var err error
t, err = cli.ParseDate(commitTimeStr)
t, err = dconfig.ParseDate(commitTimeStr)
if err != nil {
return HandleVErrAndExitCode(errhand.BuildDError("error: invalid date").AddCause(err).Build(), usage)

View File

@@ -200,7 +200,7 @@ func pullHelper(
continue
}
t := datas.CommitNowFunc()
t := datas.CommitterDate()
roots, err := dEnv.Roots(ctx)
if err != nil {

View File

@@ -0,0 +1,49 @@
// Copyright 2023 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 dconfig
import (
"errors"
"time"
)
// SupportedLayouts is the set of time string formats for configuration date strings.
// 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",
}
// ParseDate attempt to parse a date string into a time.Time object.
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.")
}

View File

@@ -38,4 +38,6 @@ const (
EnvEditTableBufferRows = "DOLT_EDIT_TABLE_BUFFER_ROWS"
EnvDisableFixedAccess = "DOLT_DISABLE_FIXED_ACCESS"
EnvDoltAssistAgree = "DOLT_ASSIST_AGREE"
EnvDoltAuthorDate = "DOLT_AUTHOR_DATE"
EnvDoltCommitterDate = "DOLT_COMMITTER_DATE"
)

View File

@@ -146,7 +146,7 @@ func (ddb *DoltDB) CSMetricsSummary() string {
// 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, initBranch, name, email string) error {
return ddb.WriteEmptyRepoWithCommitMetaGenerator(ctx, initBranch, datas.MakeCommitMetaGenerator(name, email, datas.CommitNowFunc()))
return ddb.WriteEmptyRepoWithCommitMetaGenerator(ctx, initBranch, datas.MakeCommitMetaGenerator(name, email, datas.CommitterDate()))
}
func (ddb *DoltDB) WriteEmptyRepoWithCommitMetaGenerator(ctx context.Context, initBranch string, commitMeta datas.CommitMetaGenerator) error {

View File

@@ -252,7 +252,7 @@ func (mr *MultiRepoTestSetup) CommitWithWorkingSet(dbName string) *doltdb.Commit
mergeParentCommits = []*doltdb.Commit{ws.MergeState().Commit()}
}
t := datas.CommitNowFunc()
t := datas.CommitterDate()
roots, err := dEnv.Roots(ctx)
if err != nil {
panic("couldn't get roots: " + err.Error())

View File

@@ -277,7 +277,7 @@ func InitEmptyClonedRepo(ctx context.Context, dEnv *env.DoltEnv) error {
return ErrEmailNotFound
}
err := dEnv.InitDBWithTime(ctx, types.Format_Default, name, email, initBranch, datas.CommitNowFunc())
err := dEnv.InitDBWithTime(ctx, types.Format_Default, name, email, initBranch, datas.CommitterDate())
if err != nil {
return fmt.Errorf("failed to init repo: %w", err)
}

View File

@@ -366,7 +366,7 @@ func (dEnv *DoltEnv) bestEffortDeleteAll(dir string) {
// InitRepo takes an empty directory and initializes it with a .dolt directory containing repo state, uncommitted license and readme, and creates a noms
// database with dolt structure.
func (dEnv *DoltEnv) InitRepo(ctx context.Context, nbf *types.NomsBinFormat, name, email, branchName string) error { // should remove name and email args
return dEnv.InitRepoWithTime(ctx, nbf, name, email, branchName, datas.CommitNowFunc())
return dEnv.InitRepoWithTime(ctx, nbf, name, email, branchName, datas.CommitterDate())
}
func (dEnv *DoltEnv) InitRepoWithTime(ctx context.Context, nbf *types.NomsBinFormat, name, email, branchName string, t time.Time) error { // should remove name and email args

View File

@@ -55,7 +55,7 @@ func NewMemoryDoltDB(ctx context.Context, initBranch string) (*doltdb.DoltDB, er
m := "memory"
branchRef := ref.NewBranchRef(initBranch)
err := ddb.WriteEmptyRepoWithCommitTimeAndDefaultBranch(ctx, m, m, datas.CommitNowFunc(), branchRef)
err := ddb.WriteEmptyRepoWithCommitTimeAndDefaultBranch(ctx, m, m, datas.CommitterDate(), branchRef)
if err != nil {
return nil, err
}

View File

@@ -20,16 +20,15 @@ import (
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
"github.com/dolthub/vitess/go/vt/proto/query"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
"github.com/dolthub/dolt/go/store/datas"
)
var hashType = types.MustCreateString(query.Type_TEXT, 32, sql.Collation_ascii_bin)
// doltCommit is the stored procedure version for the CLI command `dolt commit`.
func doltCommit(ctx *sql.Context, args ...string) (sql.RowIter, error) {
commitHash, skipped, err := doDoltCommit(ctx, args)
@@ -129,11 +128,13 @@ func doDoltCommit(ctx *sql.Context, args []string) (string, bool, error) {
t := ctx.QueryTime()
if commitTimeStr, ok := apr.GetValue(cli.DateParam); ok {
var err error
t, err = cli.ParseDate(commitTimeStr)
t, err = dconfig.ParseDate(commitTimeStr)
if err != nil {
return "", false, fmt.Errorf(err.Error())
}
} else if datas.CustomAuthorDate {
t = datas.AuthorDate()
}
if apr.Contains(cli.ForceFlag) {

View File

@@ -25,6 +25,7 @@ import (
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/libraries/doltcore/branch_control"
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
@@ -464,7 +465,7 @@ func createMergeSpec(ctx *sql.Context, sess *dsess.DoltSession, dbName string, a
t := ctx.QueryTime()
if commitTimeStr, ok := apr.GetValue(cli.DateParam); ok {
t, err = cli.ParseDate(commitTimeStr)
t, err = dconfig.ParseDate(commitTimeStr)
if err != nil {
return nil, err
}

View File

@@ -1741,12 +1741,12 @@ func (tcc *testCommitClock) Now() time.Time {
}
func installTestCommitClock(tcc *testCommitClock) func() {
oldNowFunc := datas.CommitNowFunc
oldNowFunc := datas.CommitterDate
oldCommitLoc := datas.CommitLoc
datas.CommitNowFunc = tcc.Now
datas.CommitterDate = tcc.Now
datas.CommitLoc = time.UTC
return func() {
datas.CommitNowFunc = oldNowFunc
datas.CommitterDate = oldNowFunc
datas.CommitLoc = oldCommitLoc
}
}

View File

@@ -1327,13 +1327,13 @@ func (tcc *testCommitClock) Now() time.Time {
}
func installTestCommitClock() func() {
oldNowFunc := datas.CommitNowFunc
oldNowFunc := datas.CommitterDate
oldCommitLoc := datas.CommitLoc
tcc := &testCommitClock{}
datas.CommitNowFunc = tcc.Now
datas.CommitterDate = tcc.Now
datas.CommitLoc = time.UTC
return func() {
datas.CommitNowFunc = oldNowFunc
datas.CommitterDate = oldNowFunc
datas.CommitLoc = oldCommitLoc
}
}

View File

@@ -17,9 +17,13 @@ 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"
)
@@ -41,9 +45,14 @@ var ErrNameNotConfigured = errors.New("Aborting commit due to empty committer na
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.")
var CommitNowFunc = time.Now
// 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
@@ -56,7 +65,36 @@ type CommitMeta struct {
// 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())
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
@@ -77,10 +115,10 @@ func NewCommitMetaWithUserTS(name, email, desc string, userTS time.Time) (*Commi
return nil, ErrEmptyCommitMessage
}
ms := uint64(CommitNowFunc().UnixMilli())
userMS := userTS.UnixMilli()
committerDateMillis := uint64(CommitterDate().UnixMilli())
authorDateMillis := userTS.UnixMilli()
return &CommitMeta{n, e, ms, d, userMS}, nil
return &CommitMeta{n, e, committerDateMillis, d, authorDateMillis}, nil
}
func getRequiredFromSt(st types.Struct, k string) (types.Value, error) {

View File

@@ -34,7 +34,7 @@ const (
tagMetaVersion = "1.0"
)
var TagNowFunc = CommitNowFunc
var TagNowFunc = CommitterDate
var TagLoc = CommitLoc
// TagMeta contains all the metadata that is associated with a tag within a data repo.

View File

@@ -215,3 +215,53 @@ SQL
[[ "$output" =~ "Log into DoltHub: dolt login" ]] || false
[[ "$output" =~ "OR add name to config: dolt config [--global|--local] --add user.name \"FIRST LAST\"" ]] || false
}
@test "commit: set author time with env var" {
dolt sql -q "CREATE table t (pk int primary key);"
dolt add t
TZ=PST+8 DOLT_AUTHOR_DATE='2023-09-26T12:34:56' dolt commit -m "adding table t"
run dolt_log_in_PST
[[ "$output" =~ 'Tue Sep 26 12:34:56' ]] || false
# --date should take precendent over the env var
TZ=PST+8 DOLT_AUTHOR_DATE='2023-09-26T12:34:56' dolt commit --date '2023-09-26T01:23:45' --allow-empty -m "empty commit"
run dolt_log_in_PST
[[ "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
}
@test "commit: set committer time with env var" {
dolt sql -q "CREATE table t (pk int primary key);"
dolt add t
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' dolt commit -m "adding table t"
run dolt_log_in_PST
[[ ! "$output" =~ 'Tue Sep 26 12:34:56' ]] || false
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt commit --allow-empty -m "empty commit"
run dolt_log_in_PST
[[ "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
run dolt_log_in_PST
[[ ! "$output" =~ 'Tue Sep 26 12:34:56' ]] || false
# We don't have a way to examine the committer time directly with dolt show or another user
# command. So we'll make two identical commits on two different branches and assert they get the
# same hash
dolt branch b1
dolt branch b2
dolt checkout b1
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt commit --allow-empty -m "empty commit"
get_head_commit
head1=`get_head_commit`
dolt checkout b2
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt commit --allow-empty -m "empty commit"
get_head_commit
head2=`get_head_commit`
[ "$head1" == "$head2" ]
}

View File

@@ -43,6 +43,10 @@ get_head_commit() {
dolt log -n 1 | grep -m 1 commit | cut -c 13-44
}
dolt_log_in_PST() {
TZ=PST+8 dolt log -n1
}
setup_no_dolt_init() {
export PATH=$PATH:~/go/bin
cd $BATS_TMPDIR

View File

@@ -1086,3 +1086,68 @@ SQL
[ $status -eq 0 ]
[[ "$output" =~ "2 tables changed, 3 rows added(+), 1 rows modified(*), 1 rows deleted(-)" ]] || false
}
@test "merge: setting DOLT_AUTHOR_DATE" {
dolt sql -q "CREATE table t (pk int primary key, col1 int);"
dolt sql -q "INSERT INTO t VALUES (1, 1), (2, 2);"
dolt commit -Am "add table t"
dolt checkout -b right
dolt sql -q "insert into t values (3, 3), (4, 4);"
dolt sql -q "delete from t where pk = 1;"
dolt commit -Am "right"
dolt checkout main
dolt sql -q "insert into t values (5, 5);"
dolt commit -Am "left"
TZ=PST+8 DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt merge right -m "merge right into main"
run dolt_log_in_PST
[[ "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
}
@test "merge: setting DOLT_AUTHOR_DATE and DOLT_COMMITTER_DATE" {
dolt sql -q "CREATE table t (pk int primary key, col1 int);"
dolt sql -q "INSERT INTO t VALUES (1, 1), (2, 2);"
dolt commit -Am "add table t"
dolt remote add local file://./remote
dolt push local main
dolt checkout -b right
dolt sql -q "insert into t values (3, 3), (4, 4);"
dolt sql -q "delete from t where pk = 1;"
dolt commit -Am "right"
dolt push local right
dolt checkout main
dolt sql -q "insert into t values (5, 5);"
dolt commit -Am "left"
dolt push local main
# We don't have any way to print the committer time of a commit, so instead we'll do the merge
# here and on a clone and assert that they get the same hash
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt merge right -m "merge right into main"
run dolt_log_in_PST
[[ "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
head1=`get_head_commit`
dolt clone file://./remote clone
cd clone
dolt fetch
dolt checkout right
dolt checkout main
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt merge right -m "merge right into main"
run dolt_log_in_PST
[[ "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
head2=`get_head_commit`
[ "$head1" == "$head2" ]
}

View File

@@ -75,9 +75,6 @@ teardown() {
dolt commit --allow-empty -m "a commit for main from repo2"
dolt push
dolt branch
run dolt checkout other
[ "$status" -eq 0 ]
[[ "$output" =~ "branch 'other' set up to track 'origin/other'." ]] || false
@@ -2493,3 +2490,41 @@ SQL
[ "$status" -ne 0 ]
[[ "$output" =~ "--prune option cannot be provided with a ref spec" ]] || false
}
@test "remotes: pull with DOLT_AUTHOR_DATE and DOLT_COMMITER_DATE doesn't overwrite commit timestamps" {
mkdir repo1
cd repo1
dolt init
dolt sql -q "create table t1(a int)"
dolt commit -Am "new table"
dolt branch b1
dolt remote add origin file://../remote1
dolt push origin main
dolt push origin b1
cd ..
dolt clone file://./remote1 repo2
cd repo2
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt fetch
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt pull
run dolt_log_in_PST
[[ ! "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt checkout b1
run dolt_log_in_PST
[[ ! "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
cd ../repo1
dolt checkout b1
dolt commit --allow-empty -m 'empty commit'
dolt push origin b1
cd ../repo2
TZ=PST+8 DOLT_COMMITTER_DATE='2023-09-26T12:34:56' DOLT_AUTHOR_DATE='2023-09-26T01:23:45' dolt pull
run dolt_log_in_PST
[[ ! "$output" =~ 'Tue Sep 26 01:23:45' ]] || false
}