Merge pull request #10532 from dolthub/macneale4-claude/verification-resolution

Adding --continue flag to cherry-pick
This commit is contained in:
Neil Macneale IV
2026-02-20 10:46:25 -08:00
committed by GitHub
14 changed files with 1251 additions and 609 deletions

View File

@@ -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.")

View File

@@ -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 \"<message>\") 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 \"<message>\") 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 {

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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())

View File

@@ -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())

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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

View File

@@ -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.",

View File

@@ -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 <test@example.com>', '--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 <branch@example.com>', '--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"}},
},
},
},
}

View File

@@ -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 <feature@example.com>" --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
}