Vinai/dolt merge p2 (#1322)

Adds addition functionality to DOLT_MERGE beyond just ffs
This commit is contained in:
Vinai Rachakonda
2021-02-16 20:40:23 -08:00
committed by GitHub
parent 1ce12d9060
commit 70a0528b6d
15 changed files with 859 additions and 165 deletions

View File

@@ -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 <bats@email.fake>" ]] || 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')"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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