From 70a0528b6db8e8a37bd13faf2e2d968a14e3b9b1 Mon Sep 17 00:00:00 2001 From: Vinai Rachakonda Date: Tue, 16 Feb 2021 20:40:23 -0800 Subject: [PATCH] Vinai/dolt merge p2 (#1322) Adds addition functionality to DOLT_MERGE beyond just ffs --- bats/sql-add.bats | 16 +- bats/sql-checkout.bats | 33 ++ bats/sql-merge.bats | 377 +++++++++++++++++- bats/sql-server.bats | 81 ++++ go/cmd/dolt/cli/arg_parser_helpers.go | 10 + go/cmd/dolt/commands/merge.go | 38 +- go/cmd/dolt/commands/pull.go | 2 +- go/cmd/dolt/commands/sql.go | 3 +- .../dtestutils/testcommands/command.go | 2 +- go/libraries/doltcore/env/actions/checkout.go | 6 +- go/libraries/doltcore/env/environment.go | 101 +---- go/libraries/doltcore/env/repo_state.go | 99 +++++ .../doltcore/sqle/dfunctions/dolt_checkout.go | 15 +- .../doltcore/sqle/dfunctions/dolt_commit.go | 11 +- .../doltcore/sqle/dfunctions/dolt_merge.go | 230 ++++++++++- 15 files changed, 859 insertions(+), 165 deletions(-) diff --git a/bats/sql-add.bats b/bats/sql-add.bats index ad9e376100..530345e8e4 100644 --- a/bats/sql-add.bats +++ b/bats/sql-add.bats @@ -51,6 +51,21 @@ teardown() { [[ "$output" =~ "$regex" ]] || false } +@test "DOLT_ADD all w/ . combined with DOLT_COMMIT -a works" { + run dolt sql -q "SELECT DOLT_ADD('.')" + run dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Commit1')" + + # Check that everything was added + run dolt diff + [ "$status" -eq 0 ] + [ "$output" = "" ] + + run dolt log + [ $status -eq 0 ] + [[ "$output" =~ "Commit1" ]] || false + [[ "$output" =~ "Bats Tests " ]] || false +} + @test "DOLT_ADD can take in one table" { run dolt sql -q "SELECT DOLT_ADD('test')" run dolt sql -q "SELECT DOLT_COMMIT('-m', 'Commit1')" @@ -68,7 +83,6 @@ teardown() { [[ "$output" =~ "$regex" ]] || false } - @test "DOLT_ADD can take in multiple tables" { run dolt sql -q "SELECT DOLT_ADD('test', 'test2')" run dolt sql -q "SELECT DOLT_COMMIT('-m', 'Commit1')" diff --git a/bats/sql-checkout.bats b/bats/sql-checkout.bats index 94ae31569b..82412a6351 100644 --- a/bats/sql-checkout.bats +++ b/bats/sql-checkout.bats @@ -158,6 +158,39 @@ SQL [[ ! "$output" =~ "4" ]] || false } +@test "DOLT_CHECKOUT between branches operating on the same table works." { + run dolt sql << SQL +CREATE TABLE one_pk ( + pk1 BIGINT NOT NULL, + c1 BIGINT, + c2 BIGINT, + PRIMARY KEY (pk1) +); +SELECT DOLT_COMMIT('-a', '-m', 'add tables'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,0,0); +SELECT DOLT_COMMIT('-a', '-m', 'changed master'); +SELECT DOLT_CHECKOUT('feature-branch'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,1,1); +SQL + [ $status -eq 0 ] + + run dolt sql -q "SELECT * FROM one_pk" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "pk1,c1,c2" ]] || false + [[ "$output" =~ "0,1,1" ]] || false + [[ ! "$output" =~ "0,0,0" ]] || false + + dolt commit -a -m "changed feature branch" + dolt checkout master + run dolt sql -q "SELECT * FROM one_pk" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "pk1,c1,c2" ]] || false + [[ ! "$output" =~ "0,1,1" ]] || false + [[ "$output" =~ "0,0,0" ]] || false +} + get_head_commit() { dolt log -n 1 | grep -m 1 commit | cut -c 8- } diff --git a/bats/sql-merge.bats b/bats/sql-merge.bats index 0cff8afb9d..48a47027ef 100644 --- a/bats/sql-merge.bats +++ b/bats/sql-merge.bats @@ -17,6 +17,13 @@ teardown() { teardown_common } +@test "DOLT_MERGE with unknown branch name throws an error" { + dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Step 1');" + + run dolt sql -q "SELECT DOLT_MERGE('feature-branch');" + [ $status -eq 1 ] +} + @test "DOLT_MERGE works with ff" { dolt sql << SQL SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); @@ -37,9 +44,9 @@ SQL [ $status -eq 0 ] [[ "$output" =~ "3" ]] || false - run dolt sql -q "SELECT COUNT(*) FROM test;" + run dolt status [ $status -eq 0 ] - [[ "$output" =~ "4" ]] || false + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false run dolt sql -q "SELECT * FROM test;" -r csv [ $status -eq 0 ] @@ -48,6 +55,10 @@ SQL [[ "$output" =~ "2" ]] || false [[ "$output" =~ "3" ]] || false [[ "$output" =~ "1000" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM test;" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "4" ]] || false } @test "DOLT_MERGE correctly returns head and working session variables." { @@ -70,13 +81,371 @@ SQL [[ "$output" =~ $head_hash ]] || false } -@test "DOLT_MERGE with unknown branch name name throws an error" { - dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Step 1');" +@test "DOLT_MERGE correctly merges branches with differing content in same table without conflicts" { + dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'Insert 3'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO test VALUES (500000); +SELECT DOLT_COMMIT('-a', '-m', 'Insert 500000'); +SELECT DOLT_MERGE('feature-branch'); +SQL + + run dolt sql -q "SELECT * FROM test" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "pk" ]] || false + [[ "$output" =~ "0" ]] || false + [[ "$output" =~ "1" ]] || false + [[ "$output" =~ "2" ]] || false + [[ "$output" =~ "3" ]] || false + [[ "$output" =~ "500000" ]] || false + + run dolt log -n 1 + [ $status -eq 0 ] + [[ "$output" =~ "Insert 500000" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM dolt_log" + [ $status -eq 0 ] + [[ "$output" =~ "3" ]] || false + + run dolt status + [[ "$output" =~ "All conflicts fixed but you are still merging" ]] || false + [[ "$output" =~ "Changes to be committed:" ]] || false + [[ "$output" =~ ([[:space:]]*modified:[[:space:]]*test) ]] || false + + run dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Finish up Merge')"; + [ $status -eq 0 ] + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false + + run dolt log -n 1 + [ $status -eq 0 ] + [[ "$output" =~ "Finish up Merge" ]] || false +} + +@test "DOLT_MERGE works with no-ff" { + run dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'update feature-branch'); +SELECT DOLT_CHECKOUT('master'); +SELECT DOLT_MERGE('feature-branch', '-no-ff', '-m', 'this is a no-ff'); +SQL + [ $status -eq 0 ] + + run dolt log -n 1 + [ $status -eq 0 ] + [[ "$output" =~ "this is a no-ff" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM dolt_log" + [ $status -eq 0 ] + [[ "$output" =~ "4" ]] || false +} + +@test "DOLT_MERGE -no-ff correctly changes head and working session variables." { + dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'update feature-branch'); +SELECT DOLT_CHECKOUT('master'); +SQL + head_variable=@@dolt_repo_$$_head + head_hash=$(get_head_commit) + working_variable=@@dolt_repo_$$_working + working_hash=$(get_working_hash) + + run dolt sql -q "SELECT DOLT_MERGE('feature-branch', '-no-ff', '-m', 'this is a no-ff');" + [ $status -eq 0 ] + + run dolt sql -q "SELECT $head_variable" + [ $status -eq 0 ] + [[ ! "$output" =~ $head_hash ]] || false + + run dolt sql -q "SELECT $working_variable" + [ $status -eq 0 ] + [[ ! "$output" =~ $working_hash ]] || false +} + +@test "DOLT_MERGE properly detects merge conflicts, returns and error and then aborts." { + run dolt sql << SQL +CREATE TABLE one_pk ( + pk1 BIGINT NOT NULL, + c1 BIGINT, + c2 BIGINT, + PRIMARY KEY (pk1) +); +SELECT DOLT_COMMIT('-a', '-m', 'add tables'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,0,0); +SELECT DOLT_COMMIT('-a', '-m', 'changed master'); +SELECT DOLT_CHECKOUT('feature-branch'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,1,1); +SELECT DOLT_COMMIT('-a', '-m', 'changed feature branch'); +SELECT DOLT_CHECKOUT('master'); +SELECT DOLT_MERGE('feature-branch'); +SQL + [ $status -eq 1 ] + [[ $output =~ "merge has conflicts" ]] || false + + run dolt sql -q "SELECT DOLT_MERGE('--abort');" + [ $status -eq 0 ] + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false + + run dolt sql -q "SELECT * FROM one_pk;" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "pk1,c1,c2" ]] || false + [[ "$output" =~ "0,0,0" ]] || false + [[ ! "$output" =~ "0,1,1" ]] || false +} + +@test "DOLT_MERGE properly detects merge conflicts and renders the conflicts in dolt_conflicts." { + run dolt sql << SQL +CREATE TABLE one_pk ( + pk1 BIGINT NOT NULL, + c1 BIGINT, + c2 BIGINT, + PRIMARY KEY (pk1) +); +SELECT DOLT_COMMIT('-a', '-m', 'add tables'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,0,0); +SELECT DOLT_COMMIT('-a', '-m', 'changed master'); +SELECT DOLT_CHECKOUT('feature-branch'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,1,1); +SELECT DOLT_COMMIT('-a', '-m', 'changed feature branch'); +SELECT DOLT_CHECKOUT('master'); +SELECT DOLT_MERGE('feature-branch'); +SQL + [ $status -eq 1 ] + [[ $output =~ "merge has conflicts" ]] || false + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "You have unmerged tables" ]] || false + [[ "$output" =~ ([[:space:]]*both modified:[[:space:]]*one_pk) ]] || false + + run dolt sql -q "SELECT * FROM dolt_conflicts" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "table,num_conflicts" ]] || false + [[ "$output" =~ "one_pk,1" ]] || false + + # Go through the process of resolving commits + run dolt sql << SQL +REPLACE INTO one_pk (pk1, c1, c2) SELECT their_pk1, their_c1, their_c2 FROM dolt_conflicts_one_pk WHERE their_pk1 IS NOT NULL; +DELETE FROM one_pk WHERE pk1 in (SELECT base_pk1 FROM dolt_conflicts_one_pk WHERE their_pk1 IS NULL); +DELETE FROM dolt_conflicts_one_pk; +SQL + [ $status -eq 0 ] + + run dolt sql -q "SELECT * FROM dolt_conflicts" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "table,num_conflicts" ]] || false + [[ "$output" =~ "one_pk,0" ]] || false + + run dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Finish Resolving');" + [ $status -eq 0 ] + + run dolt sql -q "SELECT * FROM one_pk" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "pk1,c1,c2" ]] || false + [[ "$output" =~ "0,1,1" ]] || false + + run dolt sql -q "SELECT COUNT(*) from dolt_status;" + [ $status -eq 0 ] + [[ "$output" =~ "0" ]] || false +} + +@test "DOLT_MERGE with unresolved conflicts throws an error" { + run dolt sql << SQL +CREATE TABLE one_pk ( + pk1 BIGINT NOT NULL, + c1 BIGINT, + c2 BIGINT, + PRIMARY KEY (pk1) +); +SELECT DOLT_COMMIT('-a', '-m', 'add tables'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,0,0); +SELECT DOLT_COMMIT('-a', '-m', 'changed master'); +SELECT DOLT_CHECKOUT('feature-branch'); +INSERT INTO one_pk (pk1,c1,c2) VALUES (0,1,1); +SELECT DOLT_COMMIT('-a', '-m', 'changed feature branch'); +SELECT DOLT_CHECKOUT('master'); +SELECT DOLT_MERGE('feature-branch'); +SQL + [ $status -eq 1 ] + [[ $output =~ "merge has conflicts" ]] || false run dolt sql -q "SELECT DOLT_MERGE('feature-branch');" [ $status -eq 1 ] + [[ $output =~ "merge has unresolved conflicts" ]] || false +} + +@test "DOLT_MERGE during an active merge throws an error" { + run dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'Insert 3'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO test VALUES (500000); +SELECT DOLT_COMMIT('-a', '-m', 'Insert 500000'); +SELECT DOLT_MERGE('feature-branch'); +SELECT DOLT_MERGE('feature-branch'); +SQL + + [ $status -eq 1 ] + [[ $output =~ "merging is not possible because you have not committed an active merge" ]] || false +} + +@test "DOLT_MERGE works with ff and squash" { + dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'this is a ff'); +SELECT DOLT_CHECKOUT('master'); +SQL + run dolt sql -q "SELECT DOLT_MERGE('feature-branch', '--squash');" + [ $status -eq 0 ] + + run dolt log -n 1 + [ $status -eq 0 ] + [[ "$output" =~ "Step 1" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM dolt_log" + [ $status -eq 0 ] + [[ "$output" =~ "2" ]] || false + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "Changes to be committed:" ]] || false + [[ "$output" =~ ([[:space:]]*modified:[[:space:]]*test) ]] || false + + run dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'hi');" + [ $status -eq 0 ] + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false +} + +@test "DOLT_MERGE with no-ff and squash works." { + dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'Insert 3'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO test VALUES (500000); +SELECT DOLT_COMMIT('-a', '-m', 'Insert 500000'); +SELECT DOLT_MERGE('feature-branch', '--squash'); +SQL + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "Changes to be committed:" ]] || false + [[ "$output" =~ ([[:space:]]*modified:[[:space:]]*test) ]] || false + + run dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Finish up Merge')"; + [ $status -eq 0 ] + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false + + run dolt log -n 1 + [ $status -eq 0 ] + [[ "$output" =~ "Finish up Merge" ]] || false +} + +@test "DOLT_MERGE throws errors with working set changes." { + run dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +SELECT DOLT_COMMIT('-a', '-m', 'this is a ff'); +SELECT DOLT_CHECKOUT('master'); +CREATE TABLE tbl ( + pk int primary key +); +SELECT DOLT_MERGE('feature-branch'); +SQL + [ $status -eq 1 ] + [[ "$output" =~ "cannot merge with uncommitted changes" ]] || false +} + +@test "DOLT_MERGE with a long series of changing operations works." { + dolt sql << SQL +SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); +SELECT DOLT_CHECKOUT('-b', 'feature-branch'); +INSERT INTO test VALUES (3); +INSERT INTO test VALUES (4); +INSERT INTO test VALUES (21232); +DELETE FROM test WHERE pk=4; +UPDATE test SET pk=21 WHERE pk=21232; +SELECT DOLT_COMMIT('-a', '-m', 'Insert 3'); +SELECT DOLT_CHECKOUT('master'); +INSERT INTO test VALUES (500000); +INSERT INTO test VALUES (500001); +DELETE FROM test WHERE pk=500001; +UPDATE test SET pk=60 WHERE pk=500000; +SELECT DOLT_COMMIT('-a', '-m', 'Insert 60'); +SELECT DOLT_MERGE('feature-branch'); +SQL + + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "Changes to be committed:" ]] || false + [[ "$output" =~ ([[:space:]]*modified:[[:space:]]*test) ]] || false + + run dolt sql -q "SELECT DOLT_COMMIT('-a', '-m', 'Finish up Merge')"; + [ $status -eq 0 ] + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false + + run dolt log -n 1 + [ $status -eq 0 ] + [[ "$output" =~ "Finish up Merge" ]] || false + + run dolt sql -q "SELECT * FROM test;" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "pk" ]] || false + [[ "$output" =~ "0" ]] || false + [[ "$output" =~ "1" ]] || false + [[ "$output" =~ "2" ]] || false + [[ "$output" =~ "3" ]] || false + [[ "$output" =~ "21" ]] || false + [[ "$output" =~ "60" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM test;" -r csv + [ $status -eq 0 ] + [[ "$output" =~ "6" ]] || false } get_head_commit() { dolt log -n 1 | grep -m 1 commit | cut -c 8- } + +get_working_hash() { + dolt sql -q "select @@dolt_repo_$$_working" | sed -n 4p | sed -e 's/|//' -e 's/|//' -e 's/ //' +} diff --git a/bats/sql-server.bats b/bats/sql-server.bats index 05fc73b65d..5de07d56f1 100644 --- a/bats/sql-server.bats +++ b/bats/sql-server.bats @@ -438,4 +438,85 @@ SQL server_query 1 "SELECT * FROM repo1.r1_one_pk" "pk,c1,c2\n1,1,1\n2,2,2\n3,3,3" server_query 1 "SELECT * FROM repo2.r2_one_pk" "pk,c3,c4\n1,1,1\n2,2,2\n3,3,3" +} + +@test "DOLT_ADD, DOLT_COMMIT, DOLT_CHECKOUT, DOLT_MERGE work together in server mode" { + skiponwindows "Has dependencies that are missing on the Jenkins Windows installation." + + cd repo1 + start_sql_server repo1 + + + multi_query 1 " + CREATE TABLE test ( + pk int primary key + ); + INSERT INTO test VALUES (0),(1),(2); + SELECT DOLT_ADD('.'); + SELECT DOLT_COMMIT('-a', '-m', 'Step 1'); + SELECT DOLT_CHECKOUT('-b', 'feature-branch'); + " + + server_query 1 "SELECT * FROM test" "pk\n0\n1\n2" + run dolt branch + [ "$status" -eq 0 ] + [[ "$output" =~ "* feature-branch" ]] || false + + multi_query 1 " + INSERT INTO test VALUES (3); + INSERT INTO test VALUES (4); + INSERT INTO test VALUES (21232); + DELETE FROM test WHERE pk=4; + UPDATE test SET pk=21 WHERE pk=21232; + " + server_query 1 "SELECT * FROM test" "pk\n0\n1\n2\n3\n21" + + multi_query 1 " + SELECT DOLT_COMMIT('-a', '-m', 'Insert 3'); + SELECT DOLT_CHECKOUT('master'); + " + server_query 1 "SELECT * FROM test" "pk\n0\n1\n2" + + multi_query 1 " + INSERT INTO test VALUES (500000); + INSERT INTO test VALUES (500001); + DELETE FROM test WHERE pk=500001; + UPDATE test SET pk=60 WHERE pk=500000; + SELECT DOLT_ADD('.'); + SELECT DOLT_COMMIT('-m', 'Insert 60'); + SELECT DOLT_MERGE('feature-branch'); + SELECT DOLT_COMMIT('-a', '-m', 'Finish up Merge'); + " + server_query 1 "SELECT * FROM test" "pk\n0\n1\n2\n3\n21\n60" + + run dolt status + [ $status -eq 0 ] + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false +} + +@test "DOLT_MERGE ff works" { + skiponwindows "Has dependencies that are missing on the Jenkins Windows installation." + + cd repo1 + start_sql_server repo1 + + + multi_query 1 " + CREATE TABLE test ( + pk int primary key + ); + INSERT INTO test VALUES (0),(1),(2); + SELECT DOLT_ADD('.'); + SELECT DOLT_COMMIT('-m', 'Step 1'); + SELECT DOLT_CHECKOUT('-b', 'feature-branch'); + INSERT INTO test VALUES (3); + UPDATE test SET pk=1000 WHERE pk=0; + SELECT DOLT_COMMIT('-a', '-m', 'this is a ff'); + SELECT DOLT_CHECKOUT('master'); + SELECT DOLT_MERGE('feature-branch'); + " + + server_query 1 "SELECT * FROM test" "pk\n1\n2\n3\n1000" + + server_query 1 "SELECT COUNT(*) FROM dolt_log" "COUNT(*)\n3" } \ No newline at end of file diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index bbc3384882..4b39745093 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -82,8 +82,15 @@ const ( SoftResetParam = "soft" CheckoutCoBranch = "b" NoFFParam = "no-ff" + SquashParam = "squash" + AbortParam = "abort" ) +var mergeAbortDetails = `Abort the current conflict resolution process, and try to reconstruct the pre-merge state. + +If there were uncommitted working set changes present when the merge started, {{.EmphasisLeft}}dolt merge --abort{{.EmphasisRight}} will be unable to reconstruct these changes. It is therefore recommended to always commit or stash your changes before running dolt merge. +` + // Creates the argparser shared dolt commit cli and DOLT_COMMIT. func CreateCommitArgParser() *argparser.ArgParser { ap := argparser.NewArgParser() @@ -99,6 +106,9 @@ func CreateCommitArgParser() *argparser.ArgParser { func CreateMergeArgParser() *argparser.ArgParser { ap := argparser.NewArgParser() ap.SupportsFlag(NoFFParam, "", "Create a merge commit even when the merge resolves as a fast-forward.") + ap.SupportsFlag(SquashParam, "", "Merges changes to the working set without updating the commit history") + ap.SupportsString(CommitMessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.") + ap.SupportsFlag(AbortParam, "", mergeAbortDetails) return ap } diff --git a/go/cmd/dolt/commands/merge.go b/go/cmd/dolt/commands/merge.go index 030be96822..495c164752 100644 --- a/go/cmd/dolt/commands/merge.go +++ b/go/cmd/dolt/commands/merge.go @@ -34,12 +34,6 @@ import ( "github.com/dolthub/dolt/go/store/hash" ) -const ( - abortParam = "abort" - squashParam = "squash" - noFFParam = "no-ff" -) - var mergeDocs = cli.CommandDocumentationContent{ ShortDesc: "Join two or more development histories together", LongDesc: `Incorporates changes from the named commits (since the time their histories diverged from the current branch) into the current branch. @@ -56,11 +50,6 @@ The second syntax ({{.LessThan}}dolt merge --abort{{.GreaterThan}}) can only be }, } -var abortDetails = `Abort the current conflict resolution process, and try to reconstruct the pre-merge state. - -If there were uncommitted working set changes present when the merge started, {{.EmphasisLeft}}dolt merge --abort{{.EmphasisRight}} will be unable to reconstruct these changes. It is therefore recommended to always commit or stash your changes before running dolt merge. -` - type MergeCmd struct{} // Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command @@ -75,19 +64,10 @@ func (cmd MergeCmd) Description() string { // CreateMarkdown creates a markdown file containing the helptext for the command at the given path func (cmd MergeCmd) CreateMarkdown(fs filesys.Filesys, path, commandStr string) error { - ap := cmd.createArgParser() + ap := cli.CreateMergeArgParser() return CreateMarkdown(fs, path, cli.GetCommandDocumentation(commandStr, mergeDocs, ap)) } -func (cmd MergeCmd) createArgParser() *argparser.ArgParser { - ap := argparser.NewArgParser() - ap.SupportsFlag(abortParam, "", abortDetails) - ap.SupportsFlag(squashParam, "", "Merges changes to the working set without updating the commit history") - ap.SupportsFlag(noFFParam, "", "Create a merge commit even when the merge resolves as a fast-forward.") - ap.SupportsString(cli.CommitMessageArg, "m", "msg", "Use the given {{.LessThan}}msg{{.GreaterThan}} as the commit message.") - return ap -} - // EventType returns the type of the event to log func (cmd MergeCmd) EventType() eventsapi.ClientEventType { return eventsapi.ClientEventType_MERGE @@ -95,17 +75,17 @@ func (cmd MergeCmd) EventType() eventsapi.ClientEventType { // Exec executes the command func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int { - ap := cmd.createArgParser() + ap := cli.CreateMergeArgParser() help, usage := cli.HelpAndUsagePrinters(cli.GetCommandDocumentation(commandStr, mergeDocs, ap)) apr := cli.ParseArgs(ap, args, help) - if apr.ContainsAll(squashParam, noFFParam) { - cli.PrintErrf("error: Flags '--%s' and '--%s' cannot be used together.\n", squashParam, noFFParam) + if apr.ContainsAll(cli.SquashParam, cli.NoFFParam) { + cli.PrintErrf("error: Flags '--%s' and '--%s' cannot be used together.\n", cli.SquashParam, cli.NoFFParam) return 1 } var verr errhand.VerboseError - if apr.Contains(abortParam) { + if apr.Contains(cli.AbortParam) { if !dEnv.IsMergeActive() { cli.PrintErrln("fatal: There is no merge to abort") return 1 @@ -149,7 +129,7 @@ func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string, } func abortMerge(ctx context.Context, doltEnv *env.DoltEnv) errhand.VerboseError { - err := actions.CheckoutAllTables(ctx, doltEnv) + err := actions.CheckoutAllTables(ctx, doltEnv.DbData()) if err == nil { err = doltEnv.RepoState.ClearMerge(doltEnv.FS) @@ -194,12 +174,12 @@ func mergeCommitSpec(ctx context.Context, apr *argparser.ArgParseResults, dEnv * cli.Println("Updating", h1.String()+".."+h2.String()) - squash := apr.Contains(squashParam) + squash := apr.Contains(cli.SquashParam) if squash { cli.Println("Squash commit -- not updating HEAD") } - tblNames, workingDiffs, err := dEnv.MergeWouldStompChanges(ctx, cm2) + tblNames, workingDiffs, err := env.MergeWouldStompChanges(ctx, cm2, dEnv.DbData()) if err != nil { return errhand.BuildDError("error: failed to determine mergability.").AddCause(err).Build() @@ -215,7 +195,7 @@ func mergeCommitSpec(ctx context.Context, apr *argparser.ArgParseResults, dEnv * } if ok, err := cm1.CanFastForwardTo(ctx, cm2); ok { - if apr.Contains(noFFParam) { + if apr.Contains(cli.NoFFParam) { return execNoFFMerge(ctx, apr, dEnv, cm2, verr, workingDiffs) } else { return executeFFMerge(ctx, squash, dEnv, cm2, workingDiffs) diff --git a/go/cmd/dolt/commands/pull.go b/go/cmd/dolt/commands/pull.go index 9e5e007f63..d086b1cc48 100644 --- a/go/cmd/dolt/commands/pull.go +++ b/go/cmd/dolt/commands/pull.go @@ -57,7 +57,7 @@ func (cmd PullCmd) CreateMarkdown(fs filesys.Filesys, path, commandStr string) e func (cmd PullCmd) createArgParser() *argparser.ArgParser { ap := argparser.NewArgParser() - ap.SupportsFlag(squashParam, "", "Merges changes to the working set without updating the commit history") + ap.SupportsFlag(cli.SquashParam, "", "Merges changes to the working set without updating the commit history") return ap } diff --git a/go/cmd/dolt/commands/sql.go b/go/cmd/dolt/commands/sql.go index d579592ba6..e8aa0c08dd 100644 --- a/go/cmd/dolt/commands/sql.go +++ b/go/cmd/dolt/commands/sql.go @@ -910,7 +910,7 @@ func (s *stats) shouldFlush() bool { return s.unflushedEdits >= maxBatchSize } -// updateRepoState takes in a context and database and updates repo state if autocommit is on +// updateRepoState takes in a context and database and updates repo state. func updateRepoState(ctx *sql.Context, se *sqlEngine) error { err := se.iterDBs(func(_ string, db dsqle.Database) (bool, error) { root, err := db.GetRoot(ctx) @@ -945,7 +945,6 @@ func updateRepoState(ctx *sql.Context, se *sqlEngine) error { return err } - func flushBatchedEdits(ctx *sql.Context, se *sqlEngine) error { err := se.iterDBs(func(_ string, db dsqle.Database) (bool, error) { err := db.Flush(ctx) diff --git a/go/libraries/doltcore/dtestutils/testcommands/command.go b/go/libraries/doltcore/dtestutils/testcommands/command.go index a5819cf22d..25c095a672 100644 --- a/go/libraries/doltcore/dtestutils/testcommands/command.go +++ b/go/libraries/doltcore/dtestutils/testcommands/command.go @@ -225,7 +225,7 @@ func (m Merge) Exec(t *testing.T, dEnv *env.DoltEnv) error { assert.NoError(t, err) assert.NotEqual(t, h1, h2) - tblNames, _, err := dEnv.MergeWouldStompChanges(context.Background(), cm2) + tblNames, _, err := env.MergeWouldStompChanges(context.Background(), cm2, dEnv.DbData()) if err != nil { return err } diff --git a/go/libraries/doltcore/env/actions/checkout.go b/go/libraries/doltcore/env/actions/checkout.go index 5ee07e8801..1314aad187 100644 --- a/go/libraries/doltcore/env/actions/checkout.go +++ b/go/libraries/doltcore/env/actions/checkout.go @@ -22,8 +22,8 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/env" ) -func CheckoutAllTables(ctx context.Context, dEnv *env.DoltEnv) error { - roots, err := getRoots(ctx, dEnv.DoltDB, dEnv.RepoStateReader(), WorkingRoot, StagedRoot, HeadRoot) +func CheckoutAllTables(ctx context.Context, dbData env.DbData) error { + roots, err := getRoots(ctx, dbData.Ddb, dbData.Rsr, WorkingRoot, StagedRoot, HeadRoot) if err != nil { return err @@ -37,7 +37,7 @@ func CheckoutAllTables(ctx context.Context, dEnv *env.DoltEnv) error { docs := doltdocs.SupportedDocs - return checkoutTablesAndDocs(ctx, dEnv.DbData(), roots, tbls, docs) + return checkoutTablesAndDocs(ctx, dbData, roots, tbls, docs) } diff --git a/go/libraries/doltcore/env/environment.go b/go/libraries/doltcore/env/environment.go index f8cf814298..dc304a092f 100644 --- a/go/libraries/doltcore/env/environment.go +++ b/go/libraries/doltcore/env/environment.go @@ -430,6 +430,9 @@ func (r *repoStateWriter) SetCWBHeadRef(ctx context.Context, marshalableRef ref. func (r *repoStateWriter) ClearMerge() error { return r.dEnv.RepoState.ClearMerge(r.dEnv.FS) } +func (r *repoStateWriter) StartMerge(commitStr string) error { + return r.dEnv.RepoState.StartMerge(commitStr, r.dEnv.FS) +} func (dEnv *DoltEnv) RepoStateWriter() RepoStateWriter { return &repoStateWriter{dEnv} @@ -563,104 +566,6 @@ func (dEnv *DoltEnv) GetTablesWithConflicts(ctx context.Context) ([]string, erro return root.TablesInConflict(ctx) } -func (dEnv *DoltEnv) MergeWouldStompChanges(ctx context.Context, mergeCommit *doltdb.Commit) ([]string, map[string]hash.Hash, error) { - headRoot, err := dEnv.HeadRoot(ctx) - - if err != nil { - return nil, nil, err - } - - workingRoot, err := dEnv.WorkingRoot(ctx) - - if err != nil { - return nil, nil, err - } - - mergeRoot, err := mergeCommit.GetRootValue() - - if err != nil { - return nil, nil, err - } - - headTableHashes, err := mapTableHashes(ctx, headRoot) - - if err != nil { - return nil, nil, err - } - - workingTableHashes, err := mapTableHashes(ctx, workingRoot) - - if err != nil { - return nil, nil, err - } - - mergeTableHashes, err := mapTableHashes(ctx, mergeRoot) - - if err != nil { - return nil, nil, err - } - - headWorkingDiffs := diffTableHashes(headTableHashes, workingTableHashes) - mergeWorkingDiffs := diffTableHashes(headTableHashes, mergeTableHashes) - - stompedTables := make([]string, 0, len(headWorkingDiffs)) - for tName, _ := range headWorkingDiffs { - if _, ok := mergeWorkingDiffs[tName]; ok { - // even if the working changes match the merge changes, don't allow (matches git behavior). - stompedTables = append(stompedTables, tName) - } - } - - return stompedTables, headWorkingDiffs, nil -} - -func mapTableHashes(ctx context.Context, root *doltdb.RootValue) (map[string]hash.Hash, error) { - names, err := root.GetTableNames(ctx) - - if err != nil { - return nil, err - } - - nameToHash := make(map[string]hash.Hash) - for _, name := range names { - h, ok, err := root.GetTableHash(ctx, name) - - if err != nil { - return nil, err - } else if !ok { - panic("GetTableNames returned a table that GetTableHash says isn't there.") - } else { - nameToHash[name] = h - } - } - - return nameToHash, nil -} - -func diffTableHashes(headTableHashes, otherTableHashes map[string]hash.Hash) map[string]hash.Hash { - diffs := make(map[string]hash.Hash) - for tName, hh := range headTableHashes { - if h, ok := otherTableHashes[tName]; ok { - if h != hh { - // modification - diffs[tName] = h - } - } else { - // deletion - diffs[tName] = hash.Hash{} - } - } - - for tName, h := range otherTableHashes { - if _, ok := headTableHashes[tName]; !ok { - // addition - diffs[tName] = h - } - } - - return diffs -} - func (dEnv *DoltEnv) CredsDir() (string, error) { return getCredsDir(dEnv.hdp) } diff --git a/go/libraries/doltcore/env/repo_state.go b/go/libraries/doltcore/env/repo_state.go index ee0338187f..daa29955b5 100644 --- a/go/libraries/doltcore/env/repo_state.go +++ b/go/libraries/doltcore/env/repo_state.go @@ -42,6 +42,7 @@ type RepoStateWriter interface { SetWorkingHash(context.Context, hash.Hash) error SetCWBHeadRef(context.Context, ref.MarshalableRef) error ClearMerge() error + StartMerge(commitStr string) error } type DocsReadWriter interface { @@ -287,3 +288,101 @@ func GetRoots(ctx context.Context, ddb *doltdb.DoltDB, rsr RepoStateReader) (wor return working, staged, head, nil } + +func MergeWouldStompChanges(ctx context.Context, mergeCommit *doltdb.Commit, dbData DbData) ([]string, map[string]hash.Hash, error) { + headRoot, err := HeadRoot(ctx, dbData.Ddb, dbData.Rsr) + + if err != nil { + return nil, nil, err + } + + workingRoot, err := WorkingRoot(ctx, dbData.Ddb, dbData.Rsr) + + if err != nil { + return nil, nil, err + } + + mergeRoot, err := mergeCommit.GetRootValue() + + if err != nil { + return nil, nil, err + } + + headTableHashes, err := mapTableHashes(ctx, headRoot) + + if err != nil { + return nil, nil, err + } + + workingTableHashes, err := mapTableHashes(ctx, workingRoot) + + if err != nil { + return nil, nil, err + } + + mergeTableHashes, err := mapTableHashes(ctx, mergeRoot) + + if err != nil { + return nil, nil, err + } + + headWorkingDiffs := diffTableHashes(headTableHashes, workingTableHashes) + mergeWorkingDiffs := diffTableHashes(headTableHashes, mergeTableHashes) + + stompedTables := make([]string, 0, len(headWorkingDiffs)) + for tName, _ := range headWorkingDiffs { + if _, ok := mergeWorkingDiffs[tName]; ok { + // even if the working changes match the merge changes, don't allow (matches git behavior). + stompedTables = append(stompedTables, tName) + } + } + + return stompedTables, headWorkingDiffs, nil +} + +func mapTableHashes(ctx context.Context, root *doltdb.RootValue) (map[string]hash.Hash, error) { + names, err := root.GetTableNames(ctx) + + if err != nil { + return nil, err + } + + nameToHash := make(map[string]hash.Hash) + for _, name := range names { + h, ok, err := root.GetTableHash(ctx, name) + + if err != nil { + return nil, err + } else if !ok { + panic("GetTableNames returned a table that GetTableHash says isn't there.") + } else { + nameToHash[name] = h + } + } + + return nameToHash, nil +} + +func diffTableHashes(headTableHashes, otherTableHashes map[string]hash.Hash) map[string]hash.Hash { + diffs := make(map[string]hash.Hash) + for tName, hh := range headTableHashes { + if h, ok := otherTableHashes[tName]; ok { + if h != hh { + // modification + diffs[tName] = h + } + } else { + // deletion + diffs[tName] = hash.Hash{} + } + } + + for tName, h := range otherTableHashes { + if _, ok := headTableHashes[tName]; !ok { + // addition + diffs[tName] = h + } + } + + return diffs +} diff --git a/go/libraries/doltcore/sqle/dfunctions/dolt_checkout.go b/go/libraries/doltcore/sqle/dfunctions/dolt_checkout.go index fd74b381a3..af0eaa97e3 100644 --- a/go/libraries/doltcore/sqle/dfunctions/dolt_checkout.go +++ b/go/libraries/doltcore/sqle/dfunctions/dolt_checkout.go @@ -199,14 +199,27 @@ func updateHeadAndWorkingSessionVars(ctx *sql.Context, dbData env.DbData) error if err != nil { return err } + hs := headHash.String() + + hasWorkingChanges := hasWorkingSetChanges(dbData.Rsr) + hasStagedChanges, err := hasStagedSetChanges(ctx, dbData.Ddb, dbData.Rsr) - err = setSessionRootExplicit(ctx, headHash.String(), sqle.HeadKeySuffix) if err != nil { return err } workingHash := dbData.Rsr.WorkingHash().String() + // This will update the session table editor's root and clear its cache. + if !hasStagedChanges && !hasWorkingChanges { + return setHeadAndWorkingSessionRoot(ctx, hs) + } + + err = setSessionRootExplicit(ctx, hs, sqle.HeadKeySuffix) + if err != nil { + return err + } + return setSessionRootExplicit(ctx, workingHash, sqle.WorkingKeySuffix) } diff --git a/go/libraries/doltcore/sqle/dfunctions/dolt_commit.go b/go/libraries/doltcore/sqle/dfunctions/dolt_commit.go index 5d11238146..413398bafc 100644 --- a/go/libraries/doltcore/sqle/dfunctions/dolt_commit.go +++ b/go/libraries/doltcore/sqle/dfunctions/dolt_commit.go @@ -68,11 +68,6 @@ func (d DoltCommitFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) allFlag := apr.Contains(cli.AllFlag) allowEmpty := apr.Contains(cli.AllowEmptyFlag) - // Check if there are no changes in the working set but the -a flag is true - if allFlag && !hasWorkingSetChanges(rsr) && !allowEmpty { - return nil, fmt.Errorf("Cannot commit an empty commit. See the --allow-empty if you want to.") - } - // Check if there are no changes in the staged set but the -a flag is false hasStagedChanges, err := hasStagedSetChanges(ctx, ddb, rsr) if err != nil { @@ -83,6 +78,12 @@ func (d DoltCommitFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return nil, fmt.Errorf("Cannot commit an empty commit. See the --allow-empty if you want to.") } + // Check if there are no changes in the working set but the -a flag is true. + // The -a flag is fine when a merge is active or there are staged changes as result of a merge or an add. + if allFlag && !hasWorkingSetChanges(rsr) && !allowEmpty && !rsr.IsMergeActive() && !hasStagedChanges { + return nil, fmt.Errorf("Cannot commit an empty commit. See the --allow-empty if you want to.") + } + if allFlag { err = actions.StageAllTables(ctx, dbData) } diff --git a/go/libraries/doltcore/sqle/dfunctions/dolt_merge.go b/go/libraries/doltcore/sqle/dfunctions/dolt_merge.go index ddaf99c6be..0c1df8b4d8 100644 --- a/go/libraries/doltcore/sqle/dfunctions/dolt_merge.go +++ b/go/libraries/doltcore/sqle/dfunctions/dolt_merge.go @@ -25,7 +25,10 @@ import ( "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/doltcore/env/actions" + "github.com/dolthub/dolt/go/libraries/doltcore/merge" "github.com/dolthub/dolt/go/libraries/doltcore/sqle" + "github.com/dolthub/dolt/go/libraries/utils/argparser" ) const DoltMergeFuncName = "dolt_merge" @@ -57,6 +60,24 @@ func (d DoltMergeFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) apr := cli.ParseArgs(ap, args, nil) + if apr.ContainsAll(cli.SquashParam, cli.NoFFParam) { + return 1, fmt.Errorf("error: Flags '--%s' and '--%s' cannot be used together.\n", cli.SquashParam, cli.NoFFParam) + } + + if apr.Contains(cli.AbortParam) { + if !dbData.Rsr.IsMergeActive() { + return 1, fmt.Errorf("fatal: There is no merge to abort") + } + + err = abortMerge(ctx, dbData) + + if err != nil { + return 1, err + } + + return "Merge aborted", nil + } + // The first argument should be the branch name. branchName := apr.Arg(0) @@ -70,7 +91,20 @@ func (d DoltMergeFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) return nil, sql.ErrDatabaseNotFound.New(dbName) } - parent, _, parentRoot, err := getParent(ctx, err, sess, dbName) + hasConflicts, err := root.HasConflicts(ctx) + if err != nil { + return 1, err + } + + if hasConflicts { + return 1, errors.New("error: merge has unresolved conflicts") + } + + if dbData.Rsr.IsMergeActive() { + return 1, errors.New("error: merging is not possible because you have not committed an active merge") + } + + parent, ph, parentRoot, err := getParent(ctx, err, sess, dbName) if err != nil { return nil, err } @@ -92,36 +126,63 @@ func (d DoltMergeFunc) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) } if canFF { - err = executeFFMerge(ctx, false, dbData, cm) + if apr.Contains(cli.NoFFParam) { + err = executeNoFFMerge(ctx, sess, apr, dbData, parent, cm) + } else { + err = executeFFMerge(ctx, apr.Contains(cli.SquashParam), dbData, cm) + } + if err != nil { return nil, err } return cmh.String(), err - } else { - return nil, errors.New("DOLT_MERGE only supports fast forwards") - } -} - -func (d DoltMergeFunc) String() string { - childrenStrings := make([]string, len(d.Children())) - - for i, child := range d.Children() { - childrenStrings[i] = child.String() } - return fmt.Sprintf("DOLT_MERGE(%s)", strings.Join(childrenStrings, ",")) + err = executeMerge(ctx, apr.Contains(cli.SquashParam), parent, cm, dbData) + if err != nil { + return nil, err + } + + returnMsg := fmt.Sprintf("Updating %s..%s", cmh.String(), ph.String()) + + return returnMsg, nil } -func (d DoltMergeFunc) Type() sql.Type { - return sql.Text +func abortMerge(ctx *sql.Context, dbData env.DbData) error { + err := actions.CheckoutAllTables(ctx, dbData) + + if err != nil { + return err + } + + err = dbData.Rsw.ClearMerge() + if err != nil { + return err + } + + hh, err := dbData.Rsr.CWBHeadHash(ctx) + if err != nil { + return err + } + + return setHeadAndWorkingSessionRoot(ctx, hh.String()) } -func (d DoltMergeFunc) WithChildren(children ...sql.Expression) (sql.Expression, error) { - return NewDoltMergeFunc(children...) -} +func executeMerge(ctx *sql.Context, squash bool, parent, cm *doltdb.Commit, dbData env.DbData) error { + mergeRoot, mergeStats, err := merge.MergeCommits(ctx, parent, cm) -func NewDoltMergeFunc(args ...sql.Expression) (sql.Expression, error) { - return &DoltMergeFunc{expression.NaryExpression{ChildExpressions: args}}, nil + if err != nil { + switch err { + case doltdb.ErrUpToDate: + return errors.New("Already up to date.") + case merge.ErrFastForward: + panic("fast forward merge") + default: + return errors.New("Bad merge") + } + } + + return mergeRootToWorking(ctx, squash, dbData, mergeRoot, cm, mergeStats) } func executeFFMerge(ctx *sql.Context, squash bool, dbData env.DbData, cm2 *doltdb.Commit) error { @@ -158,3 +219,132 @@ func executeFFMerge(ctx *sql.Context, squash bool, dbData env.DbData, cm2 *doltd return nil } + +func executeNoFFMerge(ctx *sql.Context, dSess *sqle.DoltSession, apr *argparser.ArgParseResults, dbData env.DbData, pr, cm2 *doltdb.Commit) error { + mergedRoot, err := cm2.GetRootValue() + if err != nil { + return errors.New("Failed to return root value.") + } + + err = mergeRootToWorking(ctx, false, dbData, mergedRoot, cm2, map[string]*merge.MergeStats{}) + if err != nil { + return err + } + + msg, msgOk := apr.GetValue(cli.CommitMessageArg) + if !msgOk { + ph, err := pr.HashOf() + if err != nil { + return err + } + + cmh, err := cm2.HashOf() + if err != nil { + return err + } + + msg = fmt.Sprintf("SQL Generated commit merging %s into %s", ph.String(), cmh.String()) + } + + var name, email string + if authorStr, ok := apr.GetValue(cli.AuthorParam); ok { + name, email, err = cli.ParseAuthor(authorStr) + if err != nil { + return err + } + } else { + name = dSess.Username + email = dSess.Email + } + + // Specify the time if the date parameter is not. + t := ctx.QueryTime() + if commitTimeStr, ok := apr.GetValue(cli.DateParam); ok { + var err error + t, err = cli.ParseDate(commitTimeStr) + if err != nil { + return err + } + } + + h, err := actions.CommitStaged(ctx, dbData, actions.CommitStagedProps{ + Message: msg, + Date: t, + AllowEmpty: apr.Contains(cli.AllowEmptyFlag), + CheckForeignKeys: !apr.Contains(cli.ForceFlag), + Name: name, + Email: email, + }) + + if err != nil { + return err + } + + return setHeadAndWorkingSessionRoot(ctx, h) +} + +func mergeRootToWorking(ctx *sql.Context, squash bool, dbData env.DbData, mergedRoot *doltdb.RootValue, cm2 *doltdb.Commit, mergeStats map[string]*merge.MergeStats) error { + h2, err := cm2.HashOf() + if err != nil { + return err + } + + workingRoot := mergedRoot + if !squash { + err = dbData.Rsw.StartMerge(h2.String()) + + if err != nil { + return err + } + } + + workingHash, err := env.UpdateWorkingRoot(ctx, dbData.Ddb, dbData.Rsw, workingRoot) + if err != nil { + return err + } + + hasConflicts := checkForConflicts(mergeStats) + + if hasConflicts { + return errors.New("merge has conflicts. use the dolt_conflicts table to resolve.") + } + + _, err = env.UpdateStagedRoot(ctx, dbData.Ddb, dbData.Rsw, workingRoot) + if err != nil { + return err + } + + return setSessionRootExplicit(ctx, workingHash.String(), sqle.WorkingKeySuffix) +} + +func checkForConflicts(tblToStats map[string]*merge.MergeStats) bool { + for _, stats := range tblToStats { + if stats.Operation == merge.TableModified && stats.Conflicts > 0 { + return true + } + } + + return false +} + +func (d DoltMergeFunc) String() string { + childrenStrings := make([]string, len(d.Children())) + + for i, child := range d.Children() { + childrenStrings[i] = child.String() + } + + return fmt.Sprintf("DOLT_MERGE(%s)", strings.Join(childrenStrings, ",")) +} + +func (d DoltMergeFunc) Type() sql.Type { + return sql.Text +} + +func (d DoltMergeFunc) WithChildren(children ...sql.Expression) (sql.Expression, error) { + return NewDoltMergeFunc(children...) +} + +func NewDoltMergeFunc(args ...sql.Expression) (sql.Expression, error) { + return &DoltMergeFunc{expression.NaryExpression{ChildExpressions: args}}, nil +}