Merge pull request #1680 from dolthub/andy/merge-base

Andy/merge base
This commit is contained in:
AndyA
2021-05-05 16:24:52 -07:00
committed by GitHub
10 changed files with 349 additions and 3 deletions

View File

@@ -58,7 +58,7 @@ var fkWarningMessage = "Warning: This merge is being applied to tables that have
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
// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
func (cmd MergeCmd) Name() string {
return "merge"
}

View File

@@ -0,0 +1,96 @@
// 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 commands
import (
"context"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/utils/argparser"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
)
var mergeBaseDocs = cli.CommandDocumentationContent{
ShortDesc: `Find the common ancestor of two commits.`,
LongDesc: `Find the common ancestor of two commits, and return the ancestor's commit hash.'`,
Synopsis: []string{
`{{.LessThan}}commit spec{{.GreaterThan}} {{.LessThan}}commit spec{{.GreaterThan}}`,
},
}
type MergeBaseCmd struct{}
// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
func (cmd MergeBaseCmd) Name() string {
return "merge-base"
}
// Description returns a description of the command
func (cmd MergeBaseCmd) Description() string {
return mergeBaseDocs.ShortDesc
}
// CreateMarkdown creates a markdown file containing the helptext for the command at the given path
func (cmd MergeBaseCmd) CreateMarkdown(fs filesys.Filesys, path, commandStr string) error {
ap := cmd.createArgParser()
return CreateMarkdown(fs, path, cli.GetCommandDocumentation(commandStr, mergeBaseDocs, ap))
}
func (cmd MergeBaseCmd) createArgParser() *argparser.ArgParser {
ap := argparser.NewArgParser()
//ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"start-point", "A commit that a new branch should point at."})
return ap
}
// EventType returns the type of the event to log
func (cmd MergeBaseCmd) EventType() eventsapi.ClientEventType {
return eventsapi.ClientEventType_TYPE_UNSPECIFIED
}
// Exec executes the command
func (cmd MergeBaseCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv) int {
ap := cmd.createArgParser()
help, usage := cli.HelpAndUsagePrinters(cli.GetCommandDocumentation(commandStr, mergeBaseDocs, ap))
apr := cli.ParseArgsOrDie(ap, args, help)
var verr errhand.VerboseError
if apr.NArg() != 2 {
verr = errhand.BuildDError("%s takes exactly 2 args", cmd.Name()).Build()
return HandleVErrAndExitCode(verr, usage)
}
left, verr := ResolveCommitWithVErr(dEnv, apr.Arg(0))
if verr != nil {
return HandleVErrAndExitCode(verr, usage)
}
right, verr := ResolveCommitWithVErr(dEnv, apr.Arg(1))
if verr != nil {
return HandleVErrAndExitCode(verr, usage)
}
mergeBase, err := merge.MergeBase(ctx, left, right)
if err != nil {
verr = errhand.BuildDError("could not find merge-base for args %s", apr.Args()).AddCause(err).Build()
return HandleVErrAndExitCode(verr, usage)
}
cli.Println(mergeBase.String())
return 0
}

View File

