From 154b166ed9b2d4ce7b7844c8e2030b6c40ac25ec Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 18 Feb 2026 19:32:51 +0000 Subject: [PATCH 01/12] Move cherry-pick enginetests into their own file --- .../doltcore/sqle/enginetest/dolt_queries.go | 583 ----------------- .../enginetest/dolt_queries_cherry_pick.go | 604 ++++++++++++++++++ 2 files changed, 604 insertions(+), 583 deletions(-) create mode 100644 go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index b033d19f77a..b0d244b7591 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -7225,589 +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;", - Expected: []sql.Row{{"generated_foo", byte(0), "new table"}}, - }, - { - // 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{ { 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..855e4d25e76 --- /dev/null +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -0,0 +1,604 @@ +// 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 ( + "github.com/dolthub/go-mysql-server/enginetest/queries" + "github.com/dolthub/go-mysql-server/sql" +) + +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;", + Expected: []sql.Row{{"generated_foo", byte(0), "new table"}}, + }, + { + // 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}}, + },*/ + }, + }, +} From 81fb9273635aca2ec970b8939d88aec989af7cfd Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 18 Feb 2026 20:00:30 +0000 Subject: [PATCH 02/12] First pass on cherry-pick --continue tests --- .../enginetest/dolt_queries_cherry_pick.go | 201 ++++++++++++++++-- 1 file changed, 182 insertions(+), 19 deletions(-) diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go index 855e4d25e76..11b4d192f35 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -28,7 +28,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "call dolt_checkout('-b', 'branch1');", "insert into t values (1, \"one\");", "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -54,7 +54,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "call dolt_checkout('-b', 'branch1');", "insert into t values (1, \"one\");", "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -76,7 +76,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "call dolt_checkout('-b', 'branch1');", "insert into t values (1, \"one\");", "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", "INSERT INTO t VALUES (100, 'onehundy');", }, @@ -103,7 +103,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -121,10 +121,10 @@ var DoltCherryPickTests = []queries.ScriptTest{ "call dolt_checkout('-b', 'branch1');", "insert into t values (1, \"one\");", "call dolt_commit('-am', 'adding row 1');", - "set @commit1 = hashof('HEAD');", + "set @commit1 = dolt_hashof('HEAD');", "insert into t values (2, \"two\");", "call dolt_commit('-am', 'adding row 2');", - "set @commit2 = hashof('HEAD');", + "set @commit2 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -150,7 +150,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, { // 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');", + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", Expected: []sql.Row{{1}}, }, }, @@ -188,7 +188,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -202,7 +202,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, { // 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');", + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", Expected: []sql.Row{{1}}, }, { @@ -224,7 +224,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "call dolt_checkout('-b', 'branch1');", "drop table dropme;", "call dolt_commit('-Am', 'drop table dropme');", - "set @commit1 = hashof('HEAD');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -250,7 +250,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -272,7 +272,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -294,7 +294,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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');", + "set @commit1 = dolt_hashof('HEAD');", "call dolt_checkout('main');", }, Assertions: []queries.ScriptTestAssertion{ @@ -324,7 +324,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, Assertions: []queries.ScriptTestAssertion{ { - Query: "call dolt_cherry_pick(hashof('branch1'));", + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", Expected: []sql.Row{{"", 1, 0, 0}}, }, { @@ -368,7 +368,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, Assertions: []queries.ScriptTestAssertion{ { - Query: "call dolt_cherry_pick(hashof('branch1'));", + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", Expected: []sql.Row{{"", 1, 0, 0}}, }, { @@ -411,7 +411,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, Assertions: []queries.ScriptTestAssertion{ { - Query: "call dolt_cherry_pick(hashof('branch1'));", + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", Expected: []sql.Row{{"", 1, 0, 0}}, }, { @@ -450,7 +450,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, { // 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');", + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", Expected: []sql.Row{{1}}, }, }, @@ -519,7 +519,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, { // 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');", + Query: "select count(*) from dolt_commit_ancestors where commit_hash = dolt_hashof('HEAD');", Expected: []sql.Row{{1}}, }, }, @@ -547,7 +547,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, Assertions: []queries.ScriptTestAssertion{ { - Query: "call dolt_cherry_pick(hashof('branch1'));", + Query: "call dolt_cherry_pick(dolt_hashof('branch1'));", Expected: []sql.Row{{"", 1, 0, 0}}, }, { @@ -601,4 +601,167 @@ var DoltCherryPickTests = []queries.ScriptTest{ },*/ }, }, + { + 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_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: "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');", + ExpectedErrStr: "error: cannot continue cherry-pick with unresolved conflicts", + }, + { + 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 commiter, message, date from dolt_log limit 1;", + Expected: []sql.Row{{"Test User ", "add row from branch1", "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: "call dolt_cherry_pick(@commit1);", + Expected: []sql.Row{{"", 2, 0, 0}}, + }, + { + Query: "select table_name from dolt_conflicts order by table_name;", + Expected: []sql.Row{{"t1"}, {"t2"}}, + }, + { + Query: "update t1 set v = 'resolved_t1' where pk = 1;", + Expected: []sql.Row{{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}}, + }, + { + Query: "update t2 set v = 'resolved_t2' where pk = 1;", + Expected: []sql.Row{{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}}, + }, + { + Query: "call dolt_add('t1', 't2');", + Expected: []sql.Row{{0}}, + }, + { + 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 commiter, message, date from dolt_log limit 1;", + Expected: []sql.Row{{"Branch User ", "add rows from branch1", "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: "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", + }, + }, + }, } From 08fd48b377187e13c185aae5769d7de9befe8025 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 18 Feb 2026 22:16:42 +0000 Subject: [PATCH 03/12] checkpoint for tests --- .../enginetest/dolt_queries_cherry_pick.go | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go index 11b4d192f35..9f10708dbe5 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -17,6 +17,8 @@ package enginetest import ( "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" ) var DoltCherryPickTests = []queries.ScriptTest{ @@ -609,12 +611,16 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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_dolt_hashof('HEAD');", + "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}}, @@ -654,7 +660,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ Expected: []sql.Row{{1, "resolved_value"}}, }, { - Query: "select commiter, message, date from dolt_log limit 1;", + Query: "select committer, message, date from dolt_log limit 1;", Expected: []sql.Row{{"Test User ", "add row from branch1", "2022-01-01T12:00:00Z"}}, }, }, @@ -694,6 +700,10 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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}}, @@ -731,7 +741,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ Expected: []sql.Row{}, }, { - Query: "select commiter, message, date from dolt_log limit 1;", + Query: "select committer, message, date from dolt_log limit 1;", Expected: []sql.Row{{"Branch User ", "add rows from branch1", "2022-02-01T10:30:00Z"}}, }, }, @@ -750,6 +760,10 @@ var DoltCherryPickTests = []queries.ScriptTest{ "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}}, From 1d47e96e776eea7f9f54de6450ff17a7dfed4e65 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Wed, 18 Feb 2026 23:41:54 +0000 Subject: [PATCH 04/12] Implement cherry-pick --continue --- go/cmd/dolt/cli/arg_parser_helpers.go | 1 + go/cmd/dolt/commands/cherry-pick.go | 55 ++++++++++- .../doltcore/cherry_pick/cherry_pick.go | 92 +++++++++++++++++++ .../sqle/dprocedures/dolt_cherry_pick.go | 8 ++ .../enginetest/dolt_queries_cherry_pick.go | 62 ++++++++++--- 5 files changed, 202 insertions(+), 16 deletions(-) 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..8c18a687a6b 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") @@ -97,7 +97,12 @@ func (cmd CherryPickCmd) Exec(ctx context.Context, commandStr string, args []str } 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 +122,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") @@ -220,7 +225,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 +240,46 @@ 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)) + } + + // Get the commit hash from the result + commitHash := "" + for _, row := range rows { + var ok bool + commitHash, ok, err = sql.Unwrap[string](sqlCtx, row[0]) + if err != nil { + return fmt.Errorf("Unable to parse commitHash column: %w", err) + } + if !ok { + return fmt.Errorf("Unexpected type for commitHash column, expected string, found %T", row[0]) + } + } + + // Print the commit info on successful continue + commit, err := getCommitInfo(queryist, sqlCtx, 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/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index e7fd3e64059..f706c14c69e 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -231,6 +231,98 @@ 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() + + // Check if there are any unresolved conflicts + hasConflicts, err := doltdb.HasConflicts(ctx, ws.WorkingRoot()) + if err != nil { + return "", 0, 0, 0, fmt.Errorf("error: unable to check for conflicts: %w", err) + } + if hasConflicts { + return "", 0, 0, 0, fmt.Errorf("error: cannot continue cherry-pick with unresolved conflicts") + } + + // Check if there are unstaged changes (working != staged) + stagedRoot := ws.StagedRoot() + workingRoot := ws.WorkingRoot() + + 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") + } + + // Get the original commit metadata from the merge state + 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, + Name: cherryCommitMeta.Name, + Email: cherryCommitMeta.Email, + } + + // Get the roots from the working set for creating the commit + 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") + } + + // Clear the merge state from the working set before committing + // This ensures DoltCommit sees a clean working set + 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. 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_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go index 9f10708dbe5..30689631348 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -15,12 +15,35 @@ 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", @@ -660,8 +683,8 @@ var DoltCherryPickTests = []queries.ScriptTest{ Expected: []sql.Row{{1, "resolved_value"}}, }, { - Query: "select committer, message, date from dolt_log limit 1;", - Expected: []sql.Row{{"Test User ", "add row from branch1", "2022-01-01T12:00:00Z"}}, + 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")}}, }, }, }, @@ -709,20 +732,37 @@ var DoltCherryPickTests = []queries.ScriptTest{ Expected: []sql.Row{{"", 2, 0, 0}}, }, { - Query: "select table_name from dolt_conflicts order by table_name;", + Query: "select `table` from dolt_conflicts order by `table`;", Expected: []sql.Row{{"t1"}, {"t2"}}, }, { - Query: "update t1 set v = 'resolved_t1' where pk = 1;", - Expected: []sql.Row{{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}}, + Query: "update t1 set v = 'resolved_t1' where pk = 1;", + SkipResultsCheck: true, }, { - Query: "update t2 set v = 'resolved_t2' where pk = 1;", - Expected: []sql.Row{{types.OkResult{RowsAffected: 1, Info: plan.UpdateInfo{Matched: 1, Updated: 1}}}}, + Query: "delete from dolt_conflicts_t1;", + SkipResultsCheck: true, }, { - Query: "call dolt_add('t1', 't2');", - Expected: []sql.Row{{0}}, + Query: "call dolt_add('t1');", + SkipResultsCheck: true, + }, + { + // Should still have one remaining conflict. + Query: "call dolt_cherry_pick('--continue');", + ExpectedErrStr: "error: cannot continue cherry-pick with unresolved conflicts", + }, + { + 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');", @@ -741,8 +781,8 @@ var DoltCherryPickTests = []queries.ScriptTest{ Expected: []sql.Row{}, }, { - Query: "select committer, message, date from dolt_log limit 1;", - Expected: []sql.Row{{"Branch User ", "add rows from branch1", "2022-02-01T10:30:00Z"}}, + 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")}}, }, }, }, From af02bd778c6e0a0f8f457ecbe754726d124664ce Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 19 Feb 2026 00:37:13 +0000 Subject: [PATCH 05/12] Add bats test for cherry-pick --continue --- go/cmd/dolt/commands/cherry-pick.go | 6 +++ integration-tests/bats/cherry-pick.bats | 69 +++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/go/cmd/dolt/commands/cherry-pick.go b/go/cmd/dolt/commands/cherry-pick.go index 8c18a687a6b..4469fef9923 100644 --- a/go/cmd/dolt/commands/cherry-pick.go +++ b/go/cmd/dolt/commands/cherry-pick.go @@ -96,6 +96,12 @@ 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.Context, queryist.Queryist) return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) diff --git a/integration-tests/bats/cherry-pick.bats b/integration-tests/bats/cherry-pick.bats index 67296c434ed..642a9a8913b 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 =~ "cannot continue cherry-pick with unresolved conflicts" ]] || false + +} \ No newline at end of file From 31981e58210d472489901540d3a8972390abc52b Mon Sep 17 00:00:00 2001 From: macneale4 Date: Thu, 19 Feb 2026 00:52:42 +0000 Subject: [PATCH 06/12] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/sqle/enginetest/dolt_queries.go | 1 - 1 file changed, 1 deletion(-) diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index b0d244b7591..a3af6ff55b9 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -7225,7 +7225,6 @@ var DoltAutoIncrementTests = []queries.ScriptTest{ }, } - var DoltCommitTests = []queries.ScriptTest{ { Name: "CALL DOLT_COMMIT('-ALL') adds all tables (including new ones) to the commit.", From ad5eb54bc1e974db6371fd6d17c2adaf71fdc97a Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 19 Feb 2026 01:06:47 +0000 Subject: [PATCH 07/12] code clean up and simplification --- go/cmd/dolt/commands/cherry-pick.go | 20 ++++---------------- go/cmd/dolt/commands/commit.go | 2 +- go/cmd/dolt/commands/log.go | 2 +- go/cmd/dolt/commands/merge.go | 2 +- go/cmd/dolt/commands/pull.go | 2 +- go/cmd/dolt/commands/revert.go | 2 +- go/cmd/dolt/commands/show.go | 2 +- go/cmd/dolt/commands/utils.go | 6 +++--- 8 files changed, 13 insertions(+), 25 deletions(-) diff --git a/go/cmd/dolt/commands/cherry-pick.go b/go/cmd/dolt/commands/cherry-pick.go index 4469fef9923..05775455267 100644 --- a/go/cmd/dolt/commands/cherry-pick.go +++ b/go/cmd/dolt/commands/cherry-pick.go @@ -212,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) } @@ -256,22 +256,10 @@ func cherryPickContinue(sqlCtx *sql.Context, queryist cli.Queryist) error { if len(rows) != 1 { return fmt.Errorf("error: unexpected number of rows returned from dolt_cherry_pick: %d", len(rows)) } + row := rows[0] + commitHash := fmt.Sprintf("%v", row[0]) - // Get the commit hash from the result - commitHash := "" - for _, row := range rows { - var ok bool - commitHash, ok, err = sql.Unwrap[string](sqlCtx, row[0]) - if err != nil { - return fmt.Errorf("Unable to parse commitHash column: %w", err) - } - if !ok { - return fmt.Errorf("Unexpected type for commitHash column, expected string, found %T", row[0]) - } - } - - // Print the commit info on successful continue - 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) } 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) From c62c9e5057ee9217e4a7d2be7f8f3fc76a4263f5 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 19 Feb 2026 11:38:05 -0800 Subject: [PATCH 08/12] Better comments --- go/libraries/doltcore/cherry_pick/cherry_pick.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/go/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index f706c14c69e..ba933b4df94 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -260,6 +260,8 @@ func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, stagedRoot := ws.StagedRoot() workingRoot := ws.WorkingRoot() + // This is a little strict. Technically, you could use the dolt_workspace table to stage something + // and result in different roots, but this seems like the right thing to do after a merge conflict resolution step. isClean, err := rootsEqual(stagedRoot, workingRoot) if err != nil { return "", 0, 0, 0, fmt.Errorf("error: unable to compare staged and working roots: %w", err) @@ -268,7 +270,6 @@ func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, return "", 0, 0, 0, fmt.Errorf("error: cannot continue cherry-pick with unstaged changes") } - // Get the original commit metadata from the merge state cherryCommit := mergeState.Commit() if cherryCommit == nil { return "", 0, 0, 0, fmt.Errorf("error: unable to get original commit from merge state") @@ -283,12 +284,11 @@ func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, commitProps := actions.CommitStagedProps{ Message: cherryCommitMeta.Description, Date: cherryCommitMeta.Time(), - AllowEmpty: false, + AllowEmpty: false, // in a conflict workflow, never will be 'true' Name: cherryCommitMeta.Name, Email: cherryCommitMeta.Email, } - // Get the roots from the working set for creating the commit roots, ok := doltSession.GetRoots(ctx, dbName) if !ok { return "", 0, 0, 0, fmt.Errorf("fatal: unable to load roots for %s", dbName) @@ -302,8 +302,6 @@ func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, return "", 0, 0, 0, fmt.Errorf("error: no changes to commit") } - // Clear the merge state from the working set before committing - // This ensures DoltCommit sees a clean working set clearedWs := ws.ClearMerge() err = doltSession.SetWorkingSet(ctx, dbName, clearedWs) if err != nil { From 26a3aa4f3c0573185e4175c13ce2abedba23934e Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 19 Feb 2026 20:47:51 +0000 Subject: [PATCH 09/12] the cherry_pick interface shouldn't error when there are conflicts --- go/cmd/dolt/commands/cherry-pick.go | 24 +++++++++++++++++ .../doltcore/cherry_pick/cherry_pick.go | 27 ++++++++++++++----- .../enginetest/dolt_queries_cherry_pick.go | 10 +++---- integration-tests/bats/cherry-pick.bats | 2 +- 4 files changed, 50 insertions(+), 13 deletions(-) diff --git a/go/cmd/dolt/commands/cherry-pick.go b/go/cmd/dolt/commands/cherry-pick.go index 05775455267..5cc82c175c4 100644 --- a/go/cmd/dolt/commands/cherry-pick.go +++ b/go/cmd/dolt/commands/cherry-pick.go @@ -256,7 +256,31 @@ func cherryPickContinue(sqlCtx *sql.Context, queryist cli.Queryist) error { 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) diff --git a/go/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index ba933b4df94..bf23cb819d8 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -247,18 +247,31 @@ func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, } mergeState := ws.MergeState() - // Check if there are any unresolved conflicts - hasConflicts, err := doltdb.HasConflicts(ctx, ws.WorkingRoot()) + // 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) } - if hasConflicts { - return "", 0, 0, 0, fmt.Errorf("error: cannot continue cherry-pick with unresolved conflicts") + 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) - // Check if there are unstaged changes (working != staged) - stagedRoot := ws.StagedRoot() - workingRoot := ws.WorkingRoot() + // 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, but this seems like the right thing to do after a merge conflict resolution step. diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go index 30689631348..29ae05b8800 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -659,8 +659,8 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, }, { - Query: "call dolt_cherry_pick('--continue');", - ExpectedErrStr: "error: cannot continue cherry-pick with unresolved conflicts", + Query: "call dolt_cherry_pick('--continue');", + Expected: []sql.Row{{"", 1, 0, 0}}, }, { Query: "delete from dolt_conflicts_t", @@ -748,9 +748,9 @@ var DoltCherryPickTests = []queries.ScriptTest{ SkipResultsCheck: true, }, { - // Should still have one remaining conflict. - Query: "call dolt_cherry_pick('--continue');", - ExpectedErrStr: "error: cannot continue cherry-pick with unresolved conflicts", + // 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;", diff --git a/integration-tests/bats/cherry-pick.bats b/integration-tests/bats/cherry-pick.bats index 642a9a8913b..6dd8a26e483 100644 --- a/integration-tests/bats/cherry-pick.bats +++ b/integration-tests/bats/cherry-pick.bats @@ -728,6 +728,6 @@ teardown() { # Try to continue without resolving conflicts run dolt cherry-pick --continue [ $status -eq 1 ] - [[ $output =~ "cannot continue cherry-pick with unresolved conflicts" ]] || false + [[ $output =~ "Unable to apply commit cleanly due to conflicts" ]] || false } \ No newline at end of file From 513b66e304857a875944b318b83ef9a6673cf163 Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Thu, 19 Feb 2026 22:12:57 +0000 Subject: [PATCH 10/12] WIP - suspect --- .../doltcore/cherry_pick/cherry_pick.go | 33 ++-- .../enginetest/dolt_queries_cherry_pick.go | 147 ++++++++++++++++++ 2 files changed, 170 insertions(+), 10 deletions(-) diff --git a/go/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index bf23cb819d8..48e4f587eda 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -474,21 +474,34 @@ 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/enginetest/dolt_queries_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go index 29ae05b8800..4e63ed179d3 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -818,4 +818,151 @@ var DoltCherryPickTests = []queries.ScriptTest{ }, }, }, + { + 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"}}, + }, + }, + }, } From 836cc49f76af3497fcd84af8bd08f12a8824e06f Mon Sep 17 00:00:00 2001 From: macneale4 Date: Thu, 19 Feb 2026 23:03:08 +0000 Subject: [PATCH 11/12] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/cherry_pick/cherry_pick.go | 4 ++-- .../doltcore/sqle/enginetest/dolt_queries_cherry_pick.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index 48e4f587eda..9acc0d41061 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -481,7 +481,7 @@ func cherryPick(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots, 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 { @@ -490,7 +490,7 @@ func cherryPick(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots, hasArtifacts = true } } - + if hasArtifacts { ws, err := dSess.WorkingSet(ctx, dbName) if err != nil { diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go index 4e63ed179d3..378dc417fe7 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_cherry_pick.go @@ -853,7 +853,7 @@ var DoltCherryPickTests = []queries.ScriptTest{ { // Try to continue with constraint violations still present Query: "call dolt_cherry_pick('--continue');", - Expected: []sql.Row{{"", 0, 0, 1}}, // Still has constraint violation + Expected: []sql.Row{{"", 0, 0, 1}}, // Still has constraint violation }, { // Fix the violation From 2629bcb67f88a8fa0a1696325f39f92354f2faca Mon Sep 17 00:00:00 2001 From: Neil Macneale IV Date: Fri, 20 Feb 2026 18:09:10 +0000 Subject: [PATCH 12/12] Add comment requested in PR --- go/libraries/doltcore/cherry_pick/cherry_pick.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go/libraries/doltcore/cherry_pick/cherry_pick.go b/go/libraries/doltcore/cherry_pick/cherry_pick.go index 9acc0d41061..b7172dc3819 100644 --- a/go/libraries/doltcore/cherry_pick/cherry_pick.go +++ b/go/libraries/doltcore/cherry_pick/cherry_pick.go @@ -274,7 +274,8 @@ func ContinueCherryPick(ctx *sql.Context, dbName string) (string, int, int, int, } // This is a little strict. Technically, you could use the dolt_workspace table to stage something - // and result in different roots, but this seems like the right thing to do after a merge conflict resolution step. + // 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)