Added dolt revert

This commit is contained in:
Daylon Wilkins
2021-08-11 12:14:25 -07:00
committed by Daylon Wilkins
parent c4a0f9acac
commit a6716c1490
7 changed files with 642 additions and 30 deletions

View File

@@ -100,17 +100,27 @@ type HiddenCommand interface {
type SubCommandHandler struct {
name string
description string
// Unspecified ONLY applies when no other command has been given. This is different from how a default command would
// function, as a command that doesn't exist for this sub handler will result in an error.
Unspecified Command
Subcommands []Command
hidden bool
}
// NewSubCommandHandler returns a new SubCommandHandler instance
func NewSubCommandHandler(name, description string, subcommands []Command) SubCommandHandler {
return SubCommandHandler{name, description, subcommands, false}
return SubCommandHandler{name, description, nil, subcommands, false}
}
// NewHiddenSubCommandHandler returns a new SubCommandHandler instance that is hidden from display
func NewHiddenSubCommandHandler(name, description string, subcommands []Command) SubCommandHandler {
return SubCommandHandler{name, description, subcommands, true}
return SubCommandHandler{name, description, nil, subcommands, true}
}
// NewSubCommandHandlerWithUnspecified returns a new SubCommandHandler that will invoke the unspecified command ONLY if
// no direct command is given.
func NewSubCommandHandlerWithUnspecified(name, description string, hidden bool, unspecified Command, subcommands []Command) SubCommandHandler {
return SubCommandHandler{name, description, unspecified, subcommands, hidden}
}
func (hc SubCommandHandler) Name() string {
@@ -134,43 +144,24 @@ func (hc SubCommandHandler) Hidden() bool {
}
func (hc SubCommandHandler) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int {
if len(args) < 1 {
if len(args) < 1 && hc.Unspecified == nil {
hc.printUsage(commandStr)
return 1
}
subCommandStr := strings.ToLower(strings.TrimSpace(args[0]))
var subCommandStr string
if len(args) > 0 {
subCommandStr = strings.ToLower(strings.TrimSpace(args[0]))
}
for _, cmd := range hc.Subcommands {
lwrName := strings.ToLower(cmd.Name())
if lwrName == subCommandStr {
cmdRequiresRepo := true
if rnrCmd, ok := cmd.(RepoNotRequiredCommand); ok {
cmdRequiresRepo = rnrCmd.RequiresRepo()
}
if cmdRequiresRepo && !hasHelpFlag(args) {
isValid := CheckEnvIsValid(dEnv)
if !isValid {
return 2
}
}
var evt *events.Event
if evtCmd, ok := cmd.(EventMonitoredCommand); ok {
evt = events.NewEvent(evtCmd.EventType())
ctx = events.NewContextForEvent(ctx, evt)
}
ret := cmd.Exec(ctx, commandStr+" "+subCommandStr, args[1:], dEnv)
if evt != nil {
events.GlobalCollector.CloseEventAndAdd(evt)
}
return ret
return hc.handleCommand(ctx, commandStr+" "+subCommandStr, cmd, args[1:], dEnv)
}
}
if hc.Unspecified != nil {
return hc.handleCommand(ctx, commandStr, hc.Unspecified, args, dEnv)
}
if !isHelp(subCommandStr) {
PrintErrln(color.RedString("Unknown Command " + subCommandStr))
@@ -180,6 +171,34 @@ func (hc SubCommandHandler) Exec(ctx context.Context, commandStr string, args []
return 1
}
func (hc SubCommandHandler) handleCommand(ctx context.Context, commandStr string, cmd Command, args []string, dEnv *env.DoltEnv) int {
cmdRequiresRepo := true
if rnrCmd, ok := cmd.(RepoNotRequiredCommand); ok {
cmdRequiresRepo = rnrCmd.RequiresRepo()
}
if cmdRequiresRepo && !hasHelpFlag(args) {
isValid := CheckEnvIsValid(dEnv)
if !isValid {
return 2
}
}
var evt *events.Event
if evtCmd, ok := cmd.(EventMonitoredCommand); ok {
evt = events.NewEvent(evtCmd.EventType())
ctx = events.NewContextForEvent(ctx, evt)
}
ret := cmd.Exec(ctx, commandStr, args, dEnv)
if evt != nil {
events.GlobalCollector.CloseEventAndAdd(evt)
}
return ret
}
// CheckEnvIsValid validates that a DoltEnv has been initialized properly and no errors occur during load, and prints
// error messages to the user if there are issues with the environment or if errors were encountered while loading it.
func CheckEnvIsValid(dEnv *env.DoltEnv) bool {

View File

@@ -0,0 +1,139 @@
// Copyright 2021 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 commands
import (
"context"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
)
var revertDocs = cli.CommandDocumentationContent{
ShortDesc: "Undo the changes introduced in a commit",
LongDesc: `Removes the changes made in a commit (or series of commits) from the working set, and then automatically commits the
result. This is done by way of a three-way merge. Given a specific commit (e.g. HEAD~1), this is similar to applying the
patch from HEAD~1..HEAD~2, giving us a patch of what to remove to effectively remove the influence of the specified
commit. If multiple commits are specified, then this process is repeated for each commit in the order specified. This
requires a clean working set.
For now, any conflicts or constraint violations that are brought by the merge cause the command to fail.`,
Synopsis: []string{
"<revision>...",
},
}
type RevertCmd struct{}
var _ cli.Command = RevertCmd{}
// Name implements the interface cli.Command.
func (cmd RevertCmd) Name() string {
return "revert"
}
// Description implements the interface cli.Command.
func (cmd RevertCmd) Description() string {
return "Undo the changes introduced in a commit."
}
// CreateMarkdown implements the interface cli.Command.
func (cmd RevertCmd) CreateMarkdown(fs filesys.Filesys, path, commandStr string) error {
ap := argparser.NewArgParser()
return CreateMarkdown(fs, path, cli.GetCommandDocumentation(commandStr, revertDocs, ap))
}
// createArgParser creates the argument parser for this command.
func (cmd RevertCmd) createArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"revision",
"The commit revisions. If multiple revisions are given, they're applied in the order given."})
return ap
}
// Exec implements the interface cli.Command.
func (cmd RevertCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int {
ap := cmd.createArgParser()
help, usage := cli.HelpAndUsagePrinters(cli.GetCommandDocumentation(commandStr, commitDocs, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
if apr.NArg() < 1 {
usage()
return 1
}
headRoot, err := dEnv.HeadRoot(ctx)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
workingRoot, err := dEnv.WorkingRoot(ctx)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
headHash, err := headRoot.HashOf()
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
workingHash, err := workingRoot.HashOf()
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
if !headHash.Equal(workingHash) {
cli.PrintErrln("You must commit any changes before using revert.")
return 1
}
headRef := dEnv.RepoState.CWBHeadRef()
commits := make([]*doltdb.Commit, apr.NArg())
for i, arg := range apr.Args() {
commitSpec, err := doltdb.NewCommitSpec(arg)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
commit, err := dEnv.DoltDB.Resolve(ctx, commitSpec, headRef)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
commits[i] = commit
}
workingRoot, revertMessage, err := merge.Revert(ctx, dEnv.DoltDB, workingRoot, commits)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
workingHash, err = workingRoot.HashOf()
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
if headHash.Equal(workingHash) {
cli.Println("No changes were made.")
return 0
}
err = dEnv.UpdateWorkingRoot(ctx, workingRoot)
if err != nil {
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
res := AddCmd{}.Exec(ctx, "add", []string{"-A"}, dEnv)
if res != 0 {
return res
}
return CommitCmd{}.Exec(ctx, "commit", []string{"-m", revertMessage}, dEnv)
}

View File

@@ -76,6 +76,7 @@ var doltCommand = cli.NewSubCommandHandler("dolt", "it's git for data", []cli.Co
commands.PullCmd{},
commands.FetchCmd{},
commands.CloneCmd{},
commands.RevertCmd{},
credcmds.Commands,
commands.LoginCmd{},
commands.VersionCmd{VersionStr: Version},
@@ -320,6 +321,7 @@ func commandNeedsMigrationCheck(args []string) bool {
for _, cmd := range []cli.Command{
commands.ResetCmd{},
commands.CommitCmd{},
commands.RevertCmd{},
commands.SqlCmd{},
sqlserver.SqlServerCmd{},
sqlserver.SqlClientCmd{},

View File

@@ -0,0 +1,85 @@
// Copyright 2021 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 merge
import (
"context"
"fmt"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
)
// Revert is a convenience function for a three-way merge. In particular, given some root and a collection of commits
// that are all parents of the root value, this applies a three-way merge with the following characteristics (assuming
// a commit is HEAD~1):
//
// Base: HEAD~1
// Ours: root
// Theirs: HEAD~2
//
// The root is updated with the merged result, and this process is repeated for each commit given, in the order given.
// Currently, we error on conflicts or constraint violations generated by the merge.
func Revert(ctx context.Context, ddb *doltdb.DoltDB, root *doltdb.RootValue, commits []*doltdb.Commit) (*doltdb.RootValue, string, error) {
revertMessage := "Revert"
for i, baseCommit := range commits {
if i > 0 {
revertMessage += " and"
}
baseRoot, err := baseCommit.GetRootValue()
if err != nil {
return nil, "", err
}
baseMeta, err := baseCommit.GetCommitMeta()
if err != nil {
return nil, "", err
}
revertMessage = fmt.Sprintf(`%s "%s"`, revertMessage, baseMeta.Description)
var theirRoot *doltdb.RootValue
if len(baseCommit.ParentRefs()) > 0 {
parentCM, err := ddb.ResolveParent(ctx, baseCommit, 0)
if err != nil {
return nil, "", err
}
theirRoot, err = parentCM.GetRootValue()
if err != nil {
return nil, "", err
}
} else {
theirRoot, err = doltdb.EmptyRootValue(ctx, ddb.ValueReadWriter())
if err != nil {
return nil, "", err
}
}
root, _, err = MergeRoots(ctx, root, theirRoot, baseRoot)
if err != nil {
return nil, "", err
}
if ok, err := root.HasConflicts(ctx); err != nil {
return nil, "", err
} else if ok {
return nil, "", fmt.Errorf("revert currently does not handle conflicts")
}
if ok, err := root.HasConstraintViolations(ctx); err != nil {
return nil, "", err
} else if ok {
return nil, "", fmt.Errorf("revert currently does not handle constraint violations")
}
}
return root, revertMessage, nil
}

View File

@@ -32,6 +32,7 @@ var DoltFunctions = []sql.Function{
sql.Function2{Name: DoltMergeBaseFuncName, Fn: NewMergeBase},
sql.FunctionN{Name: ConstraintsVerifyFuncName, Fn: NewConstraintsVerifyFunc},
sql.FunctionN{Name: ConstraintsVerifyAllFuncName, Fn: NewConstraintsVerifyAllFunc},
sql.FunctionN{Name: RevertFuncName, Fn: NewRevertFunc},
}
// These are the DoltFunctions that get exposed to Dolthub Api.

View File

@@ -0,0 +1,166 @@
// Copyright 2021 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 dfunctions
import (
"fmt"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
)
const (
RevertFuncName = "dolt_revert"
)
// RevertFunc represents the dolt function "dolt revert".
type RevertFunc struct {
expression.NaryExpression
}
var _ sql.Expression = (*RevertFunc)(nil)
// NewRevertFunc creates a new RevertFunc expression that reverts commits.
func NewRevertFunc(ctx *sql.Context, args ...sql.Expression) (sql.Expression, error) {
return &RevertFunc{expression.NaryExpression{ChildExpressions: args}}, nil
}
// Eval implements the Expression interface.
func (r *RevertFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
dbName := ctx.GetCurrentDatabase()
dSess := dsess.DSessFromSess(ctx.Session)
ddb, ok := dSess.GetDoltDB(ctx, dbName)
if !ok {
return nil, fmt.Errorf("dolt database could not be found")
}
workingSet, err := dSess.WorkingSet(ctx, dbName)
if err != nil {
return nil, err
}
workingRoot := workingSet.WorkingRoot()
headCommit, err := dSess.GetHeadCommit(ctx, dbName)
if err != nil {
return nil, err
}
headRoot, err := headCommit.GetRootValue()
if err != nil {
return nil, err
}
headHash, err := headRoot.HashOf()
if err != nil {
return nil, err
}
workingHash, err := workingRoot.HashOf()
if err != nil {
return nil, err
}
if !headHash.Equal(workingHash) {
return nil, fmt.Errorf("you must commit any changes before using revert")
}
headRef, err := dSess.CWBHeadRef(ctx, dbName)
if err != nil {
return nil, err
}
commits := make([]*doltdb.Commit, len(r.ChildExpressions))
for i, expr := range r.ChildExpressions {
res, err := expr.Eval(ctx, row)
if err != nil {
return nil, err
}
revisionStr, ok := res.(string)
if !ok {
return nil, sql.ErrUnexpectedType.New(i, fmt.Sprintf("%T", res))
}
commitSpec, err := doltdb.NewCommitSpec(revisionStr)
if err != nil {
return nil, err
}
commit, err := ddb.Resolve(ctx, commitSpec, headRef)
if err != nil {
return nil, err
}
commits[i] = commit
}
workingRoot, revertMessage, err := merge.Revert(ctx, ddb, workingRoot, commits)
if err != nil {
return nil, err
}
workingHash, err = workingRoot.HashOf()
if err != nil {
return nil, err
}
if !headHash.Equal(workingHash) {
err = dSess.SetRoot(ctx, dbName, workingRoot)
if err != nil {
return nil, err
}
stringType := typeinfo.StringDefaultType.ToSqlType()
commitFunc, err := NewDoltCommitFunc(ctx,
expression.NewLiteral("-a", stringType), expression.NewLiteral("-m", stringType), expression.NewLiteral(revertMessage, stringType))
if err != nil {
return nil, err
}
_, err = commitFunc.Eval(ctx, row)
if err != nil {
return nil, err
}
}
return 0, nil
}
// String implements the Stringer interface.
func (r *RevertFunc) String() string {
return fmt.Sprint("DOLT_REVERT()")
}
// IsNullable implements the Expression interface.
func (r *RevertFunc) IsNullable() bool {
return false
}
// Resolved implements the Expression interface.
func (r *RevertFunc) Resolved() bool {
for _, expr := range r.ChildExpressions {
if !expr.Resolved() {
return false
}
}
return true
}
func (r *RevertFunc) Type() sql.Type {
return sql.Int8
}
// Children implements the Expression interface.
func (r *RevertFunc) Children() []sql.Expression {
exprs := make([]sql.Expression, len(r.ChildExpressions))
for i := range exprs {
exprs[i] = r.ChildExpressions[i]
}
return exprs
}
// WithChildren implements the Expression interface.
func (r *RevertFunc) WithChildren(ctx *sql.Context, children ...sql.Expression) (sql.Expression, error) {
return NewRevertFunc(ctx, children...)
}

View File

@@ -0,0 +1,200 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helper/common.bash
setup() {
setup_common
dolt sql -q "CREATE TABLE test(pk BIGINT PRIMARY KEY, v1 BIGINT)"
dolt add -A
dolt commit -m "Created table"
dolt sql -q "INSERT INTO test VALUES (1, 1)"
dolt add -A
dolt commit -m "Inserted 1"
dolt sql -q "INSERT INTO test VALUES (2, 2)"
dolt add -A
dolt commit -m "Inserted 2"
dolt sql -q "INSERT INTO test VALUES (3, 3)"
dolt add -A
dolt commit -m "Inserted 3"
}
teardown() {
assert_feature_version
teardown_common
}
@test "revert: HEAD" {
dolt revert HEAD
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "$output" =~ "2,2" ]] || false
[[ "${#lines[@]}" = "3" ]] || false
}
@test "revert: HEAD~1" {
dolt revert HEAD~1
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "$output" =~ "3,3" ]] || false
[[ "${#lines[@]}" = "3" ]] || false
}
@test "revert: HEAD & HEAD~1" {
dolt revert HEAD HEAD~1
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "${#lines[@]}" = "2" ]] || false
}
@test "revert: has changes in the working set" {
dolt sql -q "INSERT INTO test VALUES (4, 4)"
run dolt revert HEAD
[ "$status" -eq "1" ]
[[ "$output" =~ "changes" ]] || false
}
@test "revert: conflicts" {
dolt sql -q "INSERT INTO test VALUES (4, 4)"
dolt add -A
dolt commit -m "Inserted 4"
dolt sql -q "REPLACE INTO test VALUES (4, 5)"
dolt add -A
dolt commit -m "Updated 4"
run dolt revert HEAD~1
[ "$status" -eq "1" ]
[[ "$output" =~ "conflict" ]] || false
}
@test "revert: constraint violations" {
dolt sql <<"SQL"
CREATE TABLE parent (pk BIGINT PRIMARY KEY, v1 BIGINT, INDEX(v1));
CREATE TABLE child (pk BIGINT PRIMARY KEY, v1 BIGINT, CONSTRAINT fk_name FOREIGN KEY (v1) REFERENCES parent (v1));
INSERT INTO parent VALUES (10, 1), (20, 2);
INSERT INTO child VALUES (1, 1), (2, 2);
SQL
dolt add -A
dolt commit -m "MC1"
dolt sql -q "DELETE FROM child WHERE pk = 2"
dolt add -A
dolt commit -m "MC2"
dolt sql -q "DELETE FROM parent WHERE pk = 20"
dolt add -A
dolt commit -m "MC3"
run dolt revert HEAD~1
[ "$status" -eq "1" ]
[[ "$output" =~ "constraint violation" ]] || false
}
@test "revert: too far back" {
run dolt revert HEAD~10
[ "$status" -eq "1" ]
[[ "$output" =~ "ancestor" ]] || false
}
@test "revert: no changes" {
run dolt revert HEAD~4
[ "$status" -eq "0" ]
[[ "$output" =~ "No changes were made" ]] || false
}
@test "revert: invalid hash" {
run dolt revert aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
[ "$status" -eq "1" ]
[[ "$output" =~ "hash" ]] || false
}
@test "revert: SQL HEAD" {
dolt sql -q "SELECT DOLT_REVERT('HEAD')"
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "$output" =~ "2,2" ]] || false
[[ "${#lines[@]}" = "3" ]] || false
}
@test "revert: SQL HEAD~1" {
dolt sql -q "SELECT DOLT_REVERT('HEAD~1')"
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "$output" =~ "3,3" ]] || false
[[ "${#lines[@]}" = "3" ]] || false
}
@test "revert: SQL HEAD & HEAD~1" {
dolt sql -q "SELECT DOLT_REVERT('HEAD', 'HEAD~1')"
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "${#lines[@]}" = "2" ]] || false
}
@test "revert: SQL has changes in the working set" {
dolt sql -q "INSERT INTO test VALUES (4, 4)"
run dolt sql -q "SELECT DOLT_REVERT('HEAD')"
[ "$status" -eq "1" ]
[[ "$output" =~ "changes" ]] || false
}
@test "revert: SQL conflicts" {
dolt sql -q "INSERT INTO test VALUES (4, 4)"
dolt add -A
dolt commit -m "Inserted 4"
dolt sql -q "REPLACE INTO test VALUES (4, 5)"
dolt add -A
dolt commit -m "Updated 4"
run dolt sql -q "SELECT DOLT_REVERT('HEAD~1')"
[ "$status" -eq "1" ]
[[ "$output" =~ "conflict" ]] || false
}
@test "revert: SQL constraint violations" {
dolt sql <<"SQL"
CREATE TABLE parent (pk BIGINT PRIMARY KEY, v1 BIGINT, INDEX(v1));
CREATE TABLE child (pk BIGINT PRIMARY KEY, v1 BIGINT, CONSTRAINT fk_name FOREIGN KEY (v1) REFERENCES parent (v1));
INSERT INTO parent VALUES (10, 1), (20, 2);
INSERT INTO child VALUES (1, 1), (2, 2);
SQL
dolt add -A
dolt commit -m "MC1"
dolt sql -q "DELETE FROM child WHERE pk = 2"
dolt add -A
dolt commit -m "MC2"
dolt sql -q "DELETE FROM parent WHERE pk = 20"
dolt add -A
dolt commit -m "MC3"
run dolt sql -q "SELECT DOLT_REVERT('HEAD~1')"
[ "$status" -eq "1" ]
[[ "$output" =~ "constraint violation" ]] || false
}
@test "revert: SQL too far back" {
run dolt sql -q "SELECT DOLT_REVERT('HEAD~10')"
[ "$status" -eq "1" ]
[[ "$output" =~ "ancestor" ]] || false
}
@test "revert: SQL no changes" {
dolt sql -q "SELECT DOLT_REVERT('HEAD~4')"
run dolt sql -q "SELECT * FROM test" -r=csv
[ "$status" -eq "0" ]
[[ "$output" =~ "pk,v1" ]] || false
[[ "$output" =~ "1,1" ]] || false
[[ "$output" =~ "2,2" ]] || false
[[ "$output" =~ "3,3" ]] || false
[[ "${#lines[@]}" = "4" ]] || false
}
@test "revert: SQL invalid hash" {
run dolt sql -q "SELECT DOLT_REVERT('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')"
[ "$status" -eq "1" ]
[[ "$output" =~ "hash" ]] || false
}