@@ -84,7 +84,7 @@ func ResolveCommitWithVErr(dEnv *env.DoltEnv, cSpecStr string) (*doltdb.Commit,
if err == doltdb.ErrInvalidAncestorSpec {
return nil, errhand.BuildDError("'%s' could not resolve ancestor spec", cSpecStr).Build()
} else if err == doltdb.ErrBranchNotFound {
return nil, errhand.BuildDError("unknown branch in commit spec: '%s'", cSpecStr).Build()
return nil, errhand.BuildDError("unknown ref in commit spec: '%s'", cSpecStr).Build()
} else if doltdb.IsNotFoundErr(err) {
return nil, errhand.BuildDError("'%s' not found", cSpecStr).Build()
} else if err == doltdb.ErrFoundHashNotACommit {

View File

@@ -90,6 +90,7 @@ var doltCommand = cli.NewSubCommandHandler("dolt", "it's git for data", []cli.Co
commands.GarbageCollectionCmd{},
commands.FilterBranchCmd{},
commands.VerifyConstraintsCmd{},
commands.MergeBaseCmd{},
})
func init() {

View File

@@ -0,0 +1,32 @@
// 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 merge
import (
"context"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/store/hash"
)
func MergeBase(ctx context.Context, left, right *doltdb.Commit) (base hash.Hash, err error) {
ancestor, err := doltdb.GetCommitAncestor(ctx, left, right)
if err != nil {
return base, err
}
return ancestor.HashOf()
}

View File

@@ -0,0 +1,124 @@
// 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 dfunctions
import (
"fmt"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/expression"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle"
)
const DoltMergeBaseFuncName = "dolt_merge_base"
type MergeBase struct {
expression.BinaryExpression
}
// NewMergeBase returns a MergeBase sql function.
func NewMergeBase(left, right sql.Expression) sql.Expression {
return &MergeBase{expression.BinaryExpression{Left: left, Right: right}}
}
// Eval implements the sql.Expression interface.
func (d MergeBase) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
if _, ok := d.Left.Type().(sql.StringType); !ok {
return nil, sql.ErrInvalidType.New(d.Left.Type())
}
if _, ok := d.Right.Type().(sql.StringType); !ok {
return nil, sql.ErrInvalidType.New(d.Right.Type())
}
leftSpec, err := d.Left.Eval(ctx, row)
if err != nil {
return nil, err
}
rightSpec, err := d.Right.Eval(ctx, row)
if err != nil {
return nil, err
}
if leftSpec == nil || rightSpec == nil {
return nil, nil
}
left, right, err := resolveRefSpecs(ctx, leftSpec.(string), rightSpec.(string))
if err != nil {
return nil, err
}
mergeBase, err := merge.MergeBase(ctx, left, right)
if err != nil {
return nil, err
}
return mergeBase.String(), nil
}
func resolveRefSpecs(ctx *sql.Context, leftSpec, rightSpec string) (left, right *doltdb.Commit, err error) {
lcs, err := doltdb.NewCommitSpec(leftSpec)
if err != nil {
return nil, nil, err
}
rcs, err := doltdb.NewCommitSpec(rightSpec)
if err != nil {
return nil, nil, err
}
sess := sqle.DSessFromSess(ctx.Session)
dbName := ctx.GetCurrentDatabase()
dbData, ok := sess.GetDbData(dbName)
if !ok {
return nil, nil, sql.ErrDatabaseNotFound.New(dbName)
}
doltDB, ok := sess.GetDoltDB(dbName)
if !ok {
return nil, nil, sql.ErrDatabaseNotFound.New(dbName)
}
left, err = doltDB.Resolve(ctx, lcs, dbData.Rsr.CWBHeadRef())
if err != nil {
return nil, nil, err
}
right, err = doltDB.Resolve(ctx, rcs, dbData.Rsr.CWBHeadRef())
if err != nil {
return nil, nil, err
}
return
}
// String implements the sql.Expression interface.
func (d MergeBase) String() string {
return fmt.Sprintf("DOLT_MERGE_BASE(%s,%s)", d.Left.String(), d.Right.String())
}
// Type implements the sql.Expression interface.
func (d MergeBase) Type() sql.Type {
return sql.Text
}
// WithChildren implements the sql.Expression interface.
func (d MergeBase) WithChildren(children ...sql.Expression) (sql.Expression, error) {
if len(children) != 2 {
return nil, sql.ErrInvalidChildrenNumber.New(d, len(children), 2)
}
return NewMergeBase(children[0], children[1]), nil
}

View File

@@ -29,6 +29,7 @@ var DoltFunctions = []sql.Function{
sql.FunctionN{Name: DoltCheckoutFuncName, Fn: NewDoltCheckoutFunc},
sql.FunctionN{Name: DoltMergeFuncName, Fn: NewDoltMergeFunc},
sql.Function0{Name: ActiveBranchFuncName, Fn: NewActiveBranchFunc},
sql.Function2{Name: DoltMergeBaseFuncName, Fn: NewMergeBase},
}
// These are the DoltFunctions that get exposed to Dolthub Api.

View File

@@ -12,7 +12,7 @@ teardown() {
@test "conflict-detection: merge non-existant branch errors" {
run dolt merge batmans-parents
[ $status -eq 1 ]
[[ "$output" =~ "unknown branch" ]] || false
[[ "$output" =~ "unknown ref" ]] || false
[[ ! "$output" =~ "panic" ]] || false
}

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env bats
load $BATS_TEST_DIRNAME/helper/common.bash
setup() {
setup_common
dolt sql -q "CREATE TABLE test (pk int primary key);"
dolt add -A && dolt commit -m "commit A"
dolt branch zero
dolt sql -q "INSERT INTO test VALUES (0);"
dolt commit -am "commit B"
dolt branch one
dolt branch two
dolt sql -q "INSERT INTO test VALUES (1);"
dolt commit -am "commit C"
dolt checkout two
dolt sql -q "INSERT INTO test VALUES (2);"
dolt commit -am "commit D"
dolt checkout master
# # # # # # # # # # # # # # # # # # # # # # #
# #
# <-- (zero) #
# / #
# / <-- (one) #
# / / #
# (init) -- (A) -- (B) -- (C) <-- (master) #
# \ #
# -- (D) <-- (two) #
# #
# # # # # # # # # # # # # # # # # # # # # # #
}
teardown() {
teardown_common
}
@test "merge-base: cli" {
run dolt merge-base master two
[ "$status" -eq 0 ]
MERGE_BASE="$output"
run dolt merge-base master one
[ "$status" -eq 0 ]
[ "$output" = "$MERGE_BASE" ]
run dolt merge-base one two
[ "$status" -eq 0 ]
[ "$output" = "$MERGE_BASE" ]
dolt checkout master
run dolt log
[ "$status" -eq 0 ]
[[ "$output" =~ "$MERGE_BASE" ]] || false
dolt checkout two
run dolt log
[ "$status" -eq 0 ]
[[ "$output" =~ "$MERGE_BASE" ]] || false
dolt checkout zero
run dolt log
[ "$status" -eq 0 ]
[[ ! "$output" =~ "$MERGE_BASE" ]] || false
}
@test "merge-base: sql" {
run dolt sql -q "SELECT message FROM dolt_log WHERE commit_hash = dolt_merge_base('master', 'zero');" -r csv
[ "$status" -eq 0 ]
[ "${lines[1]}" = "commit A" ]
run dolt sql -q "SELECT message FROM dolt_log WHERE commit_hash = dolt_merge_base('master', 'one');" -r csv
[ "$status" -eq 0 ]
[ "${lines[1]}" = "commit B" ]
run dolt sql -q "SELECT message FROM dolt_log WHERE commit_hash = dolt_merge_base('master', 'two');" -r csv
[ "$status" -eq 0 ]
[ "${lines[1]}" = "commit B" ]
run dolt sql -q "SELECT message FROM dolt_log WHERE commit_hash = dolt_merge_base('master', 'master');" -r csv
[ "$status" -eq 0 ]
[ "${lines[1]}" = "commit C" ]
# dolt_merge_base() resolves commit hashes
run dolt sql -q "SELECT dolt_merge_base('master', hashof('one')) = dolt_merge_base(hashof('master'),'one') FROM dual;" -r csv
[ "$status" -eq 0 ]
[ "${lines[1]}" = "true" ]
}

View File

@@ -53,6 +53,7 @@ teardown() {
[[ "$output" =~ "migrate - Executes a repository migration to update to the latest format." ]] || false
[[ "$output" =~ "gc - Cleans up unreferenced data from the repository." ]] || false
[[ "$output" =~ "filter-branch - Edits the commit history using the provided query." ]] || false
[[ "$output" =~ "merge-base - Find the common ancestor of two commits." ]] || false
}
@test "no-repo: check all commands for valid help text" {