diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index 3f345dba404..362efad1276 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -193,6 +193,7 @@ func CreateCheckoutArgParser() *argparser.ArgParser { func CreateCherryPickArgParser() *argparser.ArgParser { ap := argparser.NewArgParserWithMaxArgs("cherrypick", 1) ap.SupportsFlag(AbortParam, "", "Abort the current conflict resolution process, and revert all changes from the in-process cherry-pick operation.") + ap.SupportsFlag(ContinueFlag, "", "Continue the current cherry-pick operation after conflicts have been resolved.") ap.SupportsFlag(AllowEmptyFlag, "", "Allow empty commits to be cherry-picked. "+ "Note that use of this option only keeps commits that were initially empty. "+ "Commits which become empty, due to a previous commit, will cause cherry-pick to fail.") diff --git a/go/cmd/dolt/commands/cherry-pick.go b/go/cmd/dolt/commands/cherry-pick.go index 25dc1d8cdd9..5cc82c175c4 100644 --- a/go/cmd/dolt/commands/cherry-pick.go +++ b/go/cmd/dolt/commands/cherry-pick.go @@ -47,7 +47,7 @@ If any data conflicts, schema conflicts, or constraint violations are detected d var ErrCherryPickConflictsOrViolations = errors.NewKind("error: Unable to apply commit cleanly due to conflicts " + "or constraint violations. Please resolve the conflicts and/or constraint violations, then use `dolt add` " + - "to add the tables to the staged set, and `dolt commit` to commit the changes and finish cherry-picking. \n" + + "to add the tables to the staged set, and `dolt cherry-pick --continue` to complete the cherry-pick. \n" + "To undo all changes from this cherry-pick operation, use `dolt cherry-pick --abort`.\n" + "For more information on handling conflicts, see: https://docs.dolthub.com/concepts/dolt/git/conflicts") @@ -96,8 +96,19 @@ func (cmd CherryPickCmd) Exec(ctx context.Context, commandStr string, args []str return 1 } + // Check for mutually exclusive flags + if apr.Contains(cli.AbortParam) && apr.Contains(cli.ContinueFlag) { + err = fmt.Errorf("error: --continue and --abort are mutually exclusive") + return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) + } + if apr.Contains(cli.AbortParam) { - err = cherryPickAbort(queryist.Queryist, queryist.Context) + err = cherryPickAbort(queryist.Context, queryist.Queryist) + return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) + } + + if apr.Contains(cli.ContinueFlag) { + err = cherryPickContinue(queryist.Context, queryist.Queryist) return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } @@ -117,11 +128,11 @@ func (cmd CherryPickCmd) Exec(ctx context.Context, commandStr string, args []str return HandleVErrAndExitCode(errhand.BuildDError("cherry-picking multiple commits is not supported yet").SetPrintUsage().Build(), usage) } - err = cherryPick(queryist.Queryist, queryist.Context, apr, args) + err = cherryPick(queryist.Context, queryist.Queryist, apr, args) return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } -func cherryPick(queryist cli.Queryist, sqlCtx *sql.Context, apr *argparser.ArgParseResults, args []string) error { +func cherryPick(sqlCtx *sql.Context, queryist cli.Queryist, apr *argparser.ArgParseResults, args []string) error { cherryStr := apr.Arg(0) if len(cherryStr) == 0 { return fmt.Errorf("error: cannot cherry-pick empty string") @@ -201,7 +212,7 @@ hint: commit your changes (dolt commit -am \"\") or reset them (dolt re if succeeded { // on success, print the commit info - commit, err := getCommitInfo(queryist, sqlCtx, commitHash) + commit, err := getCommitInfo(sqlCtx, queryist, commitHash) if commit == nil || err != nil { return fmt.Errorf("error: failed to get commit metadata for ref '%s': %v", commitHash, err) } @@ -220,7 +231,7 @@ hint: commit your changes (dolt commit -am \"\") or reset them (dolt re } } -func cherryPickAbort(queryist cli.Queryist, sqlCtx *sql.Context) error { +func cherryPickAbort(sqlCtx *sql.Context, queryist cli.Queryist) error { query := "call dolt_cherry_pick('--abort')" _, err := cli.GetRowsForSql(queryist, sqlCtx, query) if err != nil { @@ -235,6 +246,58 @@ func cherryPickAbort(queryist cli.Queryist, sqlCtx *sql.Context) error { return nil } +func cherryPickContinue(sqlCtx *sql.Context, queryist cli.Queryist) error { + query := "call dolt_cherry_pick('--continue')" + rows, err := cli.GetRowsForSql(queryist, sqlCtx, query) + if err != nil { + return err + } + + if len(rows) != 1 { + return fmt.Errorf("error: unexpected number of rows returned from dolt_cherry_pick: %d", len(rows)) + } + if len(rows[0]) != 4 { + return fmt.Errorf("error: unexpected number of columns returned from dolt_cherry_pick: %d", len(rows[0])) + } + + row := rows[0] + + // We expect to get an error if there were problems, but we also could get any of the conflicts and + // vacation counts being greater than 0 if there were problems. If we got here without an error, + // but we have conflicts or violations, we should report and stop. + dataConflicts, err := getInt64ColAsInt64(row[1]) + if err != nil { + return fmt.Errorf("Unable to parse data_conflicts column: %w", err) + } + schemaConflicts, err := getInt64ColAsInt64(row[2]) + if err != nil { + return fmt.Errorf("Unable to parse schema_conflicts column: %w", err) + } + constraintViolations, err := getInt64ColAsInt64(row[3]) + if err != nil { + return fmt.Errorf("Unable to parse constraint_violations column: %w", err) + } + if dataConflicts > 0 || schemaConflicts > 0 || constraintViolations > 0 { + return ErrCherryPickConflictsOrViolations.New() + } + + commitHash := fmt.Sprintf("%v", row[0]) + + commit, err := getCommitInfo(sqlCtx, queryist, commitHash) + if commit == nil || err != nil { + return fmt.Errorf("error: failed to get commit metadata for ref '%s': %v", commitHash, err) + } + + cli.ExecuteWithStdioRestored(func() { + pager := outputpager.Start() + defer pager.Stop() + + PrintCommitInfo(pager, 0, false, false, "auto", commit) + }) + + return nil +} + func hasStagedAndUnstagedChanged(queryist cli.Queryist, sqlCtx *sql.Context) (hasStagedChanges bool, hasUnstagedChanges bool, err error) { stagedTables, unstagedTables, err := GetDoltStatus(queryist, sqlCtx) if err != nil { diff --git a/go/cmd/dolt/commands/commit.go b/go/cmd/dolt/commands/commit.go index 23258c148e8..ff9946c2925 100644 --- a/go/cmd/dolt/commands/commit.go +++ b/go/cmd/dolt/commands/commit.go @@ -173,7 +173,7 @@ func performCommit(ctx context.Context, commandStr string, args []string, cliCtx return 0, true } - commit, err := getCommitInfo(queryist.Queryist, queryist.Context, "HEAD") + commit, err := getCommitInfo(queryist.Context, queryist.Queryist, "HEAD") if cli.ExecuteWithStdioRestored != nil { cli.ExecuteWithStdioRestored(func() { pager := outputpager.Start() diff --git a/go/cmd/dolt/commands/log.go b/go/cmd/dolt/commands/log.go index dccabd89cb2..2f8ad7a8299 100644 --- a/go/cmd/dolt/commands/log.go +++ b/go/cmd/dolt/commands/log.go @@ -302,7 +302,7 @@ func logCommits(apr *argparser.ArgParseResults, commitHashes []sql.Row, queryist var commitsInfo []CommitInfo for _, hash := range commitHashes { cmHash := hash[0].(string) - commit, err := getCommitInfoWithOptions(queryist, sqlCtx, cmHash, opts) + commit, err := getCommitInfoWithOptions(sqlCtx, queryist, cmHash, opts) if commit == nil { return fmt.Errorf("no commits found for ref %s", cmHash) } diff --git a/go/cmd/dolt/commands/merge.go b/go/cmd/dolt/commands/merge.go index c28e9c65a71..db5aea5ab02 100644 --- a/go/cmd/dolt/commands/merge.go +++ b/go/cmd/dolt/commands/merge.go @@ -391,7 +391,7 @@ func printMergeStats(fastForward bool, } if !apr.Contains(cli.NoCommitFlag) && !apr.Contains(cli.NoFFParam) && !fastForward && noConflicts { - commit, err := getCommitInfo(queryist, sqlCtx, "HEAD") + commit, err := getCommitInfo(sqlCtx, queryist, "HEAD") if err != nil { cli.Println("merge finished, but failed to get commit info") cli.Println(err.Error()) diff --git a/go/cmd/dolt/commands/pull.go b/go/cmd/dolt/commands/pull.go index a905e2443c5..6014b5b940f 100644 --- a/go/cmd/dolt/commands/pull.go +++ b/go/cmd/dolt/commands/pull.go @@ -182,7 +182,7 @@ func (cmd PullCmd) Exec(ctx context.Context, commandStr string, args []string, d if remoteHash != "" && headHash != "" { cli.Println("Updating", headHash+".."+remoteHash) } - commit, err := getCommitInfo(queryist.Queryist, queryist.Context, "HEAD") + commit, err := getCommitInfo(queryist.Context, queryist.Queryist, "HEAD") if err != nil { cli.Println("pull finished, but failed to get commit info") cli.Println(err.Error()) diff --git a/go/cmd/dolt/commands/revert.go b/go/cmd/dolt/commands/revert.go index 77af5472422..284b7479b91 100644 --- a/go/cmd/dolt/commands/revert.go +++ b/go/cmd/dolt/commands/revert.go @@ -128,7 +128,7 @@ func (cmd RevertCmd) Exec(ctx context.Context, commandStr string, args []string, return 1 } - commit, err := getCommitInfo(queryist.Queryist, queryist.Context, "HEAD") + commit, err := getCommitInfo(queryist.Context, queryist.Queryist, "HEAD") if err != nil { cli.Printf("Revert completed, but failure to get commit details occurred: %s\n", err.Error()) return 1 diff --git a/go/cmd/dolt/commands/show.go b/go/cmd/dolt/commands/show.go index 979bc81da73..f04a37b13dd 100644 --- a/go/cmd/dolt/commands/show.go +++ b/go/cmd/dolt/commands/show.go @@ -319,7 +319,7 @@ func getCommitSpecPretty(queryist cli.Queryist, sqlCtx *sql.Context, commitRef s commitRef = strings.TrimPrefix(commitRef, "#") } - commit, err = getCommitInfo(queryist, sqlCtx, commitRef) + commit, err = getCommitInfo(sqlCtx, queryist, commitRef) if err != nil { return commit, fmt.Errorf("error: failed to get commit metadata for ref '%s': %v", commitRef, err) } diff --git a/go/cmd/dolt/commands/utils.go b/go/cmd/dolt/commands/utils.go index d24dc1d146a..baf0edd7557 100644 --- a/go/cmd/dolt/commands/utils.go +++ b/go/cmd/dolt/commands/utils.go @@ -701,11 +701,11 @@ type commitInfoOptions struct { } // getCommitInfo returns the commit info for the given ref. -func getCommitInfo(queryist cli.Queryist, sqlCtx *sql.Context, ref string) (*CommitInfo, error) { - return getCommitInfoWithOptions(queryist, sqlCtx, ref, commitInfoOptions{}) +func getCommitInfo(sqlCtx *sql.Context, queryist cli.Queryist, ref string) (*CommitInfo, error) { + return getCommitInfoWithOptions(sqlCtx, queryist, ref, commitInfoOptions{}) } -func getCommitInfoWithOptions(queryist cli.Queryist, sqlCtx *sql.Context, ref string, opts commitInfoOptions) (*CommitInfo, error) { +func getCommitInfoWithOptions(sqlCtx *sql.Context, queryist cli.Queryist, ref string, opts commitInfoOptions) (*CommitInfo, error) { hashOfHead, err := getHashOf(queryist, sqlCtx, "HEAD") if err != nil { return nil, fmt.Errorf("error getting hash of HEAD: %v", err) diff --git a/go/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index e7fd3e64059..b7172dc3819 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -231,6 +231,110 @@ func AbortCherryPick(ctx *sql.Context, dbName string) error { return doltSession.SetWorkingSet(ctx, dbName, newWs) } +// ContinueCherryPick continues a cherry-pick merge that was paused due to conflicts. +// It checks that conflicts have been resolved and creates the final commit with the +// original commit's metadata. +func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, error) { + doltSession := dsess.DSessFromSess(ctx.Session) + + ws, err := doltSession.WorkingSet(ctx, dbName) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("fatal: unable to load working set: %w", err) + } + + if !ws.MergeActive() { + return "", 0, 0, 0, fmt.Errorf("error: There is no cherry-pick merge to continue") + } + mergeState := ws.MergeState() + + // Count conflicts and violations similar to the first pass + workingRoot := ws.WorkingRoot() + stagedRoot := ws.StagedRoot() + + // Count data conflicts + conflictTables, err := doltdb.TablesWithDataConflicts(ctx, workingRoot) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: unable to check for conflicts: %w", err) + } + dataConflictCount := len(conflictTables) + + // Count schema conflicts from merge state + schemaConflictCount := len(mergeState.TablesWithSchemaConflicts()) + + // Count constraint violations + violationTables, err := doltdb.TablesWithConstraintViolations(ctx, workingRoot) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: unable to check for constraint violations: %w", err) + } + constraintViolationCount := len(violationTables) + + // If there are any conflicts or violations, return the counts with an error + if dataConflictCount > 0 || schemaConflictCount > 0 || constraintViolationCount > 0 { + return "", dataConflictCount, schemaConflictCount, constraintViolationCount, nil + } + + // This is a little strict. Technically, you could use the dolt_workspace table to stage something + // and result in different roots. + // TODO: test with ignored and local tables - because this strictness will probably cause issues. + isClean, err := rootsEqual(stagedRoot, workingRoot) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: unable to compare staged and working roots: %w", err) + } + if !isClean { + return "", 0, 0, 0, fmt.Errorf("error: cannot continue cherry-pick with unstaged changes") + } + + cherryCommit := mergeState.Commit() + if cherryCommit == nil { + return "", 0, 0, 0, fmt.Errorf("error: unable to get original commit from merge state") + } + + cherryCommitMeta, err := cherryCommit.GetCommitMeta(ctx) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: unable to get commit metadata: %w", err) + } + + // Create the commit with the original commit's metadata + commitProps := actions.CommitStagedProps{ + Message: cherryCommitMeta.Description, + Date: cherryCommitMeta.Time(), + AllowEmpty: false, // in a conflict workflow, never will be 'true' + Name: cherryCommitMeta.Name, + Email: cherryCommitMeta.Email, + } + + roots, ok := doltSession.GetRoots(ctx, dbName) + if !ok { + return "", 0, 0, 0, fmt.Errorf("fatal: unable to load roots for %s", dbName) + } + + pendingCommit, err := doltSession.NewPendingCommit(ctx, dbName, roots, commitProps) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: failed to create pending commit: %w", err) + } + if pendingCommit == nil { + return "", 0, 0, 0, fmt.Errorf("error: no changes to commit") + } + + clearedWs := ws.ClearMerge() + err = doltSession.SetWorkingSet(ctx, dbName, clearedWs) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: failed to clear merge state: %w", err) + } + + commit, err := doltSession.DoltCommit(ctx, dbName, doltSession.GetTransaction(), pendingCommit) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: failed to execute commit: %w", err) + } + + commitHash, err := commit.HashOf() + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: failed to get commit hash: %w", err) + } + + return commitHash.String(), 0, 0, 0, nil +} + // cherryPick checks that the current working set is clean, verifies the cherry-pick commit is not a merge commit // or a commit without parent commit, performs merge and returns the new working set root value and // the commit message of cherry-picked commit as the commit message of the new commit created during this command. @@ -371,22 +475,35 @@ func cherryPick(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots, // If any of the merge stats show a data or schema conflict or a constraint // violation, record that a merge is in progress. + hasArtifacts := false for _, stats := range result.Stats { if stats.HasArtifacts() { - ws, err := dSess.WorkingSet(ctx, dbName) - if err != nil { - return nil, "", nil, err - } - newWorkingSet := ws.StartCherryPick(cherryCommit, cherryStr) - err = dSess.SetWorkingSet(ctx, dbName, newWorkingSet) - if err != nil { - return nil, "", nil, err - } - + hasArtifacts = true break } } + // Also check if there are any constraint violations in the result root + // This handles the case where violations weren't tracked in stats + if !hasArtifacts && result.Root != nil { + violationTables, err := doltdb.TablesWithConstraintViolations(ctx, result.Root) + if err == nil && len(violationTables) > 0 { + hasArtifacts = true + } + } + + if hasArtifacts { + ws, err := dSess.WorkingSet(ctx, dbName) + if err != nil { + return nil, "", nil, err + } + newWorkingSet := ws.StartCherryPick(cherryCommit, cherryStr) + err = dSess.SetWorkingSet(ctx, dbName, newWorkingSet) + if err != nil { + return nil, "", nil, err + } + } + return result, cherryCommitMeta.Description, cherryCommit, nil } diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_cherry_pick.go b/go/libraries/doltcore/sqle/dprocedures/dolt_cherry_pick.go index 58cbe5c3646..ce6833490a0 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_cherry_pick.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_cherry_pick.go @@ -80,10 +80,18 @@ func doDoltCherryPick(ctx *sql.Context, args []string) (string, int, int, int, e return "", 0, 0, 0, err } + if apr.Contains(cli.AbortParam) && apr.Contains(cli.ContinueFlag) { + return "", 0, 0, 0, fmt.Errorf("error: --continue and --abort are mutually exclusive") + } + if apr.Contains(cli.AbortParam) { return "", 0, 0, 0, cherry_pick.AbortCherryPick(ctx, dbName) } + if apr.Contains(cli.ContinueFlag) { + return cherry_pick.ContinueCherryPick(ctx, dbName) + } + // we only support cherry-picking a single commit for now. if apr.NArg() == 0 { return "", 0, 0, 0, ErrEmptyCherryPick diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index f5039e03d13..a3af6ff55b9 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -7225,590 +7225,6 @@ var DoltAutoIncrementTests = []queries.ScriptTest{ }, } -var DoltCherryPickTests = []queries.ScriptTest{ - { - Name: "error cases: basic validation", - SetUpScript: []string{ - "create table t (pk int primary key, v varchar(100));", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "insert into t values (1, \"one\");", - "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL Dolt_Cherry_Pick('HEAD~100');", - ExpectedErrStr: "invalid ancestor spec", - }, - { - Query: "CALL Dolt_Cherry_Pick('abcdaaaaaaaaaaaaaaaaaaaaaaaaaaaa');", - ExpectedErrStr: "target commit not found", - }, - { - Query: "CALL Dolt_Cherry_Pick('--abort');", - ExpectedErrStr: "error: There is no cherry-pick merge to abort", - }, - }, - }, - { - Name: "error cases: merge commits cannot be cherry-picked", - SetUpScript: []string{ - "create table t (pk int primary key, v varchar(100));", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "insert into t values (1, \"one\");", - "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL dolt_merge('--no-ff', 'branch1');", - Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}}, - }, - { - Query: "CALL dolt_cherry_pick('HEAD');", - ExpectedErrStr: "cherry-picking a merge commit is not supported", - }, - }, - }, - { - Name: "error cases: error with staged or unstaged changes ", - SetUpScript: []string{ - "create table t (pk int primary key, v varchar(100));", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "insert into t values (1, \"one\");", - "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - "INSERT INTO t VALUES (100, 'onehundy');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL Dolt_Cherry_Pick(@commit1);", - ExpectedErrStr: "cannot cherry-pick with uncommitted changes", - }, - { - Query: "call dolt_add('t');", - Expected: []sql.Row{{0}}, - }, - { - Query: "CALL Dolt_Cherry_Pick(@commit1);", - ExpectedErrStr: "cannot cherry-pick with uncommitted changes", - }, - }, - }, - { - Name: "error cases: different primary keys", - SetUpScript: []string{ - "create table t (pk int primary key, v varchar(100));", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "ALTER TABLE t DROP PRIMARY KEY, ADD PRIMARY KEY (pk, v);", - "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL Dolt_Cherry_Pick(@commit1);", - ExpectedErrStr: "error: cannot merge because table t has different primary keys", - }, - }, - }, - { - Name: "basic case", - SetUpScript: []string{ - "create table t (pk int primary key, v varchar(100));", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "insert into t values (1, \"one\");", - "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", - "insert into t values (2, \"two\");", - "call dolt_commit('-am', 'adding row 2');", - "set @commit2 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * FROM t;", - Expected: []sql.Row{}, - }, - { - Query: "call dolt_cherry_pick(@commit2);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SELECT * FROM t;", - Expected: []sql.Row{{2, "two"}}, - }, - { - Query: "call dolt_cherry_pick(@commit1);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SELECT * FROM t order by pk;", - Expected: []sql.Row{{1, "one"}, {2, "two"}}, - }, - { - // Assert that our new commit only has one parent (i.e. not a merge commit) - Query: "select count(*) from dolt_commit_ancestors where commit_hash = hashof('HEAD');", - Expected: []sql.Row{{1}}, - }, - }, - }, - { - Name: "keyless table", - SetUpScript: []string{ - "call dolt_checkout('main');", - "CREATE TABLE keyless (id int, name varchar(10));", - "call dolt_commit('-Am', 'create table keyless on main');", - "call dolt_checkout('-b', 'branch1');", - "INSERT INTO keyless VALUES (1,'1'), (2,'3');", - "call dolt_commit('-am', 'insert rows into keyless table on branch1');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * FROM keyless;", - Expected: []sql.Row{}, - }, - { - Query: "CALL DOLT_CHERRY_PICK('branch1');", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SELECT * FROM keyless;", - Expected: []sql.Row{{1, "1"}, {2, "3"}}, - }, - }, - }, - { - Name: "schema change: CREATE TABLE", - SetUpScript: []string{ - "call dolt_checkout('-b', 'branch1');", - "CREATE TABLE table_a (pk BIGINT PRIMARY KEY, v varchar(10));", - "INSERT INTO table_a VALUES (11, 'aa'), (22, 'ab');", - "call dolt_commit('-Am', 'create table table_a');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SHOW TABLES;", - Expected: []sql.Row{}, - }, - { - Query: "call dolt_cherry_pick(@commit1);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - // Assert that our new commit only has one parent (i.e. not a merge commit) - Query: "select count(*) from dolt_commit_ancestors where commit_hash = hashof('HEAD');", - Expected: []sql.Row{{1}}, - }, - { - Query: "SHOW TABLES;", - Expected: []sql.Row{{"table_a"}}, - }, - { - Query: "SELECT * FROM table_a;", - Expected: []sql.Row{{11, "aa"}, {22, "ab"}}, - }, - }, - }, - { - Name: "schema change: DROP TABLE", - SetUpScript: []string{ - "CREATE TABLE dropme (pk BIGINT PRIMARY KEY, v varchar(10));", - "INSERT INTO dropme VALUES (11, 'aa'), (22, 'ab');", - "call dolt_commit('-Am', 'create table dropme');", - "call dolt_checkout('-b', 'branch1');", - "drop table dropme;", - "call dolt_commit('-Am', 'drop table dropme');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SHOW TABLES;", - Expected: []sql.Row{{"dropme"}}, - }, - { - Query: "call dolt_cherry_pick(@commit1);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SHOW TABLES;", - Expected: []sql.Row{}, - }, - }, - }, - { - Name: "schema change: ALTER TABLE ADD COLUMN", - SetUpScript: []string{ - "create table test(pk int primary key);", - "call dolt_commit('-Am', 'create table test on main');", - "call dolt_checkout('-b', 'branch1');", - "ALTER TABLE test ADD COLUMN v VARCHAR(100);", - "call dolt_commit('-am', 'add column v to test on branch1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(@commit1);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SHOW CREATE TABLE test;", - Expected: []sql.Row{{"test", "CREATE TABLE `test` (\n `pk` int NOT NULL,\n `v` varchar(100),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, - }, - }, - }, - { - Name: "schema change: ALTER TABLE DROP COLUMN", - SetUpScript: []string{ - "create table test(pk int primary key, v varchar(100));", - "call dolt_commit('-Am', 'create table test on main');", - "call dolt_checkout('-b', 'branch1');", - "ALTER TABLE test DROP COLUMN v;", - "call dolt_commit('-am', 'drop column v from test on branch1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(@commit1);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SHOW CREATE TABLE test;", - Expected: []sql.Row{{"test", "CREATE TABLE `test` (\n `pk` int NOT NULL,\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, - }, - }, - }, - { - Name: "schema change: ALTER TABLE RENAME COLUMN", - SetUpScript: []string{ - "create table test(pk int primary key, v1 varchar(100));", - "call dolt_commit('-Am', 'create table test on main');", - "call dolt_checkout('-b', 'branch1');", - "ALTER TABLE test RENAME COLUMN v1 to v2;", - "call dolt_commit('-am', 'rename column v1 to v2 in test on branch1');", - "set @commit1 = hashof('HEAD');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(@commit1);", - Expected: []sql.Row{{doltCommit, 0, 0, 0}}, - }, - { - Query: "SHOW CREATE TABLE test;", - Expected: []sql.Row{{"test", "CREATE TABLE `test` (\n `pk` int NOT NULL,\n `v2` varchar(100),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, - }, - }, - }, - { - Name: "abort (@@autocommit=0)", - SetUpScript: []string{ - "SET @@autocommit=0;", - "create table t (pk int primary key, v varchar(100));", - "insert into t values (1, 'one');", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "update t set v=\"uno\" where pk=1;", - "call dolt_commit('-Am', 'updating row 1 -> uno');", - "alter table t drop column v;", - "call dolt_commit('-am', 'drop column v');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(hashof('branch1'));", - Expected: []sql.Row{{"", 1, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{{"t", uint64(1)}}, - }, - { - Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", - Expected: []sql.Row{ - {1, "uno", 1, "modified", 1, "modified"}, - }, - }, - { - Query: "call dolt_cherry_pick('--abort');", - Expected: []sql.Row{{"", 0, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{}, - }, - { - Query: "select * from t;", - Expected: []sql.Row{{1, "one"}}, - }, - }, - }, - { - Name: "abort (@@autocommit=1)", - SetUpScript: []string{ - "SET @@autocommit=1;", - "SET @@dolt_allow_commit_conflicts=1;", - "create table t (pk int primary key, v varchar(100));", - "insert into t values (1, 'one');", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "update t set v=\"uno\" where pk=1;", - "call dolt_commit('-Am', 'updating row 1 -> uno');", - "alter table t drop column v;", - "call dolt_commit('-am', 'drop column v');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(hashof('branch1'));", - Expected: []sql.Row{{"", 1, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{{"t", uint64(1)}}, - }, - { - Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", - Expected: []sql.Row{ - {1, "uno", 1, "modified", 1, "modified"}, - }, - }, - { - Query: "call dolt_cherry_pick('--abort');", - Expected: []sql.Row{{"", 0, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{}, - }, - { - Query: "select * from t;", - Expected: []sql.Row{{1, "one"}}, - }, - }, - }, - { - Name: "conflict resolution (@@autocommit=0)", - SetUpScript: []string{ - "SET @@autocommit=0;", - "create table t (pk int primary key, v varchar(100));", - "insert into t values (1, 'one');", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "update t set v=\"uno\" where pk=1;", - "call dolt_commit('-Am', 'updating row 1 -> uno');", - "alter table t drop column v;", - "call dolt_commit('-am', 'drop column v');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(hashof('branch1'));", - Expected: []sql.Row{{"", 1, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{{"t", uint64(1)}}, - }, - { - Query: "select * from dolt_status", - Expected: []sql.Row{{"t", byte(0), "modified"}, {"t", byte(0), "conflict"}}, - }, - { - Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", - Expected: []sql.Row{ - {1, "uno", 1, "modified", 1, "modified"}, - }, - }, - { - Query: "call dolt_conflicts_resolve('--ours', 't');", - Expected: []sql.Row{{0}}, - }, - { - Query: "select * from dolt_status", - Expected: []sql.Row{{"t", byte(0), "modified"}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{}, - }, - { - Query: "select * from t;", - Expected: []sql.Row{{1}}, - }, - { - Query: "call dolt_commit('-am', 'committing cherry-pick');", - Expected: []sql.Row{{doltCommit}}, - }, - { - // Assert that our new commit only has one parent (i.e. not a merge commit) - Query: "select count(*) from dolt_commit_ancestors where commit_hash = hashof('HEAD');", - Expected: []sql.Row{{1}}, - }, - }, - }, - { - Name: "conflict resolution (@@autocommit=1)", - SetUpScript: []string{ - "set @@autocommit=1;", - "SET @@dolt_allow_commit_conflicts=1;", - "create table t (pk int primary key, c1 varchar(100));", - "call dolt_commit('-Am', 'creating table t');", - "insert into t values (1, \"one\");", - "call dolt_commit('-Am', 'inserting row 1');", - "SET @commit1 = hashof('HEAD');", - "update t set c1=\"uno\" where pk=1;", - "call dolt_commit('-Am', 'updating row 1 -> uno');", - "update t set c1=\"ein\" where pk=1;", - "call dolt_commit('-Am', 'updating row 1 -> ein');", - "SET @commit2 = hashof('HEAD');", - "call dolt_reset('--hard', @commit1);", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * from dolt_status;", - Expected: []sql.Row{}, - }, - { - Query: "SELECT * from t;", - Expected: []sql.Row{{1, "one"}}, - }, - { - Query: `CALL dolt_cherry_pick(@commit2);`, - Expected: []sql.Row{{"", 1, 0, 0}}, - }, - { - Query: `SELECT * FROM dolt_conflicts;`, - Expected: []sql.Row{{"t", uint64(1)}}, - }, - { - Query: `commit;`, - Expected: []sql.Row{}, - }, - { - Query: `SELECT * FROM dolt_conflicts;`, - Expected: []sql.Row{{"t", uint64(1)}}, - }, - { - Query: `SELECT base_pk, base_c1, our_pk, our_c1, their_diff_type, their_pk, their_c1 FROM dolt_conflicts_t;`, - Expected: []sql.Row{{1, "uno", 1, "one", "modified", 1, "ein"}}, - }, - { - Query: `SELECT * FROM t;`, - Expected: []sql.Row{{1, "one"}}, - }, - { - Query: `call dolt_conflicts_resolve('--theirs', 't');`, - Expected: []sql.Row{{0}}, - }, - { - Query: `SELECT * FROM t;`, - Expected: []sql.Row{{1, "ein"}}, - }, - { - Query: "call dolt_commit('-am', 'committing cherry-pick');", - Expected: []sql.Row{{doltCommit}}, - }, - { - // Assert that our new commit only has one parent (i.e. not a merge commit) - Query: "select count(*) from dolt_commit_ancestors where commit_hash = hashof('HEAD');", - Expected: []sql.Row{{1}}, - }, - }, - }, - { - Name: "abort (@@autocommit=1) with ignored table", - SetUpScript: []string{ - "INSERT INTO dolt_ignore VALUES ('generated_*', 1);", - "CREATE TABLE generated_foo (pk int PRIMARY KEY);", - "CREATE TABLE generated_bar (pk int PRIMARY KEY);", - "insert into generated_foo values (1);", - "insert into generated_bar values (1);", - "SET @@autocommit=1;", - "SET @@dolt_allow_commit_conflicts=1;", - "create table t (pk int primary key, v varchar(100));", - "insert into t values (1, 'one');", - "call dolt_add('--force', 'generated_bar');", - "call dolt_commit('-Am', 'create table t');", - "call dolt_checkout('-b', 'branch1');", - "update t set v=\"uno\" where pk=1;", - "call dolt_commit('-Am', 'updating row 1 -> uno');", - "alter table t drop column v;", - "call dolt_commit('-am', 'drop column v');", - "call dolt_checkout('main');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "call dolt_cherry_pick(hashof('branch1'));", - Expected: []sql.Row{{"", 1, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{{"t", uint64(1)}}, - }, - { - Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", - Expected: []sql.Row{ - {1, "uno", 1, "modified", 1, "modified"}, - }, - }, - { - Query: "insert into generated_foo values (2);", - }, - /* - // TODO: https://github.com/dolthub/dolt/issues/7411 - // see below - { - Query: "insert into generated_bar values (2);", - }, - */ - { - Query: "call dolt_cherry_pick('--abort');", - Expected: []sql.Row{{"", 0, 0, 0}}, - }, - { - Query: "select * from dolt_conflicts;", - Expected: []sql.Row{}, - }, - { - Query: "select * from t;", - Expected: []sql.Row{{1, "one"}}, - }, - { - // An ignored table should still be present (and unstaged) after aborting the merge. - Query: "select * from dolt_status_ignored;", - Expected: []sql.Row{{"generated_foo", byte(0), "new table", true}}, - }, - { - // Changes made to the table during the merge should not be reverted. - Query: "select * from generated_foo;", - Expected: []sql.Row{{1}, {2}}, - }, - /*{ - // TODO: https://github.com/dolthub/dolt/issues/7411 - // The table that was force-added should be treated like any other table - // and reverted to its state before the merge began. - Query: "select * from generated_bar;", - Expected: []sql.Row{{1}}, - },*/ - }, - }, -} - var DoltCommitTests = []queries.ScriptTest{ { Name: "CALL DOLT_COMMIT('-ALL') adds all tables (including new ones) to the commit.", diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go new file mode 100644 index 00000000000..93b5f2d876c --- /dev/null +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -0,0 +1,968 @@ +// 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 enginetest + +import ( + "time" + + "github.com/dolthub/go-mysql-server/enginetest" + "github.com/dolthub/go-mysql-server/enginetest/queries" + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/plan" + "github.com/dolthub/go-mysql-server/sql/types" +) + +// timeValidator validates that a value is a time.Time with the expected date/time +type timeValidator struct { + expectedTime time.Time +} + +var _ enginetest.CustomValueValidator = &timeValidator{} + +func (tv *timeValidator) Validate(val interface{}) (bool, error) { + t, ok := val.(time.Time) + if !ok { + return false, nil + } + return t.Equal(tv.expectedTime), nil +} + +func timeEquals(dateStr string) *timeValidator { + t, _ := time.Parse("2006-01-02T15:04:05Z", dateStr) + return &timeValidator{expectedTime: t} +} + +var DoltCherryPickTests = []queries.ScriptTest{ + { + Name: "error cases: basic validation", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, \"one\");", + "call dolt_commit('-am', 'adding row 1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL Dolt_Cherry_Pick('HEAD~100');", + ExpectedErrStr: "invalid ancestor spec", + }, + { + Query: "CALL Dolt_Cherry_Pick('abcdaaaaaaaaaaaaaaaaaaaaaaaaaaaa');", + ExpectedErrStr: "target commit not found", + }, + { + Query: "CALL Dolt_Cherry_Pick('--abort');", + ExpectedErrStr: "error: There is no cherry-pick merge to abort", + }, + }, + }, + { + Name: "error cases: merge commits cannot be cherry-picked", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, \"one\");", + "call dolt_commit('-am', 'adding row 1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL dolt_merge('--no-ff', 'branch1');", + Expected: []sql.Row{{doltCommit, 0, 0, "merge successful"}}, + }, + { + Query: "CALL dolt_cherry_pick('HEAD');", + ExpectedErrStr: "cherry-picking a merge commit is not supported", + }, + }, + }, + { + Name: "error cases: error with staged or unstaged changes ", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, \"one\");", + "call dolt_commit('-am', 'adding row 1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + "INSERT INTO t VALUES (100, 'onehundy');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL Dolt_Cherry_Pick(@commit1);", + ExpectedErrStr: "cannot cherry-pick with uncommitted changes", + }, + { + Query: "call dolt_add('t');", + Expected: []sql.Row{{0}}, + }, + { + Query: "CALL Dolt_Cherry_Pick(@commit1);", + ExpectedErrStr: "cannot cherry-pick with uncommitted changes", + }, + }, + }, + { + Name: "error cases: different primary keys", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "ALTER TABLE t DROP PRIMARY KEY, ADD PRIMARY KEY (pk, v);", + "call dolt_commit('-am', 'adding row 1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL Dolt_Cherry_Pick(@commit1);", + ExpectedErrStr: "error: cannot merge because table t has different primary keys", + }, + }, + }, + { + Name: "basic case", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, \"one\");", + "call dolt_commit('-am', 'adding row 1');", + "set @commit1 = dolt_hashof('HEAD');", + "insert into t values (2, \"two\");", + "call dolt_commit('-am', 'adding row 2');", + "set @commit2 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM t;", + Expected: []sql.Row{}, + }, + { + Query: "call dolt_cherry_pick(@commit2);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SELECT * FROM t;", + Expected: []sql.Row{{2, "two"}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SELECT * FROM t order by pk;", + Expected: []sql.Row{{1, "one"}, {2, "two"}}, + }, + { + // Assert that our new commit only has one parent (i.e. not a merge commit) + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", + Expected: []sql.Row{{1}}, + }, + }, + }, + { + Name: "keyless table", + SetUpScript: []string{ + "call dolt_checkout('main');", + "CREATE TABLE keyless (id int, name varchar(10));", + "call dolt_commit('-Am', 'create table keyless on main');", + "call dolt_checkout('-b', 'branch1');", + "INSERT INTO keyless VALUES (1,'1'), (2,'3');", + "call dolt_commit('-am', 'insert rows into keyless table on branch1');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM keyless;", + Expected: []sql.Row{}, + }, + { + Query: "CALL DOLT_CHERRY_PICK('branch1');", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SELECT * FROM keyless;", + Expected: []sql.Row{{1, "1"}, {2, "3"}}, + }, + }, + }, + { + Name: "schema change: CREATE TABLE", + SetUpScript: []string{ + "call dolt_checkout('-b', 'branch1');", + "CREATE TABLE table_a (pk BIGINT PRIMARY KEY, v varchar(10));", + "INSERT INTO table_a VALUES (11, 'aa'), (22, 'ab');", + "call dolt_commit('-Am', 'create table table_a');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SHOW TABLES;", + Expected: []sql.Row{}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + // Assert that our new commit only has one parent (i.e. not a merge commit) + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", + Expected: []sql.Row{{1}}, + }, + { + Query: "SHOW TABLES;", + Expected: []sql.Row{{"table_a"}}, + }, + { + Query: "SELECT * FROM table_a;", + Expected: []sql.Row{{11, "aa"}, {22, "ab"}}, + }, + }, + }, + { + Name: "schema change: DROP TABLE", + SetUpScript: []string{ + "CREATE TABLE dropme (pk BIGINT PRIMARY KEY, v varchar(10));", + "INSERT INTO dropme VALUES (11, 'aa'), (22, 'ab');", + "call dolt_commit('-Am', 'create table dropme');", + "call dolt_checkout('-b', 'branch1');", + "drop table dropme;", + "call dolt_commit('-Am', 'drop table dropme');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SHOW TABLES;", + Expected: []sql.Row{{"dropme"}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SHOW TABLES;", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "schema change: ALTER TABLE ADD COLUMN", + SetUpScript: []string{ + "create table test(pk int primary key);", + "call dolt_commit('-Am', 'create table test on main');", + "call dolt_checkout('-b', 'branch1');", + "ALTER TABLE test ADD COLUMN v VARCHAR(100);", + "call dolt_commit('-am', 'add column v to test on branch1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SHOW CREATE TABLE test;", + Expected: []sql.Row{{"test", "CREATE TABLE `test` (\n `pk` int NOT NULL,\n `v` varchar(100),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + }, + }, + { + Name: "schema change: ALTER TABLE DROP COLUMN", + SetUpScript: []string{ + "create table test(pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table test on main');", + "call dolt_checkout('-b', 'branch1');", + "ALTER TABLE test DROP COLUMN v;", + "call dolt_commit('-am', 'drop column v from test on branch1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SHOW CREATE TABLE test;", + Expected: []sql.Row{{"test", "CREATE TABLE `test` (\n `pk` int NOT NULL,\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + }, + }, + { + Name: "schema change: ALTER TABLE RENAME COLUMN", + SetUpScript: []string{ + "create table test(pk int primary key, v1 varchar(100));", + "call dolt_commit('-Am', 'create table test on main');", + "call dolt_checkout('-b', 'branch1');", + "ALTER TABLE test RENAME COLUMN v1 to v2;", + "call dolt_commit('-am', 'rename column v1 to v2 in test on branch1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "SHOW CREATE TABLE test;", + Expected: []sql.Row{{"test", "CREATE TABLE `test` (\n `pk` int NOT NULL,\n `v2` varchar(100),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin"}}, + }, + }, + }, + { + Name: "abort (@@autocommit=0)", + SetUpScript: []string{ + "SET @@autocommit=0;", + "create table t (pk int primary key, v varchar(100));", + "insert into t values (1, 'one');", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "update t set v=\"uno\" where pk=1;", + "call dolt_commit('-Am', 'updating row 1 -> uno');", + "alter table t drop column v;", + "call dolt_commit('-am', 'drop column v');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", + Expected: []sql.Row{ + {1, "uno", 1, "modified", 1, "modified"}, + }, + }, + { + Query: "call dolt_cherry_pick('--abort');", + Expected: []sql.Row{{"", 0, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1, "one"}}, + }, + }, + }, + { + Name: "abort (@@autocommit=1)", + SetUpScript: []string{ + "SET @@autocommit=1;", + "SET @@dolt_allow_commit_conflicts=1;", + "create table t (pk int primary key, v varchar(100));", + "insert into t values (1, 'one');", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "update t set v=\"uno\" where pk=1;", + "call dolt_commit('-Am', 'updating row 1 -> uno');", + "alter table t drop column v;", + "call dolt_commit('-am', 'drop column v');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", + Expected: []sql.Row{ + {1, "uno", 1, "modified", 1, "modified"}, + }, + }, + { + Query: "call dolt_cherry_pick('--abort');", + Expected: []sql.Row{{"", 0, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1, "one"}}, + }, + }, + }, + { + Name: "conflict resolution (@@autocommit=0)", + SetUpScript: []string{ + "SET @@autocommit=0;", + "create table t (pk int primary key, v varchar(100));", + "insert into t values (1, 'one');", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "update t set v=\"uno\" where pk=1;", + "call dolt_commit('-Am', 'updating row 1 -> uno');", + "alter table t drop column v;", + "call dolt_commit('-am', 'drop column v');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: "select * from dolt_status", + Expected: []sql.Row{{"t", byte(0), "modified"}, {"t", byte(0), "conflict"}}, + }, + { + Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", + Expected: []sql.Row{ + {1, "uno", 1, "modified", 1, "modified"}, + }, + }, + { + Query: "call dolt_conflicts_resolve('--ours', 't');", + Expected: []sql.Row{{0}}, + }, + { + Query: "select * from dolt_status", + Expected: []sql.Row{{"t", byte(0), "modified"}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1}}, + }, + { + Query: "call dolt_commit('-am', 'committing cherry-pick');", + Expected: []sql.Row{{doltCommit}}, + }, + { + // Assert that our new commit only has one parent (i.e. not a merge commit) + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", + Expected: []sql.Row{{1}}, + }, + }, + }, + { + Name: "conflict resolution (@@autocommit=1)", + SetUpScript: []string{ + "set @@autocommit=1;", + "SET @@dolt_allow_commit_conflicts=1;", + "create table t (pk int primary key, c1 varchar(100));", + "call dolt_commit('-Am', 'creating table t');", + "insert into t values (1, \"one\");", + "call dolt_commit('-Am', 'inserting row 1');", + "SET @commit1 = hashof('HEAD');", + "update t set c1=\"uno\" where pk=1;", + "call dolt_commit('-Am', 'updating row 1 -> uno');", + "update t set c1=\"ein\" where pk=1;", + "call dolt_commit('-Am', 'updating row 1 -> ein');", + "SET @commit2 = hashof('HEAD');", + "call dolt_reset('--hard', @commit1);", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * from dolt_status;", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * from t;", + Expected: []sql.Row{{1, "one"}}, + }, + { + Query: `CALL dolt_cherry_pick(@commit2);`, + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: `SELECT * FROM dolt_conflicts;`, + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: `commit;`, + Expected: []sql.Row{}, + }, + { + Query: `SELECT * FROM dolt_conflicts;`, + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: `SELECT base_pk, base_c1, our_pk, our_c1, their_diff_type, their_pk, their_c1 FROM dolt_conflicts_t;`, + Expected: []sql.Row{{1, "uno", 1, "one", "modified", 1, "ein"}}, + }, + { + Query: `SELECT * FROM t;`, + Expected: []sql.Row{{1, "one"}}, + }, + { + Query: `call dolt_conflicts_resolve('--theirs', 't');`, + Expected: []sql.Row{{0}}, + }, + { + Query: `SELECT * FROM t;`, + Expected: []sql.Row{{1, "ein"}}, + }, + { + Query: "call dolt_commit('-am', 'committing cherry-pick');", + Expected: []sql.Row{{doltCommit}}, + }, + { + // Assert that our new commit only has one parent (i.e. not a merge commit) + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", + Expected: []sql.Row{{1}}, + }, + }, + }, + { + Name: "abort (@@autocommit=1) with ignored table", + SetUpScript: []string{ + "INSERT INTO dolt_ignore VALUES ('generated_*', 1);", + "CREATE TABLE generated_foo (pk int PRIMARY KEY);", + "CREATE TABLE generated_bar (pk int PRIMARY KEY);", + "insert into generated_foo values (1);", + "insert into generated_bar values (1);", + "SET @@autocommit=1;", + "SET @@dolt_allow_commit_conflicts=1;", + "create table t (pk int primary key, v varchar(100));", + "insert into t values (1, 'one');", + "call dolt_add('--force', 'generated_bar');", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "update t set v=\"uno\" where pk=1;", + "call dolt_commit('-Am', 'updating row 1 -> uno');", + "alter table t drop column v;", + "call dolt_commit('-am', 'drop column v');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: "select base_pk, base_v, our_pk, our_diff_type, their_pk, their_diff_type from dolt_conflicts_t;", + Expected: []sql.Row{ + {1, "uno", 1, "modified", 1, "modified"}, + }, + }, + { + Query: "insert into generated_foo values (2);", + }, + /* + // TODO: https://github.com/dolthub/dolt/issues/7411 + // see below + { + Query: "insert into generated_bar values (2);", + }, + */ + { + Query: "call dolt_cherry_pick('--abort');", + Expected: []sql.Row{{"", 0, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1, "one"}}, + }, + { + // An ignored table should still be present (and unstaged) after aborting the merge. + Query: "select * from dolt_status_ignored;", + Expected: []sql.Row{{"generated_foo", byte(0), "new table", true}}, + }, + { + // Changes made to the table during the merge should not be reverted. + Query: "select * from generated_foo;", + Expected: []sql.Row{{1}, {2}}, + }, + /*{ + // TODO: https://github.com/dolthub/dolt/issues/7411 + // The table that was force-added should be treated like any other table + // and reverted to its state before the merge began. + Query: "select * from generated_bar;", + Expected: []sql.Row{{1}}, + },*/ + }, + }, + { + Name: "cherry-pick --continue: successful conflict resolution workflow", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, 'branch1_value');", + "call dolt_commit('-am', 'add row from branch1', '--author', 'Test User ', '--date', '2022-01-01T12:00:00');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + "insert into t values (1, 'main_value');", + "call dolt_commit('-am', 'add row from main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "set @@dolt_allow_commit_conflicts = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: "select our_pk, our_v, their_pk, their_v from dolt_conflicts_t;", + Expected: []sql.Row{ + {1, "main_value", 1, "branch1_value"}, + }, + }, + { + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "delete from dolt_conflicts_t", + SkipResultsCheck: true, + }, + { + Query: "update t set v = 'resolved_value' where pk = 1;", + Expected: []sql.Row{{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}}, + }, + { + Query: "call dolt_add('t');", + Expected: []sql.Row{{0}}, + }, + { + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1, "resolved_value"}}, + }, + { + Query: "select committer, email, message, date from dolt_log limit 1;", + Expected: []sql.Row{{"Test User", "test@example.com", "add row from branch1", timeEquals("2022-01-01T12:00:00Z")}}, + }, + }, + }, + { + Name: "cherry-pick --continue not in a cherry-pick state", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, 'one');", + "call dolt_commit('-am', 'add row from branch1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "call dolt_cherry_pick('--continue');", + ExpectedErrStr: "error: There is no cherry-pick merge to continue", + }, + }, + }, + { + Name: "cherry-pick --continue: multiple table conflicts", + SetUpScript: []string{ + "create table t1 (pk int primary key, v varchar(100));", + "create table t2 (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create tables');", + "call dolt_checkout('-b', 'branch1');", + "insert into t1 values (1, 'branch1_t1');", + "insert into t2 values (1, 'branch1_t2');", + "call dolt_commit('-am', 'add rows from branch1', '--author', 'Branch User ', '--date', '2022-02-01T10:30:00');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + "insert into t1 values (1, 'main_t1');", + "insert into t2 values (1, 'main_t2');", + "call dolt_commit('-am', 'add rows from main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "set @@dolt_allow_commit_conflicts = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{"", 2, 0, 0}}, + }, + { + Query: "select `table` from dolt_conflicts order by `table`;", + Expected: []sql.Row{{"t1"}, {"t2"}}, + }, + { + Query: "update t1 set v = 'resolved_t1' where pk = 1;", + SkipResultsCheck: true, + }, + { + Query: "delete from dolt_conflicts_t1;", + SkipResultsCheck: true, + }, + { + Query: "call dolt_add('t1');", + SkipResultsCheck: true, + }, + { + // Should still have one remaining conflict in t2. + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "update t2 set v = 'resolved_t2' where pk = 1;", + SkipResultsCheck: true, + }, + { + Query: "delete from dolt_conflicts_t2;", + SkipResultsCheck: true, + }, + { + Query: "call dolt_add('t2');", + SkipResultsCheck: true, + }, + { + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "select * from t1;", + Expected: []sql.Row{{1, "resolved_t1"}}, + }, + { + Query: "select * from t2;", + Expected: []sql.Row{{1, "resolved_t2"}}, + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{}, + }, + { + Query: "select committer, email, message, date from dolt_log limit 1;", + Expected: []sql.Row{{"Branch User", "branch@example.com", "add rows from branch1", timeEquals("2022-02-01T10:30:00Z")}}, + }, + }, + }, + { + Name: "cherry-pick --continue: mutually exclusive with --abort", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + "insert into t values (1, 'branch1_value');", + "call dolt_commit('-am', 'add row from branch1');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + "insert into t values (1, 'main_value');", + "call dolt_commit('-am', 'add row from main');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "set @@dolt_allow_commit_conflicts = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{"", 1, 0, 0}}, + }, + { + Query: "call dolt_cherry_pick('--continue', '--abort');", + ExpectedErrStr: "error: --continue and --abort are mutually exclusive", + }, + { + Query: "call dolt_cherry_pick('--abort', '--continue');", + ExpectedErrStr: "error: --continue and --abort are mutually exclusive", + }, + }, + }, + { + Name: "cherry-pick: constraint violations only (no merge state)", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "call dolt_commit('-Am', 'create table t');", + "call dolt_checkout('-b', 'branch1');", + // On branch1, insert a value that will violate constraint" + "insert into t values (1, 'forbidden');", + "call dolt_commit('-am', 'add forbidden value');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + // Add constraint on main after the branch + "alter table t add CONSTRAINT chk_not_forbidden CHECK (v != 'forbidden');", + "call dolt_commit('-am', 'add check constraint');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "set @@dolt_allow_commit_conflicts = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "set @@dolt_force_transaction_commit = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{"", 0, 0, 1}}, // 1 constraint violation + }, + { + Query: "select violation_type, pk, v from dolt_constraint_violations_t;", + Expected: []sql.Row{{"check constraint", 1, "forbidden"}}, + }, + { + // Try to continue with constraint violations still present + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{"", 0, 0, 1}}, // Still has constraint violation + }, + { + // Fix the violation + Query: "update t set v = 'allowed' where pk = 1;", + Expected: []sql.Row{{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}}, + }, + { + Query: "delete from dolt_constraint_violations_t;", + SkipResultsCheck: true, + }, + { + Query: "call dolt_add('t');", + Expected: []sql.Row{{0}}, + }, + { + // Now continue should succeed and preserve original commit metadata + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1, "allowed"}}, + }, + }, + }, + { + Name: "cherry-pick --continue: with both conflicts and constraint violations", + SetUpScript: []string{ + "create table t (pk int primary key, v varchar(100));", + "insert into t values (1, 'initial');", + "call dolt_commit('-Am', 'create table t and row');", + "call dolt_checkout('-b', 'branch1');", + "-- On branch1, modify existing row and add new row with value that will violate constraint", + "update t set v = 'branch1_value' where pk = 1;", + "insert into t values (2, 'forbidden');", + "call dolt_commit('-am', 'modify row 1 and add row 2 with forbidden value');", + "set @commit1 = dolt_hashof('HEAD');", + "call dolt_checkout('main');", + "-- On main, change row 1 to create conflict and add constraint", + "update t set v = 'main_value' where pk = 1;", + "alter table t add CONSTRAINT chk_not_forbidden CHECK (v != 'forbidden');", + "call dolt_commit('-am', 'main changes and add constraint');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "set @@dolt_allow_commit_conflicts = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "set @@dolt_force_transaction_commit = 1;", + Expected: []sql.Row{{types.OkResult{}}}, + }, + { + Query: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{"", 1, 0, 1}}, // 1 data conflict, 1 constraint violation + }, + { + Query: "select * from dolt_conflicts;", + Expected: []sql.Row{{"t", uint64(1)}}, + }, + { + Query: "select violation_type, pk, v from dolt_constraint_violations_t;", + Expected: []sql.Row{{"check constraint", 2, "forbidden"}}, + }, + { + // Try to continue with both conflicts and violations + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{"", 1, 0, 1}}, // Still has both issues + }, + { + // Resolve the conflict + Query: "update t set v = 'resolved_value' where pk = 1;", + SkipResultsCheck: true, + }, + { + Query: "delete from dolt_conflicts_t;", + SkipResultsCheck: true, + }, + { + Query: "call dolt_add('t');", + SkipResultsCheck: true, + }, + { + // Try again - still has constraint violation + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{"", 0, 0, 1}}, // Only constraint violation remains + }, + { + // Fix the constraint violation + Query: "delete from t where pk = 2;", + SkipResultsCheck: true, + }, + { + Query: "delete from dolt_constraint_violations_t;", + SkipResultsCheck: true, + }, + { + Query: "call dolt_add('t');", + SkipResultsCheck: true, + }, + { + // Now continue should succeed + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{doltCommit, 0, 0, 0}}, + }, + { + Query: "select * from t;", + Expected: []sql.Row{{1, "resolved_value"}}, + }, + }, + }, +} diff --git a/integration-tests/bats/cherry-pick.bats b/integration-tests/bats/cherry-pick.bats index 67296c434ed..6dd8a26e483 100644 --- a/integration-tests/bats/cherry-pick.bats +++ b/integration-tests/bats/cherry-pick.bats @@ -662,3 +662,72 @@ teardown() { [[ "$output" =~ "Integration Manager,integration@company.com" ]] || false [[ "$output" =~ "Merge integration_branch" ]] || false } +@test "cherry-pick: --continue after resolving conflicts" { + dolt branch continue_test + dolt --branch continue_test sql -q "INSERT INTO test VALUES (100, 'branch1')" + dolt --branch continue_test add . + dolt --branch continue_test commit --author="Feature Dev " --date="2022-01-01T12:00:00" -m "Add row from branch1" + COMMIT1=$(get_head_commit continue_test) + + dolt sql -q "INSERT INTO test VALUES (100, 'main')" + dolt add . + dolt commit -am "Add row from main" + + run dolt cherry-pick $COMMIT1 + [ $status -eq 1 ] + [[ $output =~ "Unable to apply commit cleanly due to conflicts" ]] || false + + # Resolve the conflict (need to disable autocommit for conflict resolution) + dolt sql -q "SET autocommit = 0; UPDATE test SET v = 'resolved' WHERE pk = 100; DELETE FROM dolt_conflicts_test; COMMIT;" + dolt add test + + run dolt cherry-pick --continue --abort + [ $status -eq 1 ] + [[ $output =~ "--continue and --abort are mutually exclusive" ]] || false + + run dolt cherry-pick --continue + [ $status -eq 0 ] + + # Verify the commit was created with original metadata + run dolt log -n 1 + [ $status -eq 0 ] + [[ $output =~ "Feature Dev" ]] || false + [[ $output =~ "feature@example.com" ]] || false + [[ $output =~ "Add row from branch1" ]] || false + + # Verify the resolved data is present + run dolt sql -q "SELECT * FROM test WHERE pk = 100" -r csv + [ $status -eq 0 ] + [[ $output =~ "100,resolved" ]] || false +} + +@test "cherry-pick: --continue with no active cherry-pick" { + run dolt cherry-pick --continue + [ $status -eq 1 ] + [[ $output =~ "There is no cherry-pick merge to continue" ]] || false +} + +@test "cherry-pick: --continue with unresolved conflicts" { + # Create a branch with a conflicting change + dolt branch continue_test2 + dolt --branch continue_test2 sql -q "INSERT INTO test VALUES (100, 'branch1')" + dolt --branch continue_test2 add . + dolt --branch continue_test2 commit -am "Add row from branch1" + COMMIT1=$(get_head_commit continue_test2) + + # Create a conflicting change on main + dolt sql -q "INSERT INTO test VALUES (100, 'main')" + dolt add . + dolt commit -am "Add row from main" + + # Cherry-pick should create a conflict + run dolt cherry-pick $COMMIT1 + [ $status -eq 1 ] + [[ $output =~ "Unable to apply commit cleanly due to conflicts" ]] || false + + # Try to continue without resolving conflicts + run dolt cherry-pick --continue + [ $status -eq 1 ] + [[ $output =~ "Unable to apply commit cleanly due to conflicts" ]] || false + +} \ No newline at end of file