Migrate dolt conflicts resolve to use only SQL.

This commit is contained in:
Pavel Safronov
2023-06-27 14:09:31 -07:00
parent 7dc69f4c3f
commit df95cbc367
6 changed files with 325 additions and 471 deletions

View File

@@ -15,22 +15,13 @@
package cnfcmds
import (
"context"
"errors"
"fmt"
"io"
"strings"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/commands"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/cmd/dolt/commands/engine"
"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/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
"github.com/dolthub/dolt/go/libraries/utils/set"
"github.com/gocraft/dbr/v2"
"github.com/gocraft/dbr/v2/dialect"
)
type AutoResolveStrategy int
@@ -40,418 +31,35 @@ const (
AutoResolveStrategyTheirs
)
var ErrConfSchIncompatible = errors.New("the conflict schema's columns are not equal to the current schema's columns, please resolve manually")
// AutoResolveAll resolves all conflicts in all tables according to the given
// |strategy|.
func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy) error {
root, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
tbls, err := root.GetTableNames(ctx)
if err != nil {
return err
}
return AutoResolveTables(ctx, dEnv, strategy, tbls)
}
// AutoResolveTables resolves all conflicts in the given tables according to the
// given |strategy|.
func AutoResolveTables(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy, tbls []string) error {
ws, err := dEnv.WorkingSet(ctx)
if err != nil {
return err
}
func AutoResolveTables(queryist cli.Queryist, sqlCtx *sql.Context, strategy AutoResolveStrategy, tbls []string) error {
// schema conflicts
if ws.MergeActive() {
ws, err = ResolveSchemaConflicts(ctx, dEnv.DoltDB, ws, tbls, strategy)
if err != nil {
return err
}
if err = dEnv.UpdateWorkingSet(ctx, ws); err != nil {
return err
}
}
// data conflicts
for _, tblName := range tbls {
err = ResolveDataConflicts(ctx, dEnv, ws, tblName, strategy)
if err != nil {
return err
}
}
return nil
}
func ResolveSchemaConflicts(ctx context.Context, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, tables []string, strategy AutoResolveStrategy) (*doltdb.WorkingSet, error) {
tblSet := set.NewStrSet(tables)
updates := make(map[string]*doltdb.Table)
err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table string, conflict doltdb.SchemaConflict) error {
if !tblSet.Contains(table) {
return nil
}
ours, theirs := conflict.GetConflictingTables()
for _, tableName := range tbls {
resolveQuery := "CALL dolt_conflicts_resolve(?, ?)"
var resolveParams []interface{}
switch strategy {
case AutoResolveStrategyOurs:
updates[table] = ours
resolveParams = []interface{}{"--ours", tableName}
case AutoResolveStrategyTheirs:
updates[table] = theirs
resolveParams = []interface{}{"--theirs", tableName}
default:
panic("unhandled auto resolve strategy")
return errors.New("invalid auto resolve strategy")
}
return nil
})
if err != nil {
return nil, err
}
root := ws.WorkingRoot()
for name, tbl := range updates {
if root, err = root.PutTable(ctx, name, tbl); err != nil {
return nil, err
}
}
// clear resolved schema conflicts
var unmerged []string
for _, tbl := range ws.MergeState().TablesWithSchemaConflicts() {
if tblSet.Contains(tbl) {
continue
}
unmerged = append(unmerged, tbl)
}
return ws.WithWorkingRoot(root).WithUnmergableTables(unmerged), nil
}
// ResolveDataConflicts resolves all conflicts in the given table according to the given
// |strategy|. It errors if the schema of the conflict version you are choosing
// differs from the current schema.
func ResolveDataConflicts(ctx context.Context, dEnv *env.DoltEnv, ws *doltdb.WorkingSet, tblName string, strategy AutoResolveStrategy) (err error) {
tbl, ok, err := ws.WorkingRoot().GetTable(ctx, tblName)
if err != nil {
return err
}
if !ok {
return doltdb.ErrTableNotFound
}
has, err := tbl.HasConflicts(ctx)
if err != nil {
return err
} else if !has {
return nil
}
sch, err := tbl.GetSchema(ctx)
if err != nil {
return err
}
_, ourSch, theirSch, err := tbl.GetConflictSchemas(ctx, tblName)
if err != nil {
return err
}
switch strategy {
case AutoResolveStrategyOurs:
if !schema.ColCollsAreEqual(sch.GetAllCols(), ourSch.GetAllCols()) {
return ErrConfSchIncompatible
}
case AutoResolveStrategyTheirs:
if !schema.ColCollsAreEqual(sch.GetAllCols(), theirSch.GetAllCols()) {
return ErrConfSchIncompatible
}
default:
panic("unhandled auto resolve strategy")
}
before, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
eng, dbName, err := engine.NewSqlEngineForEnv(ctx, dEnv)
if err != nil {
return err
}
sqlCtx, err := eng.NewLocalContext(ctx)
if err != nil {
return err
}
sqlCtx.SetCurrentDatabase(dbName)
v, err := getFirstColumn(sqlCtx, eng, "SELECT @@DOLT_ALLOW_COMMIT_CONFLICTS;")
if err != nil {
return err
}
oldAllowCommitConflicts, ok := v.(int8)
if !ok {
return fmt.Errorf("unexpected type of @DOLT_ALLOW_COMMIT_CONFLICTS: %T", v)
}
// Resolving conflicts for one table, will not resolve conflicts on another.
err = execute(sqlCtx, eng, "SET DOLT_ALLOW_COMMIT_CONFLICTS = 1;")
if err != nil {
return err
}
defer func() {
err2 := execute(sqlCtx, eng, fmt.Sprintf("SET DOLT_ALLOW_COMMIT_CONFLICTS = %d", oldAllowCommitConflicts))
if err == nil {
err = err2
}
}()
if !schema.IsKeyless(sch) {
err = resolvePkTable(sqlCtx, tblName, sch, strategy, eng)
} else {
err = resolveKeylessTable(sqlCtx, tblName, sch, strategy, eng)
}
if err != nil {
return err
}
err = execute(sqlCtx, eng, "COMMIT;")
if err != nil {
return err
}
after, err := dEnv.WorkingRoot(ctx)
if err != nil {
return err
}
err = validateConstraintViolations(ctx, before, after, tblName)
if err != nil {
return err
}
return nil
}
func resolvePkTable(ctx *sql.Context, tblName string, sch schema.Schema, strategy AutoResolveStrategy, eng *engine.SqlEngine) error {
identCols := getIdentifyingColumnNames(sch)
allCols := sch.GetAllCols().GetColumnNames()
switch strategy {
case AutoResolveStrategyOurs:
err := oursPKResolver(ctx, eng, tblName)
q, err := dbr.InterpolateForDialect(resolveQuery, resolveParams, dialect.MySQL)
if err != nil {
return err
return fmt.Errorf("error interpolating resolve conflicts query for table %s: %w", tableName, err)
}
case AutoResolveStrategyTheirs:
err := theirsPKResolver(ctx, eng, tblName, allCols, identCols)
_, err = commands.GetRowsForSql(queryist, sqlCtx, q)
if err != nil {
return err
return fmt.Errorf("error resolving conflicts for table %s: %w", tableName, err)
}
}
return nil
}
func resolveKeylessTable(sqlCtx *sql.Context, tblName string, sch schema.Schema, strategy AutoResolveStrategy, eng *engine.SqlEngine) error {
allCols := sch.GetAllCols().GetColumnNames()
baseCols := strings.Join(quoteWithPrefix(allCols, "base_"), ", ")
ourCols := strings.Join(quoteWithPrefix(allCols, "our_"), ", ")
theirCols := strings.Join(quoteWithPrefix(allCols, "their_"), ", ")
confTblName := fmt.Sprintf("`dolt_conflicts_%s`", tblName)
selectConfsQ := fmt.Sprintf(
`SELECT
%s,
%s,
%s,
our_diff_type,
their_diff_type,
base_cardinality,
our_cardinality,
their_cardinality
FROM %s;`, baseCols, ourCols, theirCols, confTblName)
sqlSch, itr, err := eng.Query(sqlCtx, selectConfsQ)
if err != nil {
return err
}
s, err := sqlutil.FromDoltSchema(tblName, sch)
if err != nil {
return err
}
confSplitter, err := newConflictSplitter(sqlSch[:len(sqlSch)-3], s.Schema)
if err != nil {
return err
}
for {
r, err := itr.Next(sqlCtx)
if err == io.EOF {
break
}
if err != nil {
return err
}
ourCardinality := r[len(r)-2].(uint64)
theirCardinality := r[len(r)-1].(uint64)
split, err := confSplitter.splitConflictRow(r[:len(r)-3])
if err != nil {
return err
}
// In a keyless conflict, the non-null versions have equivalent rows.
// The first version in the split is always non-null.
rowVals := split[0].row
var rowDelta int64
switch strategy {
case AutoResolveStrategyOurs:
rowDelta = 0
case AutoResolveStrategyTheirs:
rowDelta = int64(theirCardinality) - int64(ourCardinality)
}
var stmt string
var n int64
if rowDelta > 0 {
stmt, err = sqlfmt.SqlRowAsInsertStmt(rowVals, tblName, sch)
if err != nil {
return err
}
n = rowDelta
} else if rowDelta < 0 {
stmt, err = sqlfmt.SqlRowAsDeleteStmt(rowVals, tblName, sch, 1)
if err != nil {
return err
}
n = rowDelta * -1
}
for i := int64(0); i < n; i++ {
err = execute(sqlCtx, eng, stmt)
if err != nil {
return err
}
}
}
err = execute(sqlCtx, eng, fmt.Sprintf("DELETE FROM %s", confTblName))
if err != nil {
return err
}
return nil
}
func oursPKResolver(sqlCtx *sql.Context, eng *engine.SqlEngine, tblName string) error {
del := fmt.Sprintf("DELETE FROM `dolt_conflicts_%s`;", tblName)
err := execute(sqlCtx, eng, del)
if err != nil {
return err
}
return nil
}
func getIdentifyingColumnNames(sch schema.Schema) []string {
if schema.IsKeyless(sch) {
return sch.GetAllCols().GetColumnNames()
} else {
return sch.GetPKCols().GetColumnNames()
}
}
func theirsPKResolver(sqlCtx *sql.Context, eng *engine.SqlEngine, tblName string, allCols []string, identCols []string) error {
dstCols := strings.Join(quoted(allCols), ", ")
srcCols := strings.Join(quoteWithPrefix(allCols, "their_"), ", ")
cnfTbl := fmt.Sprintf("`dolt_conflicts_%s`", tblName)
qName := fmt.Sprintf("`%s`", tblName)
q1 := fmt.Sprintf(
`
REPLACE INTO %s (%s) (
SELECT %s
FROM %s
WHERE their_diff_type = 'modified' OR their_diff_type = 'added'
);
`, qName, dstCols, srcCols, cnfTbl)
err := execute(sqlCtx, eng, q1)
if err != nil {
return err
}
selCols := strings.Join(coalesced(identCols), ", ")
q2 := fmt.Sprintf("SELECT %s from `dolt_conflicts_%s` WHERE their_diff_type = 'removed';", selCols, tblName)
sch, itr, err := eng.Query(sqlCtx, q2)
if err != nil {
return err
}
for {
row, err := itr.Next(sqlCtx)
if err != nil && err != io.EOF {
return err
}
if err == io.EOF {
break
}
deleteFilter, err := buildFilter(identCols, row, sch)
if err != nil {
return err
}
del := fmt.Sprintf("DELETE from `%s` WHERE %s;", tblName, deleteFilter)
err = execute(sqlCtx, eng, del)
if err != nil {
return err
}
}
q3 := fmt.Sprintf("DELETE FROM `dolt_conflicts_%s`;", tblName)
err = execute(sqlCtx, eng, q3)
if err != nil {
return err
}
return nil
}
func buildFilter(columns []string, row sql.Row, rowSch sql.Schema) (string, error) {
if len(columns) != len(row) {
return "", errors.New("cannot build filter since number of columns does not match number of values")
}
vals, err := sqlfmt.SqlRowAsStrings(row, rowSch)
if err != nil {
return "", err
}
var b strings.Builder
var seen bool
for i, v := range vals {
_, _ = fmt.Fprintf(&b, "`%s` = %s", columns[i], v)
if seen {
_, _ = fmt.Fprintf(&b, "AND `%s` = %s", columns[i], v)
}
seen = true
}
return b.String(), nil
}
func coalesced(cols []string) []string {
out := make([]string, len(cols))
for i := range out {
out[i] = fmt.Sprintf("coalesce(`base_%s`, `our_%s`, `their_%s`)", cols[i], cols[i], cols[i])
}
return out
}
func quoted(cols []string) []string {
out := make([]string, len(cols))
for i := range out {
out[i] = fmt.Sprintf("`%s`", cols[i])
}
return out
}
func quoteWithPrefix(arr []string, prefix string) []string {
out := make([]string, len(arr))
for i := range arr {
@@ -459,47 +67,3 @@ func quoteWithPrefix(arr []string, prefix string) []string {
}
return out
}
func execute(ctx *sql.Context, eng *engine.SqlEngine, query string) error {
_, itr, err := eng.Query(ctx, query)
if err != nil {
return err
}
_, err = itr.Next(ctx)
for err != nil && err != io.EOF {
return err
}
return nil
}
func getFirstColumn(ctx *sql.Context, eng *engine.SqlEngine, query string) (interface{}, error) {
_, itr, err := eng.Query(ctx, query)
if err != nil {
return nil, err
}
r, err := itr.Next(ctx)
if err != nil {
return nil, err
}
if len(r) == 0 {
return nil, fmt.Errorf("no columns returned")
}
return r[0], nil
}
func validateConstraintViolations(ctx context.Context, before, after *doltdb.RootValue, table string) error {
tables, err := after.GetTableNames(ctx)
if err != nil {
return err
}
violators, err := merge.GetForeignKeyViolatedTables(ctx, after, before, set.NewStrSet(tables))
if err != nil {
return err
}
if violators.Size() > 0 {
return fmt.Errorf("resolving conflicts for table %s created foreign key violations", table)
}
return nil
}

View File

@@ -16,6 +16,7 @@ package cnfcmds
import (
"context"
"github.com/dolthub/go-mysql-server/sql"
"strings"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
@@ -93,13 +94,17 @@ func (cmd ResolveCmd) Exec(ctx context.Context, commandStr string, args []string
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, resDocumentation, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
if dEnv.IsLocked() {
return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(env.ErrActiveServerLock.New(dEnv.LockFile())), usage)
queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx)
if err != nil {
return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
}
if closeFunc != nil {
defer closeFunc()
}
var verr errhand.VerboseError
if apr.ContainsAny(autoResolverParams...) {
verr = autoResolve(ctx, apr, dEnv)
verr = autoResolve(queryist, sqlCtx, apr)
} else {
verr = errhand.BuildDError("--ours or --theirs must be supplied").SetPrintUsage().Build()
}
@@ -107,7 +112,7 @@ func (cmd ResolveCmd) Exec(ctx context.Context, commandStr string, args []string
return commands.HandleVErrAndExitCode(verr, usage)
}
func autoResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) errhand.VerboseError {
func autoResolve(queryist cli.Queryist, sqlCtx *sql.Context, apr *argparser.ArgParseResults) errhand.VerboseError {
funcFlags := apr.FlagsEqualTo(autoResolverParams, true)
if funcFlags.Size() > 1 {
@@ -121,12 +126,9 @@ func autoResolve(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.
autoResolveStrategy := autoResolveStrategies[autoResolveFlag]
var err error
tbls := apr.Args
if len(tbls) == 1 && tbls[0] == "." {
err = AutoResolveAll(ctx, dEnv, autoResolveStrategy)
} else {
err = AutoResolveTables(ctx, dEnv, autoResolveStrategy, tbls)
}
err = AutoResolveTables(queryist, sqlCtx, autoResolveStrategy, tbls)
if err != nil {
return errhand.BuildDError("error: failed to resolve").AddCause(err).Build()
}

View File

@@ -167,7 +167,7 @@ func createPrintData(err error, queryist cli.Queryist, sqlCtx *sql.Context, show
return nil, err
}
statusRows, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_status;")
statusRows, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_status;")
if err != nil {
return nil, err
}
@@ -320,7 +320,7 @@ func getRemoteInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName string
// get remote branch
remoteBranchRef := fmt.Sprintf("remotes/%s/%s", remoteName, remoteBranchName)
q := fmt.Sprintf("select name, hash from dolt_remote_branches where name = '%s';", remoteBranchRef)
remoteBranches, err := getRowsForSql(queryist, sqlCtx, q)
remoteBranches, err := GetRowsForSql(queryist, sqlCtx, q)
if err != nil {
return ahead, behind, err
}
@@ -330,7 +330,7 @@ func getRemoteInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName string
remoteBranchCommit := remoteBranches[0][1].(string)
q = fmt.Sprintf("call dolt_count_commits('--from', '%s', '--to', '%s')", currentBranchCommit, remoteBranchCommit)
rows, err := getRowsForSql(queryist, sqlCtx, q)
rows, err := GetRowsForSql(queryist, sqlCtx, q)
if err != nil {
return ahead, behind, err
}
@@ -357,7 +357,7 @@ func getLocalBranchInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName s
currentBranchCommit = ""
remoteBranchName = ""
localBranches, err := getRowsForSql(queryist, sqlCtx, "select name, hash, remote, branch from dolt_branches;")
localBranches, err := GetRowsForSql(queryist, sqlCtx, "select name, hash, remote, branch from dolt_branches;")
if err != nil {
return remoteName, remoteBranchName, currentBranchCommit, err
}
@@ -376,7 +376,7 @@ func getLocalBranchInfo(queryist cli.Queryist, sqlCtx *sql.Context, branchName s
}
func getMergeStatus(queryist cli.Queryist, sqlCtx *sql.Context) (bool, error) {
mergeRows, err := getRowsForSql(queryist, sqlCtx, "select is_merging from dolt_merge_status;")
mergeRows, err := GetRowsForSql(queryist, sqlCtx, "select is_merging from dolt_merge_status;")
if err != nil {
return false, err
}
@@ -396,7 +396,7 @@ func getMergeStatus(queryist cli.Queryist, sqlCtx *sql.Context) (bool, error) {
func getDataConflictsTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[string]bool, error) {
dataConflictTables := make(map[string]bool)
dataConflicts, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_conflicts;")
dataConflicts, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_conflicts;")
if err != nil {
return nil, err
}
@@ -409,7 +409,7 @@ func getDataConflictsTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[str
func getConstraintViolationTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[string]bool, error) {
constraintViolationTables := make(map[string]bool)
constraintViolations, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_constraint_violations;")
constraintViolations, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_constraint_violations;")
if err != nil {
return nil, err
}
@@ -423,7 +423,7 @@ func getConstraintViolationTables(queryist cli.Queryist, sqlCtx *sql.Context) (m
func getWorkingStagedTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[string]bool, map[string]bool, error) {
stagedTableNames := make(map[string]bool)
workingTableNames := make(map[string]bool)
diffs, err := getRowsForSql(queryist, sqlCtx, "select * from dolt_diff where commit_hash='WORKING' OR commit_hash='STAGED';")
diffs, err := GetRowsForSql(queryist, sqlCtx, "select * from dolt_diff where commit_hash='WORKING' OR commit_hash='STAGED';")
if err != nil {
return nil, nil, err
}
@@ -441,7 +441,7 @@ func getWorkingStagedTables(queryist cli.Queryist, sqlCtx *sql.Context) (map[str
func getIgnoredTablePatternsFromSql(queryist cli.Queryist, sqlCtx *sql.Context) (doltdb.IgnorePatterns, error) {
var ignorePatterns []doltdb.IgnorePattern
ignoreRows, err := getRowsForSql(queryist, sqlCtx, fmt.Sprintf("select * from %s", doltdb.IgnoreTableName))
ignoreRows, err := GetRowsForSql(queryist, sqlCtx, fmt.Sprintf("select * from %s", doltdb.IgnoreTableName))
if err != nil {
return nil, err
}

View File

@@ -279,7 +279,7 @@ func newLateBindingEngine(
return lateBinder, nil
}
func getRowsForSql(queryist cli.Queryist, sqlCtx *sql.Context, query string) ([]sql.Row, error) {
func GetRowsForSql(queryist cli.Queryist, sqlCtx *sql.Context, query string) ([]sql.Row, error) {
schema, rowIter, err := queryist.Query(sqlCtx, query)
if err != nil {
return nil, err
@@ -294,7 +294,7 @@ func getRowsForSql(queryist cli.Queryist, sqlCtx *sql.Context, query string) ([]
func getActiveBranchName(sqlCtx *sql.Context, queryEngine cli.Queryist) (string, error) {
query := "SELECT active_branch()"
rows, err := getRowsForSql(queryEngine, sqlCtx, query)
rows, err := GetRowsForSql(queryEngine, sqlCtx, query)
if err != nil {
return "", err
}

View File

@@ -0,0 +1,233 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helper/common.bash
setup() {
setup_common
}
basic_conflict() {
dolt sql -q "create table t (i int primary key, t text)"
dolt add .
dolt commit -am "init commit"
dolt checkout -b other
dolt sql -q "insert into t values (1,'other')"
dolt commit -am "other commit"
dolt checkout main
dolt sql -q "insert into t values (1,'main')"
dolt commit -am "main commit"
}
teardown() {
assert_feature_version
teardown_common
}
@test "conflicts-resolve: call with no arguments, errors" {
run dolt conflicts resolve
[ $status -eq 1 ]
[[ $output =~ "--ours or --theirs must be supplied" ]] || false
}
@test "conflicts-resolve: call without specifying table, errors" {
run dolt conflicts resolve --theirs
[ $status -eq 1 ]
[[ $output =~ "specify at least one table to resolve conflicts" ]] || false
}
@test "conflicts-resolve: call with non-existent table, errors" {
run dolt conflicts resolve --ours notexists
[ $status -eq 1 ]
[[ $output =~ "table not found" ]] || false
}
@test "conflicts-resolve: no conflicts, no changes" {
basic_conflict
dolt checkout main
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
dolt checkout other
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
dolt checkout main
run dolt conflicts resolve --ours t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt conflicts resolve --theirs t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
dolt checkout other
run dolt conflicts resolve --ours t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
run dolt conflicts resolve --theirs t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
}
@test "conflicts-resolve: merge other into main, resolve with ours" {
basic_conflict
dolt checkout main
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt merge other
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --ours .
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
}
@test "conflicts-resolve: merge other into main, resolve with ours, specify table" {
basic_conflict
dolt checkout main
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt merge other
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --ours t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
}
@test "conflicts-resolve: merge other into main, resolve with theirs" {
basic_conflict
dolt checkout main
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt merge other
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --theirs .
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
}
@test "conflicts-resolve: merge other into main, resolve with theirs, specify table" {
basic_conflict
dolt checkout main
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt merge other
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --theirs t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
}
@test "conflicts-resolve: merge main into other, resolve with ours" {
basic_conflict
dolt checkout other
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
run dolt merge main
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --ours .
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
}
@test "conflicts-resolve: merge main into other, resolve with ours, specify table" {
basic_conflict
dolt checkout other
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
run dolt merge main
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --ours t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
}
@test "conflicts-resolve: merge main into other, resolve with theirs" {
basic_conflict
dolt checkout other
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
run dolt merge main
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --theirs .
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
}
@test "conflicts-resolve: merge main into other, resolve with theirs, specify table" {
basic_conflict
dolt checkout other
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "other" ]] || false
run dolt merge main
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt conflicts resolve --theirs t
[ $status -eq 0 ]
run dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
}

View File

@@ -47,6 +47,18 @@ get_staged_tables() {
'
}
basic_conflict() {
dolt --user dolt sql -q "create table t (i int primary key, t text)"
dolt --user dolt add .
dolt --user dolt commit -am "init commit"
dolt --user dolt checkout -b other
dolt --user dolt sql -q "insert into t values (1,'other')"
dolt --user dolt commit -am "other commit"
dolt --user dolt checkout main
dolt --user dolt sql -q "insert into t values (1,'main')"
dolt --user dolt commit -am "main commit"
}
@test "sql-local-remote: test switch between server/no server" {
start_sql_server defaultDB
@@ -439,3 +451,46 @@ get_staged_tables() {
[ "$status" -eq 1 ]
[[ "$output" =~ "When a password is provided, a user must also be provided" ]] || false
}
@test "sql-local-remote: verify dolt conflicts resolve behavior" {
start_sql_server defaultDB
cd defaultDB
basic_conflict
dolt --user dolt checkout main
run dolt --user dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt --user dolt merge other
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt --user dolt conflicts resolve --ours .
[ $status -eq 0 ]
remoteOutput=$output
run dolt --user dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
stop_sql_server 1
basic_conflict
dolt --user dolt checkout main
run dolt --user dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
run dolt --user dolt merge other
[ $status -eq 0 ]
[[ $output =~ "Automatic merge failed" ]] || false
run dolt --user dolt conflicts resolve --ours .
[ $status -eq 0 ]
localOutput=$output
run dolt --user dolt sql -q "select * from t"
[ $status -eq 0 ]
[[ $output =~ "main" ]] || false
[[ "$remoteOutput" == "$localOutput" ]] || false
}