mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-29 03:06:35 -05:00
Merge pull request #5767 from dolthub/andy/schema-conflicts
go/doltcore/{doltdb, sqle}: dolt_schema_conflicts system table
This commit is contained in:
@@ -217,14 +217,14 @@ func getCherryPickedRootValue(ctx context.Context, dEnv *env.DoltEnv, workingRoo
|
||||
|
||||
// use parent of cherry-pick as ancestor to merge
|
||||
mo := merge.MergeOpts{IsCherryPick: true}
|
||||
mergedRoot, mergeStats, err := merge.MergeRoots(ctx, workingRoot, cherryRoot, parentRoot, cherryCm, parentCm, opts, mo)
|
||||
result, err := merge.MergeRoots(ctx, workingRoot, cherryRoot, parentRoot, cherryCm, parentCm, opts, mo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var tablesWithConflict []string
|
||||
for tbl, stats := range mergeStats {
|
||||
if stats.Conflicts > 0 {
|
||||
for tbl, stats := range result.Stats {
|
||||
if stats.HasConflicts() {
|
||||
tablesWithConflict = append(tablesWithConflict, tbl)
|
||||
}
|
||||
}
|
||||
@@ -234,5 +234,5 @@ func getCherryPickedRootValue(ctx context.Context, dEnv *env.DoltEnv, workingRoo
|
||||
return nil, "", errors.New(fmt.Sprintf("conflicts in table {'%s'}", tblNames))
|
||||
}
|
||||
|
||||
return mergedRoot, commitMsg, nil
|
||||
return result.Root, commitMsg, nil
|
||||
}
|
||||
|
||||
@@ -51,8 +51,7 @@ func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolve
|
||||
return err
|
||||
}
|
||||
|
||||
tbls, err := root.TablesInConflict(ctx)
|
||||
|
||||
tbls, err := root.GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,26 +62,77 @@ func AutoResolveAll(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolve
|
||||
// AutoResolveTables resolves all conflicts in the given tables according to the
|
||||
// given |strategy|.
|
||||
func AutoResolveTables(ctx context.Context, dEnv *env.DoltEnv, strategy AutoResolveStrategy, tbls []string) error {
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tblName := range tbls {
|
||||
err = ResolveTable(ctx, dEnv, root, tblName, strategy)
|
||||
// schema conflicts
|
||||
if ws.MergeActive() {
|
||||
ws, err = ResolveSchemaConflicts(ctx, dEnv.DoltDB, ws, tbls, strategy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = dEnv.UpdateWorkingSet(ctx, ws); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// data conflicts
|
||||
for _, tblName := range tbls {
|
||||
err = ResolveDataConflicts(ctx, dEnv, ws, tblName, strategy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveTable resolves all conflicts in the given table according to the given
|
||||
func ResolveSchemaConflicts(ctx context.Context, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, tables []string, strategy AutoResolveStrategy) (*doltdb.WorkingSet, error) {
|
||||
tblSet := set.NewStrSet(tables)
|
||||
updates := make(map[string]*doltdb.Table)
|
||||
err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table string, conflict doltdb.SchemaConflict) error {
|
||||
if !tblSet.Contains(table) {
|
||||
return nil
|
||||
}
|
||||
ours, theirs := conflict.GetConflictingTables()
|
||||
switch strategy {
|
||||
case AutoResolveStrategyOurs:
|
||||
updates[table] = ours
|
||||
case AutoResolveStrategyTheirs:
|
||||
updates[table] = theirs
|
||||
default:
|
||||
panic("unhandled auto resolve strategy")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := ws.WorkingRoot()
|
||||
for name, tbl := range updates {
|
||||
if root, err = root.PutTable(ctx, name, tbl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// clear resolved schema conflicts
|
||||
var unmerged []string
|
||||
for _, tbl := range ws.MergeState().TablesWithSchemaConflicts() {
|
||||
if tblSet.Contains(tbl) {
|
||||
continue
|
||||
}
|
||||
unmerged = append(unmerged, tbl)
|
||||
}
|
||||
return ws.WithWorkingRoot(root).WithUnmergableTables(unmerged), nil
|
||||
}
|
||||
|
||||
// ResolveDataConflicts resolves all conflicts in the given table according to the given
|
||||
// |strategy|. It errors if the schema of the conflict version you are choosing
|
||||
// differs from the current schema.
|
||||
func ResolveTable(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue, tblName string, strategy AutoResolveStrategy) (err error) {
|
||||
tbl, ok, err := root.GetTable(ctx, tblName)
|
||||
func ResolveDataConflicts(ctx context.Context, dEnv *env.DoltEnv, ws *doltdb.WorkingSet, tblName string, strategy AutoResolveStrategy) (err error) {
|
||||
tbl, ok, err := ws.WorkingRoot().GetTable(ctx, tblName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -92,8 +142,7 @@ func ResolveTable(ctx context.Context, dEnv *env.DoltEnv, root *doltdb.RootValue
|
||||
has, err := tbl.HasConflicts(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
} else if !has {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ func performCommit(ctx context.Context, commandStr string, args []string, dEnv *
|
||||
mergeParentCommits = parentsHeadForAmend
|
||||
}
|
||||
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws.MergeActive(), mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
|
||||
Message: msg,
|
||||
Date: t,
|
||||
AllowEmpty: apr.Contains(cli.AllowEmptyFlag) || apr.Contains(cli.AmendFlag),
|
||||
@@ -276,7 +276,7 @@ func handleCommitErr(ctx context.Context, dEnv *env.DoltEnv, err error, usage cl
|
||||
|
||||
if actions.IsNothingStaged(err) {
|
||||
notStagedTbls := actions.NothingStagedTblDiffs(err)
|
||||
n := PrintDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, false, 0, nil, nil)
|
||||
n := PrintDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, false, 0, merge.ArtifactStatus{})
|
||||
|
||||
if n == 0 {
|
||||
bdr := errhand.BuildDError(`no changes added to commit (use "dolt add")`)
|
||||
@@ -358,18 +358,19 @@ func buildInitalCommitMsg(ctx context.Context, dEnv *env.DoltEnv, suggestedMsg s
|
||||
|
||||
stagedTblDiffs, notStagedTblDiffs, _ := diff.GetStagedUnstagedTableDeltas(ctx, roots)
|
||||
|
||||
workingTblsInConflict, _, _, err := merge.GetTablesInConflict(ctx, roots)
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
workingTblsInConflict = []string{}
|
||||
return "", err
|
||||
}
|
||||
workingTblsWithViolations, _, _, err := merge.GetTablesWithConstraintViolations(ctx, roots)
|
||||
|
||||
as, err := merge.GetMergeArtifactStatus(ctx, ws)
|
||||
if err != nil {
|
||||
workingTblsWithViolations = []string{}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
n := printStagedDiffs(buf, stagedTblDiffs, true)
|
||||
n = PrintDiffsNotStaged(ctx, dEnv, buf, notStagedTblDiffs, true, n, workingTblsInConflict, workingTblsWithViolations)
|
||||
n = PrintDiffsNotStaged(ctx, dEnv, buf, notStagedTblDiffs, true, n, as)
|
||||
|
||||
currBranch := dEnv.RepoStateReader().CWBHeadRef()
|
||||
initialCommitMessage := fmt.Sprintf("%s\n# Please enter the commit message for your changes. Lines starting"+
|
||||
@@ -405,12 +406,13 @@ func PrintDiffsNotStaged(
|
||||
notStagedTbls []diff.TableDelta,
|
||||
printHelp bool,
|
||||
linesPrinted int,
|
||||
workingTblsInConflict, workingTblsWithViolations []string,
|
||||
as merge.ArtifactStatus,
|
||||
) int {
|
||||
inCnfSet := set.NewStrSet(workingTblsInConflict)
|
||||
violationSet := set.NewStrSet(workingTblsWithViolations)
|
||||
inCnfSet := set.NewStrSet(as.DataConflictTables)
|
||||
inCnfSet.Add(as.SchemaConflictsTables...)
|
||||
violationSet := set.NewStrSet(as.ConstraintViolationsTables)
|
||||
|
||||
if len(workingTblsInConflict) > 0 || len(workingTblsWithViolations) > 0 {
|
||||
if as.HasConflicts() || as.HasConstraintViolations() {
|
||||
if linesPrinted > 0 {
|
||||
cli.Println()
|
||||
}
|
||||
@@ -419,16 +421,19 @@ func PrintDiffsNotStaged(
|
||||
iohelp.WriteLine(wr, mergedTableHelp)
|
||||
}
|
||||
|
||||
if len(workingTblsInConflict) > 0 {
|
||||
if as.HasConflicts() {
|
||||
lines := make([]string, 0, len(notStagedTbls))
|
||||
for _, tblName := range workingTblsInConflict {
|
||||
for _, tblName := range as.SchemaConflictsTables {
|
||||
lines = append(lines, fmt.Sprintf(statusFmt, schemaConflictLabel, tblName))
|
||||
}
|
||||
for _, tblName := range as.DataConflictTables {
|
||||
lines = append(lines, fmt.Sprintf(statusFmt, bothModifiedLabel, tblName))
|
||||
}
|
||||
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
|
||||
linesPrinted += len(lines)
|
||||
}
|
||||
|
||||
if len(workingTblsWithViolations) > 0 {
|
||||
if as.HasConstraintViolations() {
|
||||
violationOnly, _, _ := violationSet.LeftIntersectionRight(inCnfSet)
|
||||
lines := make([]string, 0, len(notStagedTbls))
|
||||
for _, tblName := range violationOnly.AsSortedSlice() {
|
||||
@@ -543,9 +548,10 @@ const (
|
||||
untrackedHeader = `Untracked files:`
|
||||
untrackedHeaderHelp = ` (use "dolt add <table>" to include in what will be committed)`
|
||||
|
||||
statusFmt = "\t%-16s%s"
|
||||
statusRenameFmt = "\t%-16s%s -> %s"
|
||||
bothModifiedLabel = "both modified:"
|
||||
statusFmt = "\t%-18s%s"
|
||||
statusRenameFmt = "\t%-18s%s -> %s"
|
||||
schemaConflictLabel = "schema conflict:"
|
||||
bothModifiedLabel = "both modified:"
|
||||
)
|
||||
|
||||
var tblDiffTypeToLabel = map[diff.TableDiffType]string{
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/argparser"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/set"
|
||||
"github.com/dolthub/dolt/go/store/datas"
|
||||
)
|
||||
|
||||
@@ -98,7 +99,7 @@ func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
|
||||
var verr errhand.VerboseError
|
||||
if apr.Contains(cli.AbortParam) {
|
||||
mergeActive, err := dEnv.IsMergeActive(ctx)
|
||||
mergeActive, err := isMergeActive(ctx, dEnv)
|
||||
if err != nil {
|
||||
cli.PrintErrln("fatal:", err.Error())
|
||||
return 1
|
||||
@@ -130,7 +131,7 @@ func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
}
|
||||
|
||||
if verr == nil {
|
||||
mergeActive, err := dEnv.IsMergeActive(ctx)
|
||||
mergeActive, err := isMergeActive(ctx, dEnv)
|
||||
if err != nil {
|
||||
cli.PrintErrln(err.Error())
|
||||
return 1
|
||||
@@ -190,28 +191,33 @@ func (cmd MergeCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
return handleCommitErr(ctx, dEnv, verr, usage)
|
||||
}
|
||||
|
||||
func getUnmergedTableCount(ctx context.Context, root *doltdb.RootValue) (int, error) {
|
||||
conflicted, err := root.TablesInConflict(ctx)
|
||||
func isMergeActive(ctx context.Context, denv *env.DoltEnv) (bool, error) {
|
||||
ws, err := denv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return false, err
|
||||
}
|
||||
cved, err := root.TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
uniqued := make(map[string]interface{})
|
||||
for _, t := range conflicted {
|
||||
uniqued[t] = struct{}{}
|
||||
}
|
||||
for _, t := range cved {
|
||||
uniqued[t] = struct{}{}
|
||||
}
|
||||
var unmergedTableCount int
|
||||
for range uniqued {
|
||||
unmergedTableCount++
|
||||
return ws.MergeActive(), nil
|
||||
}
|
||||
|
||||
func getUnmergedTableCount(ctx context.Context, ws *doltdb.WorkingSet) (int, error) {
|
||||
unmerged := set.NewStrSet(nil)
|
||||
if ws.MergeState() != nil {
|
||||
unmerged.Add(ws.MergeState().TablesWithSchemaConflicts()...)
|
||||
}
|
||||
|
||||
return unmergedTableCount, nil
|
||||
conflicted, err := ws.WorkingRoot().TablesWithDataConflicts(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
unmerged.Add(conflicted...)
|
||||
|
||||
cved, err := ws.WorkingRoot().TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
unmerged.Add(cved...)
|
||||
|
||||
return unmerged.Size(), nil
|
||||
}
|
||||
|
||||
func validateMergeSpec(ctx context.Context, spec *merge.MergeSpec) errhand.VerboseError {
|
||||
@@ -308,13 +314,13 @@ func printConflictsAndViolations(tblToStats map[string]*merge.MergeStats) (confl
|
||||
hasConflicts := false
|
||||
hasConstraintViolations := false
|
||||
for tblName, stats := range tblToStats {
|
||||
if stats.Operation == merge.TableModified && (stats.Conflicts > 0 || stats.ConstraintViolations > 0) {
|
||||
if stats.HasArtifacts() {
|
||||
cli.Println("Auto-merging", tblName)
|
||||
if stats.Conflicts > 0 {
|
||||
if stats.HasConflicts() {
|
||||
cli.Println("CONFLICT (content): Merge conflict in", tblName)
|
||||
hasConflicts = true
|
||||
}
|
||||
if stats.ConstraintViolations > 0 {
|
||||
if stats.HasConstraintViolations() {
|
||||
cli.Println("CONSTRAINT VIOLATION (content): Merge created constraint violation in", tblName)
|
||||
hasConstraintViolations = true
|
||||
}
|
||||
@@ -332,10 +338,10 @@ func printModifications(tblToStats map[string]*merge.MergeStats) {
|
||||
rowsChanged := 0
|
||||
var tbls []string
|
||||
for tblName, stats := range tblToStats {
|
||||
if stats.Operation == merge.TableModified && stats.Conflicts == 0 && stats.ConstraintViolations == 0 {
|
||||
if stats.Operation == merge.TableModified && stats.DataConflicts == 0 && stats.ConstraintViolations == 0 {
|
||||
tbls = append(tbls, tblName)
|
||||
nameLen := len(tblName)
|
||||
modCount := stats.Adds + stats.Modifications + stats.Deletes + stats.Conflicts
|
||||
modCount := stats.Adds + stats.Modifications + stats.Deletes + stats.DataConflicts
|
||||
|
||||
if nameLen > maxNameLen {
|
||||
maxNameLen = nameLen
|
||||
@@ -346,7 +352,7 @@ func printModifications(tblToStats map[string]*merge.MergeStats) {
|
||||
}
|
||||
|
||||
rowsAdded += stats.Adds
|
||||
rowsChanged += stats.Modifications + stats.Conflicts
|
||||
rowsChanged += stats.Modifications + stats.DataConflicts
|
||||
rowsDeleted += stats.Deletes
|
||||
}
|
||||
}
|
||||
@@ -362,7 +368,7 @@ func printModifications(tblToStats map[string]*merge.MergeStats) {
|
||||
for _, tbl := range tbls {
|
||||
stats := tblToStats[tbl]
|
||||
if stats.Operation == merge.TableModified {
|
||||
modCount := stats.Adds + stats.Modifications + stats.Deletes + stats.Conflicts
|
||||
modCount := stats.Adds + stats.Modifications + stats.Deletes + stats.DataConflicts
|
||||
modCountStr := strconv.FormatInt(int64(modCount), 10)
|
||||
visualizedChanges := visualizeChangeTypes(stats, maxModCount)
|
||||
|
||||
@@ -422,12 +428,12 @@ func fillStringWithChar(ch rune, strLen int) string {
|
||||
}
|
||||
|
||||
func handleMergeErr(ctx context.Context, dEnv *env.DoltEnv, mergeErr error, hasConflicts, hasConstraintViolations bool, usage cli.UsagePrinter) int {
|
||||
wRoot, err := dEnv.WorkingRoot(ctx)
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
cli.PrintErrln(err.Error())
|
||||
return 1
|
||||
}
|
||||
unmergedCnt, err := getUnmergedTableCount(ctx, wRoot)
|
||||
unmergedCnt, err := getUnmergedTableCount(ctx, ws)
|
||||
if err != nil {
|
||||
cli.PrintErrln(err.Error())
|
||||
return 1
|
||||
@@ -514,7 +520,7 @@ func executeNoFFMergeAndCommit(ctx context.Context, dEnv *env.DoltEnv, spec *mer
|
||||
return tblToStats, err
|
||||
}
|
||||
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws.MergeActive(), mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
|
||||
Message: msg,
|
||||
Date: spec.Date,
|
||||
AllowEmpty: spec.AllowEmpty,
|
||||
@@ -593,7 +599,7 @@ func getCommitMsgForMerge(ctx context.Context, dEnv *env.DoltEnv, userDefinedMsg
|
||||
// hasConflictOrViolations checks for conflicts or constraint violation regardless of a table being modified
|
||||
func hasConflictOrViolations(tblToStats map[string]*merge.MergeStats) bool {
|
||||
for _, tblStats := range tblToStats {
|
||||
if tblStats.Conflicts > 0 || tblStats.ConstraintViolations > 0 {
|
||||
if tblStats.HasArtifacts() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,14 +148,14 @@ func applyStashAtIdx(ctx context.Context, dEnv *env.DoltEnv, curWorkingRoot *dol
|
||||
}
|
||||
|
||||
opts := editor.Options{Deaf: dEnv.BulkDbEaFactory(), Tempdir: tmpDir}
|
||||
mergedRoot, mergeStats, err := merge.MergeRoots(ctx, curWorkingRoot, stashRoot, parentRoot, stashRoot, parentCommit, opts, merge.MergeOpts{IsCherryPick: false})
|
||||
result, err := merge.MergeRoots(ctx, curWorkingRoot, stashRoot, parentRoot, stashRoot, parentCommit, opts, merge.MergeOpts{IsCherryPick: false})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var tablesWithConflict []string
|
||||
for tbl, stats := range mergeStats {
|
||||
if stats.Conflicts > 0 {
|
||||
for tbl, stats := range result.Stats {
|
||||
if stats.HasConflicts() {
|
||||
tablesWithConflict = append(tablesWithConflict, tbl)
|
||||
}
|
||||
}
|
||||
@@ -168,7 +168,7 @@ func applyStashAtIdx(ctx context.Context, dEnv *env.DoltEnv, curWorkingRoot *dol
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err = dEnv.UpdateWorkingRoot(ctx, mergedRoot)
|
||||
err = dEnv.UpdateWorkingRoot(ctx, result.Root)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -76,26 +76,25 @@ func (cmd StatusCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
return handleStatusVErr(err)
|
||||
}
|
||||
|
||||
workingTblsInConflict, _, _, err := merge.GetTablesInConflict(ctx, roots)
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
handleStatusVErr(err)
|
||||
}
|
||||
|
||||
as, err := merge.GetMergeArtifactStatus(ctx, ws)
|
||||
if err != nil {
|
||||
handleStatusVErr(err)
|
||||
}
|
||||
|
||||
err = PrintStatus(ctx, dEnv, staged, notStaged, as)
|
||||
if err != nil {
|
||||
return handleStatusVErr(err)
|
||||
}
|
||||
|
||||
workingTblsWithViolations, _, _, err := merge.GetTablesWithConstraintViolations(ctx, roots)
|
||||
if err != nil {
|
||||
return handleStatusVErr(err)
|
||||
}
|
||||
|
||||
err = PrintStatus(ctx, dEnv, staged, notStaged, workingTblsInConflict, workingTblsWithViolations)
|
||||
if err != nil {
|
||||
return handleStatusVErr(err)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// TODO: working docs in conflict param not used here
|
||||
func PrintStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTbls []diff.TableDelta, workingTblsInConflict, workingTblsWithViolations []string) error {
|
||||
func PrintStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTbls []diff.TableDelta, as merge.ArtifactStatus) error {
|
||||
cli.Printf(branchHeader, dEnv.RepoStateReader().CWBHeadRef().GetPath())
|
||||
|
||||
err := printRemoteRefTrackingInfo(ctx, dEnv)
|
||||
@@ -103,17 +102,17 @@ func PrintStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTb
|
||||
return err
|
||||
}
|
||||
|
||||
mergeActive, err := dEnv.IsMergeActive(ctx)
|
||||
mergeActive, err := isMergeActive(ctx, dEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if mergeActive {
|
||||
if len(workingTblsInConflict) > 0 && len(workingTblsWithViolations) > 0 {
|
||||
if as.HasConflicts() && as.HasConstraintViolations() {
|
||||
cli.Println(fmt.Sprintf(unmergedTablesHeader, "conflicts and constraint violations"))
|
||||
} else if len(workingTblsInConflict) > 0 {
|
||||
} else if as.HasConflicts() {
|
||||
cli.Println(fmt.Sprintf(unmergedTablesHeader, "conflicts"))
|
||||
} else if len(workingTblsWithViolations) > 0 {
|
||||
} else if as.HasConstraintViolations() {
|
||||
cli.Println(fmt.Sprintf(unmergedTablesHeader, "constraint violations"))
|
||||
} else {
|
||||
cli.Println(allMergedHeader)
|
||||
@@ -121,7 +120,7 @@ func PrintStatus(ctx context.Context, dEnv *env.DoltEnv, stagedTbls, notStagedTb
|
||||
}
|
||||
|
||||
n := printStagedDiffs(cli.CliOut, stagedTbls, true)
|
||||
n = PrintDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, true, n, workingTblsInConflict, workingTblsWithViolations)
|
||||
n = PrintDiffsNotStaged(ctx, dEnv, cli.CliOut, notStagedTbls, true, n, as)
|
||||
|
||||
if !mergeActive && n == 0 {
|
||||
cli.Println("nothing to commit, working tree clean")
|
||||
|
||||
@@ -353,7 +353,24 @@ func (rcv *MergeState) FromCommitSpecStr() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
const MergeStateNumFields = 3
|
||||
func (rcv *MergeState) UnmergableTables(j int) []byte {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
|
||||
if o != 0 {
|
||||
a := rcv._tab.Vector(o)
|
||||
return rcv._tab.ByteVector(a + flatbuffers.UOffsetT(j*4))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rcv *MergeState) UnmergableTablesLength() int {
|
||||
o := flatbuffers.UOffsetT(rcv._tab.Offset(10))
|
||||
if o != 0 {
|
||||
return rcv._tab.VectorLen(o)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const MergeStateNumFields = 4
|
||||
|
||||
func MergeStateStart(builder *flatbuffers.Builder) {
|
||||
builder.StartObject(MergeStateNumFields)
|
||||
@@ -373,6 +390,12 @@ func MergeStateStartFromCommitAddrVector(builder *flatbuffers.Builder, numElems
|
||||
func MergeStateAddFromCommitSpecStr(builder *flatbuffers.Builder, fromCommitSpecStr flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(2, flatbuffers.UOffsetT(fromCommitSpecStr), 0)
|
||||
}
|
||||
func MergeStateAddUnmergableTables(builder *flatbuffers.Builder, unmergableTables flatbuffers.UOffsetT) {
|
||||
builder.PrependUOffsetTSlot(3, flatbuffers.UOffsetT(unmergableTables), 0)
|
||||
}
|
||||
func MergeStateStartUnmergableTablesVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT {
|
||||
return builder.StartVector(4, numElems, 4)
|
||||
}
|
||||
func MergeStateEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT {
|
||||
return builder.EndObject()
|
||||
}
|
||||
|
||||
@@ -694,7 +694,7 @@ func (root *RootValue) getTableMap(ctx context.Context) (tableMap, error) {
|
||||
return root.st.GetTablesMap(ctx, root.vrw, root.ns)
|
||||
}
|
||||
|
||||
func (root *RootValue) TablesInConflict(ctx context.Context) ([]string, error) {
|
||||
func (root *RootValue) TablesWithDataConflicts(ctx context.Context) ([]string, error) {
|
||||
names, err := root.GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -747,7 +747,7 @@ func (root *RootValue) TablesWithConstraintViolations(ctx context.Context) ([]st
|
||||
}
|
||||
|
||||
func (root *RootValue) HasConflicts(ctx context.Context) (bool, error) {
|
||||
cnfTbls, err := root.TablesInConflict(ctx)
|
||||
cnfTbls, err := root.TablesWithDataConflicts(ctx)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -277,6 +277,9 @@ const (
|
||||
// TableOfTablesWithViolationsName is the constraint violations system table name
|
||||
TableOfTablesWithViolationsName = "dolt_constraint_violations"
|
||||
|
||||
// SchemaConflictsTableName is the schema conflicts system table name
|
||||
SchemaConflictsTableName = "dolt_schema_conflicts"
|
||||
|
||||
// BranchesTableName is the branches system table name
|
||||
BranchesTableName = "dolt_branches"
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
|
||||
"github.com/dolthub/dolt/go/store/datas"
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
@@ -30,8 +32,22 @@ type MergeState struct {
|
||||
// the source commit
|
||||
commit *Commit
|
||||
// the spec string that was used to specify |commit|
|
||||
commitSpecStr string
|
||||
preMergeWorking *RootValue
|
||||
commitSpecStr string
|
||||
preMergeWorking *RootValue
|
||||
unmergableTables []string
|
||||
}
|
||||
|
||||
// todo(andy): this might make more sense in pkg merge
|
||||
type SchemaConflict struct {
|
||||
ToSch, FromSch schema.Schema
|
||||
ToFks, FromFks []ForeignKey
|
||||
ToParentSchemas map[string]schema.Schema
|
||||
FromParentSchemas map[string]schema.Schema
|
||||
toTbl, fromTbl *Table
|
||||
}
|
||||
|
||||
func (sc SchemaConflict) GetConflictingTables() (ours, theirs *Table) {
|
||||
return sc.toTbl, sc.fromTbl
|
||||
}
|
||||
|
||||
// TodoWorkingSetMeta returns an incomplete WorkingSetMeta, suitable for methods that don't have the means to construct
|
||||
@@ -63,6 +79,72 @@ func (m MergeState) PreMergeWorkingRoot() *RootValue {
|
||||
return m.preMergeWorking
|
||||
}
|
||||
|
||||
type SchemaConflictFn func(table string, conflict SchemaConflict) error
|
||||
|
||||
func (m MergeState) HasSchemaConflicts() bool {
|
||||
return len(m.unmergableTables) > 0
|
||||
}
|
||||
|
||||
func (m MergeState) TablesWithSchemaConflicts() []string {
|
||||
return m.unmergableTables
|
||||
}
|
||||
|
||||
func (m MergeState) IterSchemaConflicts(ctx context.Context, ddb *DoltDB, cb SchemaConflictFn) (err error) {
|
||||
var to, from *RootValue
|
||||
|
||||
to = m.preMergeWorking
|
||||
if from, err = m.commit.GetRootValue(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
toFKs, err := to.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toSchemas, err := to.GetAllSchemas(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fromFKs, err := from.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fromSchemas, err := from.GetAllSchemas(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, name := range m.unmergableTables {
|
||||
var sc SchemaConflict
|
||||
if sc.toTbl, _, err = to.GetTable(ctx, name); err != nil {
|
||||
return err
|
||||
}
|
||||
// todo: handle schema conflicts for renamed tables
|
||||
if sc.fromTbl, _, err = from.GetTable(ctx, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sc.ToSch, err = sc.toTbl.GetSchema(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if sc.FromSch, err = sc.fromTbl.GetSchema(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sc.ToFks, _ = toFKs.KeysForTable(name)
|
||||
sc.ToParentSchemas = toSchemas
|
||||
|
||||
sc.FromFks, _ = fromFKs.KeysForTable(name)
|
||||
sc.FromParentSchemas = fromSchemas
|
||||
|
||||
if err = cb(name, sc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type WorkingSet struct {
|
||||
Name string
|
||||
meta *datas.WorkingSetMeta
|
||||
@@ -96,6 +178,11 @@ func (ws WorkingSet) WithMergeState(mergeState *MergeState) *WorkingSet {
|
||||
return &ws
|
||||
}
|
||||
|
||||
func (ws WorkingSet) WithUnmergableTables(tables []string) *WorkingSet {
|
||||
ws.mergeState.unmergableTables = tables
|
||||
return &ws
|
||||
}
|
||||
|
||||
func (ws WorkingSet) StartMerge(commit *Commit, commitSpecStr string) *WorkingSet {
|
||||
ws.mergeState = &MergeState{
|
||||
commit: commit,
|
||||
@@ -207,10 +294,16 @@ func NewWorkingSet(ctx context.Context, name string, vrw types.ValueReadWriter,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unmergableTables, err := dsws.MergeState.UnmergableTables(ctx, vrw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mergeState = &MergeState{
|
||||
commit: commit,
|
||||
commitSpecStr: commitSpec,
|
||||
preMergeWorking: preMergeWorkingRoot,
|
||||
commit: commit,
|
||||
commitSpecStr: commitSpec,
|
||||
preMergeWorking: preMergeWorkingRoot,
|
||||
unmergableTables: unmergableTables,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +379,7 @@ func (ws *WorkingSet) writeValues(ctx context.Context, db *DoltDB) (
|
||||
return types.Ref{}, types.Ref{}, nil, err
|
||||
}
|
||||
|
||||
mergeState, err = datas.NewMergeState(ctx, db.vrw, preMergeWorking, dCommit, ws.mergeState.commitSpecStr)
|
||||
mergeState, err = datas.NewMergeState(ctx, db.vrw, preMergeWorking, dCommit, ws.mergeState.commitSpecStr, ws.mergeState.unmergableTables)
|
||||
if err != nil {
|
||||
return types.Ref{}, types.Ref{}, nil, err
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ func (mr *MultiRepoTestSetup) CommitWithWorkingSet(dbName string) *doltdb.Commit
|
||||
if err != nil {
|
||||
panic("couldn't get roots: " + err.Error())
|
||||
}
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws.MergeActive(), mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, ws, mergeParentCommits, dEnv.DbData().Ddb, actions.CommitStagedProps{
|
||||
Message: "auto commit",
|
||||
Date: t,
|
||||
AllowEmpty: true,
|
||||
|
||||
+10
-3
@@ -37,7 +37,7 @@ type CommitStagedProps struct {
|
||||
func GetCommitStaged(
|
||||
ctx context.Context,
|
||||
roots doltdb.Roots,
|
||||
mergeActive bool,
|
||||
ws *doltdb.WorkingSet,
|
||||
mergeParents []*doltdb.Commit,
|
||||
db *doltdb.DoltDB,
|
||||
props CommitStagedProps,
|
||||
@@ -61,13 +61,13 @@ func GetCommitStaged(
|
||||
}
|
||||
|
||||
isEmpty := len(staged) == 0
|
||||
allowEmpty := mergeActive || props.AllowEmpty || props.Amend
|
||||
allowEmpty := ws.MergeActive() || props.AllowEmpty || props.Amend
|
||||
if isEmpty && !allowEmpty {
|
||||
return nil, NothingStaged{notStaged}
|
||||
}
|
||||
|
||||
if !props.Force {
|
||||
inConflict, err := roots.Working.TablesInConflict(ctx)
|
||||
inConflict, err := roots.Working.TablesWithDataConflicts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -81,6 +81,13 @@ func GetCommitStaged(
|
||||
if len(violatesConstraints) > 0 {
|
||||
return nil, NewTblHasConstraintViolations(violatesConstraints)
|
||||
}
|
||||
|
||||
if ws.MergeActive() {
|
||||
schConflicts := ws.MergeState().TablesWithSchemaConflicts()
|
||||
if len(schConflicts) > 0 {
|
||||
return nil, NewTblSchemaConflictError(schConflicts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !props.Force {
|
||||
|
||||
+9
-4
@@ -23,10 +23,11 @@ import (
|
||||
type tblErrorType string
|
||||
|
||||
const (
|
||||
tblErrInvalid tblErrorType = "invalid"
|
||||
tblErrTypeNotExist tblErrorType = "do not exist"
|
||||
tblErrTypeInConflict tblErrorType = "are in conflict"
|
||||
tblErrTypeConstViols tblErrorType = "have constraint violations"
|
||||
tblErrInvalid tblErrorType = "invalid"
|
||||
tblErrTypeNotExist tblErrorType = "do not exist"
|
||||
tblErrTypeInConflict tblErrorType = "are in conflict"
|
||||
tblErrTypeSchConflict tblErrorType = "have schema conflicts"
|
||||
tblErrTypeConstViols tblErrorType = "have constraint violations"
|
||||
)
|
||||
|
||||
type TblError struct {
|
||||
@@ -42,6 +43,10 @@ func NewTblInConflictError(tbls []string) TblError {
|
||||
return TblError{tbls, tblErrTypeInConflict}
|
||||
}
|
||||
|
||||
func NewTblSchemaConflictError(tbls []string) TblError {
|
||||
return TblError{tbls, tblErrTypeSchConflict}
|
||||
}
|
||||
|
||||
func NewTblHasConstraintViolations(tbls []string) TblError {
|
||||
return TblError{tbls, tblErrTypeConstViols}
|
||||
}
|
||||
|
||||
-51
@@ -788,57 +788,6 @@ func (dEnv *DoltEnv) NewWorkingSetMeta(message string) *datas.WorkingSetMeta {
|
||||
}
|
||||
}
|
||||
|
||||
func (dEnv *DoltEnv) ClearMerge(ctx context.Context) error {
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, err := ws.HashOf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dEnv.DoltDB.UpdateWorkingSet(ctx, ws.Ref(), ws.ClearMerge(), h, dEnv.workingSetMeta())
|
||||
}
|
||||
|
||||
// StartMerge updates the WorkingSet with merge information. |commit| is the
|
||||
// source commit of the merge and |commitSpecStr| is how that |commit| was
|
||||
// specified. Typically, |commitSpecStr| is specified by the user, but it could
|
||||
// also be specified by a transaction merge.
|
||||
func (dEnv *DoltEnv) StartMerge(ctx context.Context, commit *doltdb.Commit, commitSpecStr string) error {
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h, err := ws.HashOf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dEnv.DoltDB.UpdateWorkingSet(ctx, ws.Ref(), ws.StartMerge(commit, commitSpecStr), h, dEnv.workingSetMeta())
|
||||
}
|
||||
|
||||
func (dEnv *DoltEnv) IsMergeActive(ctx context.Context) (bool, error) {
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return ws.MergeActive(), nil
|
||||
}
|
||||
|
||||
func (dEnv *DoltEnv) GetTablesWithConflicts(ctx context.Context) ([]string, error) {
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root.TablesInConflict(ctx)
|
||||
}
|
||||
|
||||
func (dEnv *DoltEnv) CredsDir() (string, error) {
|
||||
return getCredsDir(dEnv.hdp)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
|
||||
"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/store/hash"
|
||||
)
|
||||
|
||||
@@ -116,15 +115,14 @@ func NewMergeSpec(ctx context.Context, rsr env.RepoStateReader, ddb *doltdb.Dolt
|
||||
|
||||
func ExecNoFFMerge(ctx context.Context, dEnv *env.DoltEnv, spec *MergeSpec) (map[string]*MergeStats, error) {
|
||||
mergedRoot, err := spec.MergeC.GetRootValue(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrFailedToReadDatabase
|
||||
}
|
||||
result := &Result{Root: mergedRoot, Stats: make(map[string]*MergeStats)}
|
||||
|
||||
tblToStats := make(map[string]*MergeStats)
|
||||
err = mergedRootToWorking(ctx, false, dEnv, mergedRoot, spec.WorkingDiffs, spec.MergeC, spec.MergeCSpecStr, tblToStats)
|
||||
err = mergedRootToWorking(ctx, false, dEnv, result, spec.WorkingDiffs, spec.MergeC, spec.MergeCSpecStr)
|
||||
|
||||
return tblToStats, err
|
||||
return result.Stats, err
|
||||
}
|
||||
|
||||
func applyChanges(ctx context.Context, root *doltdb.RootValue, workingDiffs map[string]hash.Hash) (*doltdb.RootValue, error) {
|
||||
@@ -182,74 +180,61 @@ func ExecuteMerge(ctx context.Context, dEnv *env.DoltEnv, spec *MergeSpec) (map[
|
||||
return nil, err
|
||||
}
|
||||
opts := editor.Options{Deaf: dEnv.BulkDbEaFactory(), Tempdir: tmpDir}
|
||||
mergedRoot, tblToStats, err := MergeCommits(ctx, spec.HeadC, spec.MergeC, opts)
|
||||
result, err := MergeCommits(ctx, spec.HeadC, spec.MergeC, opts)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case doltdb.ErrUpToDate:
|
||||
return tblToStats, fmt.Errorf("already up to date; %w", err)
|
||||
return result.Stats, fmt.Errorf("already up to date; %w", err)
|
||||
case ErrFastForward:
|
||||
panic("fast forward merge")
|
||||
}
|
||||
return tblToStats, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tblToStats, mergedRootToWorking(ctx, spec.Squash, dEnv, mergedRoot, spec.WorkingDiffs, spec.MergeC, spec.MergeCSpecStr, tblToStats)
|
||||
err = mergedRootToWorking(ctx, spec.Squash, dEnv, result, spec.WorkingDiffs, spec.MergeC, spec.MergeCSpecStr)
|
||||
return result.Stats, nil
|
||||
}
|
||||
|
||||
// TODO: change this to be functional and not write to repo state
|
||||
func mergedRootToWorking(
|
||||
ctx context.Context,
|
||||
squash bool,
|
||||
dEnv *env.DoltEnv,
|
||||
mergedRoot *doltdb.RootValue,
|
||||
result *Result,
|
||||
workingDiffs map[string]hash.Hash,
|
||||
cm2 *doltdb.Commit,
|
||||
cm2SpecStr string,
|
||||
tblToStats map[string]*MergeStats,
|
||||
) error {
|
||||
var err error
|
||||
|
||||
workingRoot := mergedRoot
|
||||
) (err error) {
|
||||
staged, working := result.Root, result.Root
|
||||
if len(workingDiffs) > 0 {
|
||||
workingRoot, err = applyChanges(ctx, mergedRoot, workingDiffs)
|
||||
|
||||
working, err = applyChanges(ctx, working, workingDiffs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !squash {
|
||||
err = dEnv.StartMerge(ctx, cm2, cm2SpecStr)
|
||||
|
||||
if err != nil {
|
||||
return actions.ErrFailedToSaveRepoState
|
||||
}
|
||||
}
|
||||
|
||||
err = dEnv.UpdateWorkingRoot(context.Background(), workingRoot)
|
||||
ws, err := dEnv.WorkingSet(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conflicts, constraintViolations := conflictsAndViolations(tblToStats)
|
||||
if len(conflicts) > 0 || len(constraintViolations) > 0 {
|
||||
if !squash || result.HasSchemaConflicts() {
|
||||
ws = ws.StartMerge(cm2, cm2SpecStr)
|
||||
tt := SchemaConflictTableNames(result.SchemaConflicts)
|
||||
ws = ws.WithUnmergableTables(tt)
|
||||
}
|
||||
|
||||
ws = ws.WithWorkingRoot(working)
|
||||
if !result.HasMergeArtifacts() {
|
||||
ws = ws.WithStagedRoot(staged)
|
||||
}
|
||||
|
||||
if err = dEnv.UpdateWorkingSet(ctx, ws); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dEnv.UpdateStagedRoot(context.Background(), mergedRoot)
|
||||
}
|
||||
|
||||
// conflictsAndViolations returns array of conflicts and constraintViolations
|
||||
func conflictsAndViolations(tblToStats map[string]*MergeStats) (conflicts []string, constraintViolations []string) {
|
||||
for tblName, stats := range tblToStats {
|
||||
if stats.Operation == TableModified && (stats.Conflicts > 0 || stats.ConstraintViolations > 0) {
|
||||
if stats.Conflicts > 0 {
|
||||
conflicts = append(conflicts, tblName)
|
||||
}
|
||||
if stats.ConstraintViolations > 0 {
|
||||
constraintViolations = append(constraintViolations, tblName)
|
||||
}
|
||||
}
|
||||
if result.HasMergeArtifacts() {
|
||||
// this error is recoverable in some contexts
|
||||
return doltdb.ErrUnresolvedConflictsOrViolations
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -125,10 +125,12 @@ func TestMerge(t *testing.T) {
|
||||
defer dEnv.DoltDB.Close()
|
||||
|
||||
for _, tc := range setupCommon {
|
||||
tc.exec(t, ctx, dEnv)
|
||||
exit := tc.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
for _, tc := range test.setup {
|
||||
tc.exec(t, ctx, dEnv)
|
||||
exit := tc.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
@@ -245,10 +247,18 @@ func TestMergeConflicts(t *testing.T) {
|
||||
defer dEnv.DoltDB.Close()
|
||||
|
||||
for _, tc := range setupCommon {
|
||||
tc.exec(t, ctx, dEnv)
|
||||
exit := tc.exec(t, ctx, dEnv)
|
||||
// allow merge to fail with conflicts
|
||||
if _, ok := tc.cmd.(cmd.MergeCmd); !ok {
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
}
|
||||
for _, tc := range test.setup {
|
||||
tc.exec(t, ctx, dEnv)
|
||||
exit := tc.exec(t, ctx, dEnv)
|
||||
// allow merge to fail with conflicts
|
||||
if _, ok := tc.cmd.(cmd.MergeCmd); !ok {
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
}
|
||||
|
||||
root, err := dEnv.WorkingRoot(ctx)
|
||||
|
||||
@@ -250,7 +250,10 @@ func TestKeylessMergeConflicts(t *testing.T) {
|
||||
|
||||
for _, c := range cc {
|
||||
exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, nil)
|
||||
require.Equal(t, 0, exitCode)
|
||||
// allow merge to fail with conflicts
|
||||
if _, ok := c.cmd.(cmd.MergeCmd); !ok {
|
||||
require.Equal(t, 0, exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,28 +45,62 @@ var ErrMultipleViolationsForRow = errors.New("multiple violations for row not su
|
||||
|
||||
var ErrSameTblAddedTwice = goerrors.NewKind("table with same name '%s' added in 2 commits can't be merged")
|
||||
|
||||
func MergeCommits(ctx context.Context, commit, mergeCommit *doltdb.Commit, opts editor.Options) (*doltdb.RootValue, map[string]*MergeStats, error) {
|
||||
func MergeCommits(ctx context.Context, commit, mergeCommit *doltdb.Commit, opts editor.Options) (*Result, error) {
|
||||
ancCommit, err := doltdb.GetCommitAncestor(ctx, commit, mergeCommit)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ourRoot, err := commit.GetRootValue(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
theirRoot, err := mergeCommit.GetRootValue(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ancRoot, err := ancCommit.GetRootValue(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return MergeRoots(ctx, ourRoot, theirRoot, ancRoot, mergeCommit, ancCommit, opts, MergeOpts{IsCherryPick: false})
|
||||
mo := MergeOpts{
|
||||
IsCherryPick: false,
|
||||
KeepSchemaConflicts: true,
|
||||
}
|
||||
return MergeRoots(ctx, ourRoot, theirRoot, ancRoot, mergeCommit, ancCommit, opts, mo)
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Root *doltdb.RootValue
|
||||
SchemaConflicts []SchemaConflict
|
||||
Stats map[string]*MergeStats
|
||||
}
|
||||
|
||||
func (r Result) HasSchemaConflicts() bool {
|
||||
return len(r.SchemaConflicts) > 0
|
||||
}
|
||||
|
||||
func (r Result) HasMergeArtifacts() bool {
|
||||
if r.HasSchemaConflicts() {
|
||||
return true
|
||||
}
|
||||
for _, stats := range r.Stats {
|
||||
if stats.HasArtifacts() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SchemaConflictTableNames(sc []SchemaConflict) (tables []string) {
|
||||
tables = make([]string, len(sc))
|
||||
for i := range sc {
|
||||
tables[i] = sc[i].TableName
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MergeRoots three-way merges |ourRoot|, |theirRoot|, and |ancRoot| and returns
|
||||
@@ -88,18 +122,23 @@ func MergeRoots(
|
||||
theirs, ancestor doltdb.Rootish,
|
||||
opts editor.Options,
|
||||
mergeOpts MergeOpts,
|
||||
) (*doltdb.RootValue, map[string]*MergeStats, error) {
|
||||
var conflictStash *conflictStash
|
||||
var violationStash *violationStash
|
||||
var err error
|
||||
if !types.IsFormat_DOLT(ourRoot.VRW().Format()) {
|
||||
) (*Result, error) {
|
||||
var (
|
||||
conflictStash *conflictStash
|
||||
violationStash *violationStash
|
||||
nbf *types.NomsBinFormat
|
||||
err error
|
||||
)
|
||||
|
||||
nbf = ourRoot.VRW().Format()
|
||||
if !types.IsFormat_DOLT(nbf) {
|
||||
ourRoot, conflictStash, err = stashConflicts(ctx, ourRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
ancRoot, violationStash, err = stashViolations(ctx, ancRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +151,7 @@ func MergeRoots(
|
||||
tblNames, err := doltdb.UnionTableNames(ctx, ourRoot, theirRoot)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tblToStats := make(map[string]*MergeStats)
|
||||
@@ -127,28 +166,37 @@ func MergeRoots(
|
||||
// TODO: merge based on a more durable table identity that persists across renames
|
||||
merger, err := NewMerger(ourRoot, theirRoot, ancRoot, theirs, ancestor, ourRoot.VRW(), ourRoot.NodeStore())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var schConflicts []SchemaConflict
|
||||
for _, tblName := range tblNames {
|
||||
mergedTable, stats, err := merger.MergeTable(ctx, tblName, opts, mergeOpts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
if mergedTable.conflict.Count() > 0 {
|
||||
if types.IsFormat_DOLT(nbf) {
|
||||
schConflicts = append(schConflicts, mergedTable.conflict)
|
||||
} else {
|
||||
// return schema conflict as error
|
||||
return nil, mergedTable.conflict
|
||||
}
|
||||
}
|
||||
|
||||
if mergedTable != nil {
|
||||
if mergedTable.table != nil {
|
||||
tblToStats[tblName] = stats
|
||||
|
||||
mergedRoot, err = mergedRoot.PutTable(ctx, tblName, mergedTable)
|
||||
mergedRoot, err = mergedRoot.PutTable(ctx, tblName, mergedTable.table)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
newRootHasTable, err := mergedRoot.HasTable(ctx, tblName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if newRootHasTable {
|
||||
@@ -157,7 +205,7 @@ func MergeRoots(
|
||||
|
||||
mergedRoot, err = mergedRoot.RemoveTables(ctx, false, false, tblName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// This is a deleted table that the merge root still has
|
||||
@@ -170,57 +218,65 @@ func MergeRoots(
|
||||
|
||||
mergedFKColl, conflicts, err := ForeignKeysMerge(ctx, mergedRoot, ourRoot, theirRoot, ancRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
if len(conflicts) > 0 {
|
||||
return nil, nil, fmt.Errorf("foreign key conflicts")
|
||||
return nil, fmt.Errorf("foreign key conflicts")
|
||||
}
|
||||
|
||||
mergedRoot, err = mergedRoot.PutForeignKeyCollection(ctx, mergedFKColl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h, err := merger.rightSrc.HashOf()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mergedRoot, _, err = AddForeignKeyViolations(ctx, mergedRoot, ancRoot, nil, h)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if types.IsFormat_DOLT(ourRoot.VRW().Format()) {
|
||||
err = getConstraintViolationStats(ctx, mergedRoot, tblToStats)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedRoot, tblToStats, nil
|
||||
return &Result{
|
||||
Root: mergedRoot,
|
||||
SchemaConflicts: schConflicts,
|
||||
Stats: tblToStats,
|
||||
}, nil
|
||||
}
|
||||
|
||||
mergedRoot, err = mergeCVsWithStash(ctx, mergedRoot, violationStash)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = getConstraintViolationStats(ctx, mergedRoot, tblToStats)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mergedHasConflicts := checkForConflicts(tblToStats)
|
||||
if !conflictStash.Empty() && mergedHasConflicts {
|
||||
return nil, nil, ErrCantOverwriteConflicts
|
||||
return nil, ErrCantOverwriteConflicts
|
||||
} else if !conflictStash.Empty() {
|
||||
mergedRoot, err = applyConflictStash(ctx, conflictStash.Stash, mergedRoot)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return mergedRoot, tblToStats, nil
|
||||
return &Result{
|
||||
Root: mergedRoot,
|
||||
SchemaConflicts: schConflicts,
|
||||
Stats: tblToStats,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// mergeCVsWithStash merges the table constraint violations in |stash| with |root|.
|
||||
@@ -264,7 +320,7 @@ func mergeCVsWithStash(ctx context.Context, root *doltdb.RootValue, stash *viola
|
||||
// checks if a conflict occurred during the merge
|
||||
func checkForConflicts(tblToStats map[string]*MergeStats) bool {
|
||||
for _, stat := range tblToStats {
|
||||
if stat.Conflicts > 0 {
|
||||
if stat.HasConflicts() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -320,48 +376,35 @@ func MayHaveConstraintViolations(ctx context.Context, ancestor, merged *doltdb.R
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func GetTablesInConflict(ctx context.Context, roots doltdb.Roots) (
|
||||
workingInConflict, stagedInConflict, headInConflict []string,
|
||||
err error,
|
||||
) {
|
||||
headInConflict, err = roots.Head.TablesInConflict(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
stagedInConflict, err = roots.Staged.TablesInConflict(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
workingInConflict, err = roots.Working.TablesInConflict(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return workingInConflict, stagedInConflict, headInConflict, err
|
||||
type ArtifactStatus struct {
|
||||
SchemaConflictsTables []string
|
||||
DataConflictTables []string
|
||||
ConstraintViolationsTables []string
|
||||
}
|
||||
|
||||
func GetTablesWithConstraintViolations(ctx context.Context, roots doltdb.Roots) (
|
||||
workingViolations, stagedViolations, headViolations []string,
|
||||
err error,
|
||||
) {
|
||||
headViolations, err = roots.Head.TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
func (as ArtifactStatus) HasConflicts() bool {
|
||||
return len(as.DataConflictTables) > 0 || len(as.SchemaConflictsTables) > 0
|
||||
}
|
||||
|
||||
func (as ArtifactStatus) HasConstraintViolations() bool {
|
||||
return len(as.ConstraintViolationsTables) > 0
|
||||
}
|
||||
|
||||
func GetMergeArtifactStatus(ctx context.Context, working *doltdb.WorkingSet) (as ArtifactStatus, err error) {
|
||||
if working.MergeActive() {
|
||||
as.SchemaConflictsTables = working.MergeState().TablesWithSchemaConflicts()
|
||||
}
|
||||
|
||||
stagedViolations, err = roots.Staged.TablesWithConstraintViolations(ctx)
|
||||
as.DataConflictTables, err = working.WorkingRoot().TablesWithDataConflicts(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return as, err
|
||||
}
|
||||
|
||||
workingViolations, err = roots.Working.TablesWithConstraintViolations(ctx)
|
||||
as.ConstraintViolationsTables, err = working.WorkingRoot().TablesWithConstraintViolations(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return as, err
|
||||
}
|
||||
|
||||
return workingViolations, stagedViolations, headViolations, err
|
||||
return
|
||||
}
|
||||
|
||||
// MergeWouldStompChanges returns list of table names that are stomped and the diffs map between head and working set.
|
||||
|
||||
@@ -101,7 +101,7 @@ func mergeNomsTable(ctx context.Context, tm *TableMerger, mergedSch schema.Schem
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stats.Conflicts = int(cons.Len())
|
||||
stats.DataConflicts = int(cons.Len())
|
||||
}
|
||||
|
||||
resultTbl, err = mergeAutoIncrementValues(ctx, tm.leftTbl, tm.rightTbl, resultTbl)
|
||||
|
||||
@@ -91,7 +91,7 @@ func mergeProllyTable(ctx context.Context, tm *TableMerger, mergedSch schema.Sch
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
stats.Conflicts = int(n)
|
||||
stats.DataConflicts = int(n)
|
||||
|
||||
mergeTbl, err = mergeAutoIncrementValues(ctx, tm.leftTbl, tm.rightTbl, mergeTbl)
|
||||
if err != nil {
|
||||
@@ -162,13 +162,13 @@ func mergeProllyTableData(ctx context.Context, tm *TableMerger, finalSch schema.
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
s.Conflicts += cnt
|
||||
s.DataConflicts += cnt
|
||||
|
||||
switch diff.Op {
|
||||
case tree.DiffOpDivergentModifyConflict, tree.DiffOpDivergentDeleteConflict:
|
||||
// In this case, a modification or delete was made to one side, and a conflicting delete or modification
|
||||
// was made to the other side, so these cannot be automatically resolved.
|
||||
s.Conflicts++
|
||||
s.DataConflicts++
|
||||
err = conflicts.merge(ctx, diff, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -218,7 +218,7 @@ func mergeProllyTableData(ctx context.Context, tm *TableMerger, finalSch schema.
|
||||
case tree.DiffOpConvergentAdd, tree.DiffOpConvergentModify, tree.DiffOpConvergentDelete:
|
||||
// In this case, both sides of the merge have made the same change, so no additional changes are needed.
|
||||
if keyless {
|
||||
s.Conflicts++
|
||||
s.DataConflicts++
|
||||
err = conflicts.merge(ctx, diff, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -34,7 +34,11 @@ import (
|
||||
)
|
||||
|
||||
type MergeOpts struct {
|
||||
// IsCherryPick is set for cherry-pick operations.
|
||||
IsCherryPick bool
|
||||
// KeepSchemaConflicts if schema conflicts should be
|
||||
// stored, otherwise we end the merge with an error.
|
||||
KeepSchemaConflicts bool
|
||||
}
|
||||
|
||||
type TableMerger struct {
|
||||
@@ -104,9 +108,14 @@ func NewMerger(
|
||||
}, nil
|
||||
}
|
||||
|
||||
type MergedTable struct {
|
||||
table *doltdb.Table
|
||||
conflict SchemaConflict
|
||||
}
|
||||
|
||||
// MergeTable merges schema and table data for the table tblName.
|
||||
// TODO: this code will loop infinitely when merging certain schema changes
|
||||
func (rm *RootMerger) MergeTable(ctx context.Context, tblName string, opts editor.Options, mergeOpts MergeOpts) (*doltdb.Table, *MergeStats, error) {
|
||||
func (rm *RootMerger) MergeTable(ctx context.Context, tblName string, opts editor.Options, mergeOpts MergeOpts) (*MergedTable, *MergeStats, error) {
|
||||
tm, err := rm.makeTableMerger(ctx, tblName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -115,7 +124,7 @@ func (rm *RootMerger) MergeTable(ctx context.Context, tblName string, opts edito
|
||||
// short-circuit here if we can
|
||||
finished, stats, err := rm.maybeShortCircuit(ctx, tm, mergeOpts)
|
||||
if finished != nil || stats != nil || err != nil {
|
||||
return finished, stats, err
|
||||
return &MergedTable{table: finished}, stats, err
|
||||
}
|
||||
|
||||
if mergeOpts.IsCherryPick && !schema.SchemasAreEqual(tm.leftSch, tm.rightSch) {
|
||||
@@ -127,16 +136,32 @@ func (rm *RootMerger) MergeTable(ctx context.Context, tblName string, opts edito
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if schConflicts.Count() != 0 {
|
||||
// error on any schema conflicts that we can't resolve
|
||||
return nil, nil, ErrSchemaConflict.New(schConflicts.AsError().Error())
|
||||
if schConflicts.Count() > 0 {
|
||||
if !mergeOpts.KeepSchemaConflicts {
|
||||
return nil, nil, schConflicts
|
||||
}
|
||||
// handle schema conflicts above
|
||||
mt := &MergedTable{
|
||||
table: tm.leftTbl,
|
||||
conflict: schConflicts,
|
||||
}
|
||||
stats = &MergeStats{
|
||||
Operation: TableModified,
|
||||
SchemaConflicts: schConflicts.Count(),
|
||||
}
|
||||
return mt, stats, nil
|
||||
}
|
||||
|
||||
var tbl *doltdb.Table
|
||||
if types.IsFormat_DOLT(tm.vrw.Format()) {
|
||||
return mergeProllyTable(ctx, tm, mergeSch)
|
||||
tbl, stats, err = mergeProllyTable(ctx, tm, mergeSch)
|
||||
} else {
|
||||
return mergeNomsTable(ctx, tm, mergeSch, rm.vrw, opts)
|
||||
tbl, stats, err = mergeNomsTable(ctx, tm, mergeSch, rm.vrw, opts)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &MergedTable{table: tbl}, stats, nil
|
||||
}
|
||||
|
||||
func (rm *RootMerger) makeTableMerger(ctx context.Context, tblName string) (*TableMerger, error) {
|
||||
|
||||
@@ -38,6 +38,18 @@ const (
|
||||
DeletedCheckCollision
|
||||
)
|
||||
|
||||
// todo: link to docs explaining how to resolve schema conflicts.
|
||||
func SchemaConflictErr(cc ...SchemaConflict) error {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("merge aborted: schema conflict found for tables: \n")
|
||||
for i := range cc {
|
||||
sb.WriteRune('\t')
|
||||
sb.WriteString(cc[i].TableName)
|
||||
sb.WriteRune('\n')
|
||||
}
|
||||
return errors.New(sb.String())
|
||||
}
|
||||
|
||||
type SchemaConflict struct {
|
||||
TableName string
|
||||
ColConflicts []ColConflict
|
||||
@@ -45,15 +57,17 @@ type SchemaConflict struct {
|
||||
ChkConflicts []ChkConflict
|
||||
}
|
||||
|
||||
var EmptySchConflicts = SchemaConflict{}
|
||||
var _ error = SchemaConflict{}
|
||||
|
||||
func (sc SchemaConflict) Count() int {
|
||||
return len(sc.ColConflicts) + len(sc.IdxConflicts) + len(sc.ChkConflicts)
|
||||
}
|
||||
|
||||
func (sc SchemaConflict) AsError() error {
|
||||
func (sc SchemaConflict) Error() string {
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("schema conflicts for table %s:\n", sc.TableName))
|
||||
b.WriteString("merge aborted: schema conflict found for table ")
|
||||
b.WriteString(sc.TableName)
|
||||
b.WriteString("\n please resolve schema conflicts before merging")
|
||||
for _, c := range sc.ColConflicts {
|
||||
b.WriteString(fmt.Sprintf("\t%s\n", c.String()))
|
||||
}
|
||||
@@ -63,7 +77,7 @@ func (sc SchemaConflict) AsError() error {
|
||||
for _, c := range sc.ChkConflicts {
|
||||
b.WriteString(fmt.Sprintf("\t%s\n", c.String()))
|
||||
}
|
||||
return fmt.Errorf(b.String())
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type ColConflict struct {
|
||||
@@ -136,7 +150,7 @@ func SchemaMerge(ctx context.Context, format *types.NomsBinFormat, ourSch, their
|
||||
var mergedCC *schema.ColCollection
|
||||
mergedCC, sc.ColConflicts, err = mergeColumns(ourSch.GetAllCols(), theirSch.GetAllCols(), ancSch.GetAllCols())
|
||||
if err != nil {
|
||||
return nil, EmptySchConflicts, err
|
||||
return nil, SchemaConflict{}, err
|
||||
}
|
||||
if len(sc.ColConflicts) > 0 {
|
||||
return nil, sc, nil
|
||||
@@ -168,7 +182,7 @@ func SchemaMerge(ctx context.Context, format *types.NomsBinFormat, ourSch, their
|
||||
var mergedChks []schema.Check
|
||||
mergedChks, sc.ChkConflicts, err = mergeChecks(ctx, ourSch.Checks(), theirSch.Checks(), ancSch.Checks())
|
||||
if err != nil {
|
||||
return nil, EmptySchConflicts, err
|
||||
return nil, SchemaConflict{}, err
|
||||
}
|
||||
if len(sc.ChkConflicts) > 0 {
|
||||
return nil, sc, nil
|
||||
|
||||
@@ -28,6 +28,20 @@ type MergeStats struct {
|
||||
Adds int
|
||||
Deletes int
|
||||
Modifications int
|
||||
Conflicts int
|
||||
DataConflicts int
|
||||
SchemaConflicts int
|
||||
ConstraintViolations int
|
||||
}
|
||||
|
||||
func (ms *MergeStats) HasConflicts() bool {
|
||||
return ms.DataConflicts > 0 ||
|
||||
ms.SchemaConflicts > 0
|
||||
}
|
||||
|
||||
func (ms *MergeStats) HasConstraintViolations() bool {
|
||||
return ms.ConstraintViolations > 0
|
||||
}
|
||||
|
||||
func (ms *MergeStats) HasArtifacts() bool {
|
||||
return ms.HasConflicts() || ms.HasConstraintViolations()
|
||||
}
|
||||
|
||||
@@ -58,14 +58,6 @@ var colColl = schema.NewColCollection(
|
||||
)
|
||||
var sch = schema.MustSchemaFromCols(colColl)
|
||||
|
||||
var indexSchema schema.Index
|
||||
var compositeIndexSchema schema.Index
|
||||
|
||||
func init() {
|
||||
indexSchema, _ = sch.Indexes().AddIndexByColTags("idx_col1", []uint64{col1Tag}, nil, schema.IndexProperties{IsUnique: false, Comment: ""})
|
||||
compositeIndexSchema, _ = sch.Indexes().AddIndexByColTags("idx_col1_col2", []uint64{col1Tag, col2Tag}, nil, schema.IndexProperties{IsUnique: false, Comment: ""})
|
||||
}
|
||||
|
||||
type rowV struct {
|
||||
col1, col2 int
|
||||
}
|
||||
@@ -329,10 +321,10 @@ func TestMergeCommits(t *testing.T) {
|
||||
expected, err = expected.SetArtifacts(context.Background(), durable.ArtifactIndexFromProllyMap(expectedArtifacts))
|
||||
require.NoError(t, err)
|
||||
|
||||
mergedRows, err := merged.GetRowData(context.Background())
|
||||
mergedRows, err := merged.table.GetRowData(context.Background())
|
||||
assert.NoError(t, err)
|
||||
|
||||
artIdx, err := merged.GetArtifacts(context.Background())
|
||||
artIdx, err := merged.table.GetArtifacts(context.Background())
|
||||
require.NoError(t, err)
|
||||
artifacts := durable.ProllyMapFromArtifactIndex(artIdx)
|
||||
MustEqualArtifactMap(t, expectedArtifacts, artifacts)
|
||||
@@ -340,14 +332,14 @@ func TestMergeCommits(t *testing.T) {
|
||||
MustEqualProlly(t, tableName, durable.ProllyMapFromIndex(expectedRows), durable.ProllyMapFromIndex(mergedRows))
|
||||
|
||||
for _, index := range sch.Indexes().AllIndexes() {
|
||||
mergedIndexRows, err := merged.GetIndexRowData(context.Background(), index.Name())
|
||||
mergedIndexRows, err := merged.table.GetIndexRowData(context.Background(), index.Name())
|
||||
require.NoError(t, err)
|
||||
expectedIndexRows, err := expected.GetIndexRowData(context.Background(), index.Name())
|
||||
require.NoError(t, err)
|
||||
MustEqualProlly(t, index.Name(), durable.ProllyMapFromIndex(expectedIndexRows), durable.ProllyMapFromIndex(mergedIndexRows))
|
||||
}
|
||||
|
||||
h, err := merged.HashOf()
|
||||
h, err := merged.table.HashOf()
|
||||
require.NoError(t, err)
|
||||
eh, err := expected.HashOf()
|
||||
require.NoError(t, err)
|
||||
@@ -385,9 +377,9 @@ func TestNomsMergeCommits(t *testing.T) {
|
||||
expected, err = expected.SetConflicts(context.Background(), conflictSchema, durable.ConflictIndexFromNomsMap(expectedConflicts, vrw))
|
||||
assert.NoError(t, err)
|
||||
|
||||
mergedRows, err := merged.GetNomsRowData(context.Background())
|
||||
mergedRows, err := merged.table.GetNomsRowData(context.Background())
|
||||
assert.NoError(t, err)
|
||||
_, confIdx, err := merged.GetConflicts(context.Background())
|
||||
_, confIdx, err := merged.table.GetConflicts(context.Background())
|
||||
assert.NoError(t, err)
|
||||
conflicts := durable.NomsMapFromConflictIndex(confIdx)
|
||||
|
||||
@@ -399,7 +391,7 @@ func TestNomsMergeCommits(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, index := range sch.Indexes().AllIndexes() {
|
||||
mergedIndexRows, err := merged.GetNomsIndexRowData(context.Background(), index.Name())
|
||||
mergedIndexRows, err := merged.table.GetNomsIndexRowData(context.Background(), index.Name())
|
||||
assert.NoError(t, err)
|
||||
expectedIndexRows, err := expected.GetNomsIndexRowData(context.Background(), index.Name())
|
||||
assert.NoError(t, err)
|
||||
@@ -410,7 +402,7 @@ func TestNomsMergeCommits(t *testing.T) {
|
||||
mustString(types.EncodedValue(context.Background(), mergedIndexRows)))
|
||||
}
|
||||
|
||||
h, err := merged.HashOf()
|
||||
h, err := merged.table.HashOf()
|
||||
assert.NoError(t, err)
|
||||
eh, err := expected.HashOf()
|
||||
assert.NoError(t, err)
|
||||
@@ -692,7 +684,7 @@ func calcExpectedStats(t *testing.T) *MergeStats {
|
||||
// (DeleteAction, UpdateAction),
|
||||
// (UpdateAction, UpdateAction) with conflict,
|
||||
// (InsertAction, InsertAction) with conflict
|
||||
s.Conflicts++
|
||||
s.DataConflicts++
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -68,16 +68,19 @@ func Revert(ctx context.Context, ddb *doltdb.DoltDB, root *doltdb.RootValue, hea
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
root, _, err = MergeRoots(ctx, root, theirRoot, baseRoot, parentCM, baseCommit, opts, MergeOpts{IsCherryPick: false})
|
||||
var result *Result
|
||||
result, err = MergeRoots(ctx, root, theirRoot, baseRoot, parentCM, baseCommit, opts, MergeOpts{IsCherryPick: false})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if ok, err := root.HasConflicts(ctx); err != nil {
|
||||
root = result.Root
|
||||
|
||||
if ok, err := result.Root.HasConflicts(ctx); err != nil {
|
||||
return nil, "", err
|
||||
} else if ok {
|
||||
return nil, "", fmt.Errorf("revert currently does not handle conflicts")
|
||||
}
|
||||
if ok, err := root.HasConstraintViolations(ctx); err != nil {
|
||||
if ok, err := result.Root.HasConstraintViolations(ctx); err != nil {
|
||||
return nil, "", err
|
||||
} else if ok {
|
||||
return nil, "", fmt.Errorf("revert currently does not handle constraint violations")
|
||||
|
||||
@@ -39,9 +39,8 @@ type testCommand struct {
|
||||
args args
|
||||
}
|
||||
|
||||
func (tc testCommand) exec(t *testing.T, ctx context.Context, dEnv *env.DoltEnv) {
|
||||
exitCode := tc.cmd.Exec(ctx, tc.cmd.Name(), tc.args, dEnv, nil)
|
||||
require.Equal(t, 0, exitCode)
|
||||
func (tc testCommand) exec(t *testing.T, ctx context.Context, dEnv *env.DoltEnv) int {
|
||||
return tc.cmd.Exec(ctx, tc.cmd.Name(), tc.args, dEnv, nil)
|
||||
}
|
||||
|
||||
type args []string
|
||||
@@ -562,10 +561,12 @@ func testMergeSchemas(t *testing.T, test mergeSchemaTest) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, c := range setupCommon {
|
||||
c.exec(t, ctx, dEnv)
|
||||
exit := c.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
for _, c := range test.setup {
|
||||
c.exec(t, ctx, dEnv)
|
||||
exit := c.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
|
||||
// assert that we're on main
|
||||
@@ -605,13 +606,15 @@ func testMergeSchemasWithConflicts(t *testing.T, test mergeSchemaConflictTest) {
|
||||
defer dEnv.DoltDB.Close()
|
||||
ctx := context.Background()
|
||||
for _, c := range setupCommon {
|
||||
c.exec(t, ctx, dEnv)
|
||||
exit := c.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
|
||||
ancSch := getSchema(t, dEnv)
|
||||
|
||||
for _, c := range test.setup {
|
||||
c.exec(t, ctx, dEnv)
|
||||
exit := c.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
|
||||
// assert that we're on main
|
||||
@@ -660,14 +663,16 @@ func testMergeForeignKeys(t *testing.T, test mergeForeignKeyTest) {
|
||||
defer dEnv.DoltDB.Close()
|
||||
ctx := context.Background()
|
||||
for _, c := range setupForeignKeyTests {
|
||||
c.exec(t, ctx, dEnv)
|
||||
exit := c.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
|
||||
ancRoot, err := dEnv.WorkingRoot(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, c := range test.setup {
|
||||
c.exec(t, ctx, dEnv)
|
||||
exit := c.exec(t, ctx, dEnv)
|
||||
require.Equal(t, 0, exit)
|
||||
}
|
||||
|
||||
// assert that we're on main
|
||||
@@ -687,10 +692,10 @@ func testMergeForeignKeys(t *testing.T, test mergeForeignKeyTest) {
|
||||
|
||||
opts := editor.TestEditorOptions(dEnv.DoltDB.ValueReadWriter())
|
||||
mo := merge.MergeOpts{IsCherryPick: false}
|
||||
mergedRoot, _, err := merge.MergeRoots(ctx, mainRoot, otherRoot, ancRoot, mainWS, otherWS, opts, mo)
|
||||
result, err := merge.MergeRoots(ctx, mainRoot, otherRoot, ancRoot, mainWS, otherWS, opts, mo)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fkc, err := mergedRoot.GetForeignKeyCollection(ctx)
|
||||
fkc, err := result.Root.GetForeignKeyCollection(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.fkColl.Count(), fkc.Count())
|
||||
|
||||
|
||||
@@ -494,7 +494,7 @@ func testSchemaMergeHelper(t *testing.T, tests []schemaMergeTest, flipSides bool
|
||||
var eo editor.Options
|
||||
eo = eo.WithDeaf(editor.NewInMemDeaf(a.VRW()))
|
||||
// attempt merge before skipping to assert no panics
|
||||
root, _, err := merge.MergeRoots(ctx, l, r, a, rootish{r}, rootish{a}, eo, mo)
|
||||
result, err := merge.MergeRoots(ctx, l, r, a, rootish{r}, rootish{a}, eo, mo)
|
||||
maybeSkip(t, a.VRW().Format(), test, flipSides)
|
||||
|
||||
if test.conflict {
|
||||
@@ -504,7 +504,7 @@ func testSchemaMergeHelper(t *testing.T, tests []schemaMergeTest, flipSides bool
|
||||
require.NoError(t, err)
|
||||
exp, err := m.MapTableHashes(ctx)
|
||||
assert.NoError(t, err)
|
||||
act, err := root.MapTableHashes(ctx)
|
||||
act, err := result.Root.MapTableHashes(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(exp), len(act))
|
||||
|
||||
@@ -369,6 +369,8 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds
|
||||
dt, found = dtables.NewTableOfTablesInConflict(ctx, db.name, db.ddb), true
|
||||
case doltdb.TableOfTablesWithViolationsName:
|
||||
dt, found = dtables.NewTableOfTablesConstraintViolations(ctx, root), true
|
||||
case doltdb.SchemaConflictsTableName:
|
||||
dt, found = dtables.NewSchemaConflictsTable(ctx, db.name, db.ddb), true
|
||||
case doltdb.BranchesTableName:
|
||||
dt, found = dtables.NewBranchesTable(ctx, db.ddb), true
|
||||
case doltdb.RemoteBranchesTableName:
|
||||
@@ -386,7 +388,11 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds
|
||||
map[string]env.Remote{},
|
||||
map[string]env.BranchConfig{},
|
||||
map[string]env.Remote{})
|
||||
dt, found = dtables.NewStatusTable(ctx, db.name, db.ddb, adapter), true
|
||||
ws, err := sess.WorkingSet(ctx, db.name)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
dt, found = dtables.NewStatusTable(ctx, db.name, db.ddb, ws, adapter), true
|
||||
case doltdb.MergeStatusTableName:
|
||||
dt, found = dtables.NewMergeStatusTable(db.name), true
|
||||
case doltdb.TagsTableName:
|
||||
|
||||
@@ -170,15 +170,19 @@ func cherryPick(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots,
|
||||
}
|
||||
|
||||
// use parent of cherry-pick as ancestor root to merge
|
||||
mo := merge.MergeOpts{IsCherryPick: true}
|
||||
mergedRoot, mergeStats, err := merge.MergeRoots(ctx, roots.Working, cherryRoot, parentRoot, cherryCommit, parentCommit, dbState.EditOpts(), mo)
|
||||
|
||||
mo := merge.MergeOpts{
|
||||
IsCherryPick: true,
|
||||
KeepSchemaConflicts: false,
|
||||
}
|
||||
result, err := merge.MergeRoots(ctx, roots.Working, cherryRoot, parentRoot, cherryCommit, parentCommit, dbState.EditOpts(), mo)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var tablesWithConflict []string
|
||||
for tbl, stats := range mergeStats {
|
||||
if stats.Conflicts > 0 {
|
||||
for tbl, stats := range result.Stats {
|
||||
if stats.HasConflicts() {
|
||||
tablesWithConflict = append(tablesWithConflict, tbl)
|
||||
}
|
||||
}
|
||||
@@ -188,7 +192,7 @@ func cherryPick(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots,
|
||||
return nil, "", fmt.Errorf("conflicts in table {'%s'}", tblNames)
|
||||
}
|
||||
|
||||
workingRootHash, err = mergedRoot.HashOf()
|
||||
workingRootHash, err = result.Root.HashOf()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -202,5 +206,5 @@ func cherryPick(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots,
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return mergedRoot, cherryCommitMeta.Description, nil
|
||||
return result.Root, cherryCommitMeta.Description, nil
|
||||
}
|
||||
|
||||
@@ -339,7 +339,48 @@ func clearTableAndUpdateRoot(ctx *sql.Context, root *doltdb.RootValue, tbl *dolt
|
||||
return newRoot, nil
|
||||
}
|
||||
|
||||
func ResolveConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root *doltdb.RootValue, dbName string, ours bool, tblNames []string) error {
|
||||
func ResolveSchemaConflicts(ctx *sql.Context, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, resolveOurs bool, tables []string) (*doltdb.WorkingSet, error) {
|
||||
if !ws.MergeActive() {
|
||||
return ws, nil // no schema conflicts
|
||||
}
|
||||
|
||||
tblSet := set.NewStrSet(tables)
|
||||
updates := make(map[string]*doltdb.Table)
|
||||
err := ws.MergeState().IterSchemaConflicts(ctx, ddb, func(table string, conflict doltdb.SchemaConflict) error {
|
||||
if !tblSet.Contains(table) {
|
||||
return nil
|
||||
}
|
||||
ours, theirs := conflict.GetConflictingTables()
|
||||
if resolveOurs {
|
||||
updates[table] = ours
|
||||
} else {
|
||||
updates[table] = theirs
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := ws.WorkingRoot()
|
||||
for name, tbl := range updates {
|
||||
if root, err = root.PutTable(ctx, name, tbl); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// clear resolved schema conflicts
|
||||
var unmerged []string
|
||||
for _, tbl := range ws.MergeState().TablesWithSchemaConflicts() {
|
||||
if tblSet.Contains(tbl) {
|
||||
continue
|
||||
}
|
||||
unmerged = append(unmerged, tbl)
|
||||
}
|
||||
return ws.WithWorkingRoot(root).WithUnmergableTables(unmerged), nil
|
||||
}
|
||||
|
||||
func ResolveDataConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root *doltdb.RootValue, dbName string, ours bool, tblNames []string) error {
|
||||
for _, tblName := range tblNames {
|
||||
tbl, ok, err := root.GetTable(ctx, tblName)
|
||||
if err != nil {
|
||||
@@ -352,7 +393,7 @@ func ResolveConflicts(ctx *sql.Context, dSess *dsess.DoltSession, root *doltdb.R
|
||||
if has, err := tbl.HasConflicts(ctx); err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
|
||||
sch, err := tbl.GetSchema(ctx)
|
||||
@@ -413,9 +454,14 @@ func DoDoltConflictsResolve(ctx *sql.Context, args []string) (int, error) {
|
||||
}
|
||||
|
||||
dSess := dsess.DSessFromSess(ctx.Session)
|
||||
roots, ok := dSess.GetRoots(ctx, dbName)
|
||||
if !ok {
|
||||
return 1, fmt.Errorf("Could not load database %s", dbName)
|
||||
ws, err := dSess.WorkingSet(ctx, dbName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ddb, _ := dSess.GetDoltDB(ctx, dbName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ours := apr.Contains(cli.OursFlag)
|
||||
@@ -431,17 +477,21 @@ func DoDoltConflictsResolve(ctx *sql.Context, args []string) (int, error) {
|
||||
}
|
||||
|
||||
// get all tables in conflict
|
||||
root := roots.Working
|
||||
tbls := apr.Args
|
||||
if len(tbls) == 1 && tbls[0] == "." {
|
||||
if allTables, err := root.TablesInConflict(ctx); err != nil {
|
||||
return 1, err
|
||||
} else {
|
||||
tbls = allTables
|
||||
all, err := ws.WorkingRoot().GetTableNames(ctx)
|
||||
if err != nil {
|
||||
return 1, nil
|
||||
}
|
||||
tbls = all
|
||||
}
|
||||
|
||||
err = ResolveConflicts(ctx, dSess, root, dbName, ours, tbls)
|
||||
ws, err = ResolveSchemaConflicts(ctx, ddb, ws, ours, tbls)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
err = ResolveDataConflicts(ctx, dSess, ws.WorkingRoot(), dbName, ours, tbls)
|
||||
if err != nil {
|
||||
return 1, err
|
||||
}
|
||||
|
||||
@@ -243,8 +243,7 @@ func abortMerge(ctx *sql.Context, workingSet *doltdb.WorkingSet, roots doltdb.Ro
|
||||
}
|
||||
|
||||
func executeMerge(ctx *sql.Context, squash bool, head, cm *doltdb.Commit, cmSpec string, ws *doltdb.WorkingSet, opts editor.Options) (*doltdb.WorkingSet, error) {
|
||||
mergeRoot, mergeStats, err := merge.MergeCommits(ctx, head, cm, opts)
|
||||
|
||||
result, err := merge.MergeCommits(ctx, head, cm, opts)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case doltdb.ErrUpToDate:
|
||||
@@ -255,8 +254,7 @@ func executeMerge(ctx *sql.Context, squash bool, head, cm *doltdb.Commit, cmSpec
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return mergeRootToWorking(squash, ws, mergeRoot, cm, cmSpec, mergeStats)
|
||||
return mergeRootToWorking(squash, ws, result, cm, cmSpec)
|
||||
}
|
||||
|
||||
func executeFFMerge(ctx *sql.Context, dbName string, squash bool, ws *doltdb.WorkingSet, dbData env.DbData, cm2 *doltdb.Commit) (*doltdb.WorkingSet, error) {
|
||||
@@ -308,8 +306,9 @@ func executeNoFFMerge(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &merge.Result{Root: mergeRoot, Stats: make(map[string]*merge.MergeStats)}
|
||||
|
||||
ws, err = mergeRootToWorking(false, ws, mergeRoot, spec.MergeC, spec.MergeCSpecStr, map[string]*merge.MergeStats{})
|
||||
ws, err = mergeRootToWorking(false, ws, result, spec.MergeC, spec.MergeCSpecStr)
|
||||
if err != nil {
|
||||
// This error is recoverable, so we return a working set value along with the error
|
||||
return ws, err
|
||||
@@ -399,23 +398,21 @@ func createMergeSpec(ctx *sql.Context, sess *dsess.DoltSession, dbName string, a
|
||||
func mergeRootToWorking(
|
||||
squash bool,
|
||||
ws *doltdb.WorkingSet,
|
||||
mergedRoot *doltdb.RootValue,
|
||||
merged *merge.Result,
|
||||
cm2 *doltdb.Commit,
|
||||
cm2Spec string,
|
||||
mergeStats map[string]*merge.MergeStats,
|
||||
) (*doltdb.WorkingSet, error) {
|
||||
|
||||
workingRoot := mergedRoot
|
||||
if !squash {
|
||||
if !squash || merged.HasSchemaConflicts() {
|
||||
ws = ws.StartMerge(cm2, cm2Spec)
|
||||
tt := merge.SchemaConflictTableNames(merged.SchemaConflicts)
|
||||
ws = ws.WithUnmergableTables(tt)
|
||||
}
|
||||
|
||||
ws = ws.WithWorkingRoot(workingRoot).WithStagedRoot(workingRoot)
|
||||
if checkForConflicts(mergeStats) || checkForViolations(mergeStats) {
|
||||
ws = ws.WithWorkingRoot(merged.Root).WithStagedRoot(merged.Root)
|
||||
if merged.HasMergeArtifacts() {
|
||||
// this error is recoverable in-session, so we return the new ws along with the error
|
||||
return ws, doltdb.ErrUnresolvedConflictsOrViolations
|
||||
}
|
||||
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
@@ -437,22 +434,3 @@ func checkForUncommittedChanges(ctx *sql.Context, root *doltdb.RootValue, headRo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 checkForViolations(tblToStats map[string]*merge.MergeStats) bool {
|
||||
for _, stats := range tblToStats {
|
||||
if stats.ConstraintViolations > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -586,7 +586,7 @@ func (d *DoltSession) NewPendingCommit(ctx *sql.Context, dbName string, roots do
|
||||
roots.Head = newRoots.Head
|
||||
}
|
||||
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, sessionState.WorkingSet.MergeActive(), mergeParentCommits, sessionState.dbData.Ddb, props)
|
||||
pendingCommit, err := actions.GetCommitStaged(ctx, roots, sessionState.WorkingSet, mergeParentCommits, sessionState.dbData.Ddb, props)
|
||||
if err != nil {
|
||||
if props.Amend {
|
||||
_, err = actions.ResetSoftToRef(ctx, sessionState.dbData, headHash.String())
|
||||
|
||||
@@ -184,7 +184,7 @@ func doltCommit(ctx *sql.Context,
|
||||
// updates). The merged root value becomes our new Staged root value which
|
||||
// is the value which we are trying to commit.
|
||||
start := time.Now()
|
||||
pending.Roots.Staged, _, err = merge.MergeRoots(
|
||||
result, err := merge.MergeRoots(
|
||||
ctx,
|
||||
pending.Roots.Staged,
|
||||
curRootVal,
|
||||
@@ -196,6 +196,7 @@ func doltCommit(ctx *sql.Context,
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pending.Roots.Staged = result.Root
|
||||
|
||||
// We also need to update the working set to reflect the new staged root value
|
||||
workingSet = workingSet.WithStagedRoot(pending.Roots.Staged)
|
||||
@@ -321,7 +322,7 @@ func (tx *DoltTransaction) mergeRoots(
|
||||
) (*doltdb.WorkingSet, error) {
|
||||
|
||||
if !rootsEqual(existingWorkingSet.WorkingRoot(), workingSet.WorkingRoot()) {
|
||||
mergedRoot, _, err := merge.MergeRoots(
|
||||
result, err := merge.MergeRoots(
|
||||
ctx,
|
||||
existingWorkingSet.WorkingRoot(),
|
||||
workingSet.WorkingRoot(),
|
||||
@@ -333,11 +334,11 @@ func (tx *DoltTransaction) mergeRoots(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workingSet = workingSet.WithWorkingRoot(mergedRoot)
|
||||
workingSet = workingSet.WithWorkingRoot(result.Root)
|
||||
}
|
||||
|
||||
if !rootsEqual(existingWorkingSet.StagedRoot(), workingSet.StagedRoot()) {
|
||||
mergedRoot, _, err := merge.MergeRoots(
|
||||
result, err := merge.MergeRoots(
|
||||
ctx,
|
||||
existingWorkingSet.StagedRoot(),
|
||||
workingSet.StagedRoot(),
|
||||
@@ -349,7 +350,7 @@ func (tx *DoltTransaction) mergeRoots(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workingSet = workingSet.WithStagedRoot(mergedRoot)
|
||||
workingSet = workingSet.WithStagedRoot(result.Root)
|
||||
}
|
||||
|
||||
return workingSet, nil
|
||||
|
||||
@@ -88,7 +88,7 @@ type MergeStatusIter struct {
|
||||
func newMergeStatusItr(ctx context.Context, ws *doltdb.WorkingSet) (*MergeStatusIter, error) {
|
||||
wr := ws.WorkingRoot()
|
||||
|
||||
inConflict, err := wr.TablesInConflict(ctx)
|
||||
inConflict, err := wr.TablesWithDataConflicts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,8 +98,14 @@ func newMergeStatusItr(ctx context.Context, ws *doltdb.WorkingSet) (*MergeStatus
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var schConflicts []string
|
||||
if ws.MergeActive() {
|
||||
schConflicts = ws.MergeState().TablesWithSchemaConflicts()
|
||||
}
|
||||
|
||||
unmergedTblNames := set.NewStrSet(inConflict)
|
||||
unmergedTblNames.Add(tblsWithViolations...)
|
||||
unmergedTblNames.Add(schConflicts...)
|
||||
|
||||
var sourceCommitSpecStr *string
|
||||
var sourceCommitHash *string
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
// Copyright 2020 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 dtables
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/go-mysql-server/sql/types"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlutil"
|
||||
)
|
||||
|
||||
var _ sql.Table = (*SchemaConflictsTable)(nil)
|
||||
|
||||
// SchemaConflictsTable is a sql.Table implementation that implements a system table which shows the current conflicts
|
||||
type SchemaConflictsTable struct {
|
||||
dbName string
|
||||
ddb *doltdb.DoltDB
|
||||
}
|
||||
|
||||
// NewSchemaConflictsTable creates a SchemaConflictsTable
|
||||
func NewSchemaConflictsTable(_ *sql.Context, dbName string, ddb *doltdb.DoltDB) sql.Table {
|
||||
return &SchemaConflictsTable{dbName: dbName, ddb: ddb}
|
||||
}
|
||||
|
||||
// Name is a sql.Table interface function which returns the name of the table which is defined by the constant
|
||||
// SchemaConflictsTableName
|
||||
func (dt *SchemaConflictsTable) Name() string {
|
||||
return doltdb.SchemaConflictsTableName
|
||||
}
|
||||
|
||||
// String is a sql.Table interface function which returns the name of the table which is defined by the constant
|
||||
// SchemaConflictsTableName
|
||||
func (dt *SchemaConflictsTable) String() string {
|
||||
return doltdb.SchemaConflictsTableName
|
||||
}
|
||||
|
||||
// Schema is a sql.Table interface function that gets the sql.Schema of the log system table.
|
||||
func (dt *SchemaConflictsTable) Schema() sql.Schema {
|
||||
return []*sql.Column{
|
||||
{Name: "table_name", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: true},
|
||||
{Name: "base_schema", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false},
|
||||
{Name: "our_schema", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false},
|
||||
{Name: "their_schema", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false},
|
||||
{Name: "description", Type: types.Text, Source: doltdb.SchemaConflictsTableName, PrimaryKey: false},
|
||||
}
|
||||
}
|
||||
|
||||
// Collation implements the sql.Table interface.
|
||||
func (dt *SchemaConflictsTable) Collation() sql.CollationID {
|
||||
return sql.Collation_Default
|
||||
}
|
||||
|
||||
// Partitions is a sql.Table interface function that returns a partition of the data. Conflict data for all tables exists in a single partition.
|
||||
func (dt *SchemaConflictsTable) Partitions(ctx *sql.Context) (sql.PartitionIter, error) {
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
ws, err := sess.WorkingSet(ctx, dt.dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbd, _ := sess.GetDbData(ctx, dt.dbName)
|
||||
|
||||
if ws.MergeState() == nil || !ws.MergeState().HasSchemaConflicts() {
|
||||
return sql.PartitionsToPartitionIter(), nil
|
||||
}
|
||||
|
||||
head, err := sess.GetHeadCommit(ctx, dt.dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sql.PartitionsToPartitionIter(schemaConflictsPartition{
|
||||
state: ws.MergeState(),
|
||||
head: head,
|
||||
ddb: dbd.Ddb,
|
||||
}), nil
|
||||
}
|
||||
|
||||
// PartitionRows is a sql.Table interface function that gets a row iterator for a partition
|
||||
func (dt *SchemaConflictsTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) {
|
||||
p, ok := part.(schemaConflictsPartition)
|
||||
if !ok {
|
||||
return nil, errors.New("unexpected partition for schema conflicts table")
|
||||
}
|
||||
|
||||
base, err := doltdb.GetCommitAncestor(ctx, p.head, p.state.Commit())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseRoot, err := base.GetRootValue(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var conflicts []schemaConflict
|
||||
err = p.state.IterSchemaConflicts(ctx, p.ddb, func(table string, cnf doltdb.SchemaConflict) error {
|
||||
c, err := newSchemaConflict(ctx, table, baseRoot, cnf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conflicts = append(conflicts, c)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &schemaConflictsIter{
|
||||
conflicts: conflicts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type schemaConflictsPartition struct {
|
||||
state *doltdb.MergeState
|
||||
head *doltdb.Commit
|
||||
ddb *doltdb.DoltDB
|
||||
}
|
||||
|
||||
func (p schemaConflictsPartition) Key() []byte {
|
||||
return []byte(doltdb.SchemaConflictsTableName)
|
||||
}
|
||||
|
||||
type schemaConflict struct {
|
||||
table string
|
||||
baseSch string
|
||||
ourSch string
|
||||
theirSch string
|
||||
description string
|
||||
}
|
||||
|
||||
func newSchemaConflict(ctx context.Context, table string, br *doltdb.RootValue, c doltdb.SchemaConflict) (schemaConflict, error) {
|
||||
base, err := getCreateTableStatementFromRoot(ctx, table, br)
|
||||
if err != nil {
|
||||
return schemaConflict{}, err
|
||||
}
|
||||
|
||||
ours, err := getCreateTableStatement(table, c.ToSch, c.ToFks, c.ToParentSchemas)
|
||||
if err != nil {
|
||||
return schemaConflict{}, err
|
||||
}
|
||||
|
||||
theirs, err := getCreateTableStatement(table, c.FromSch, c.FromFks, c.FromParentSchemas)
|
||||
if err != nil {
|
||||
return schemaConflict{}, err
|
||||
}
|
||||
|
||||
return schemaConflict{
|
||||
table: table,
|
||||
baseSch: base,
|
||||
ourSch: ours,
|
||||
theirSch: theirs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getCreateTableStatementFromRoot(ctx context.Context, table string, root *doltdb.RootValue) (string, error) {
|
||||
schemas, err := root.GetAllSchemas(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sch := schemas[table]
|
||||
|
||||
fkc, err := root.GetForeignKeyCollection(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fks, _ := fkc.KeysForTable(table)
|
||||
|
||||
return getCreateTableStatement(table, sch, fks, schemas)
|
||||
}
|
||||
|
||||
func getCreateTableStatement(table string, sch schema.Schema, fks []doltdb.ForeignKey, parents map[string]schema.Schema) (string, error) {
|
||||
pkSch, err := sqlutil.FromDoltSchema(table, sch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return diff.GenerateCreateTableStatement(table, sch, pkSch, fks, parents)
|
||||
}
|
||||
|
||||
type schemaConflictsIter struct {
|
||||
conflicts []schemaConflict
|
||||
baseSchemas map[string]schema.Schema
|
||||
baseCommit *doltdb.Commit
|
||||
}
|
||||
|
||||
func (it *schemaConflictsIter) Next(ctx *sql.Context) (sql.Row, error) {
|
||||
if len(it.conflicts) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
c := it.conflicts[0] // pop next conflict
|
||||
it.conflicts = it.conflicts[1:]
|
||||
return sql.NewRow(c.table, c.baseSch, c.ourSch, c.theirSch, c.description), nil
|
||||
}
|
||||
|
||||
func (it *schemaConflictsIter) Close(ctx *sql.Context) error {
|
||||
it.conflicts = nil
|
||||
return nil
|
||||
}
|
||||
@@ -24,13 +24,13 @@ import (
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/merge"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/index"
|
||||
)
|
||||
|
||||
// StatusTable is a sql.Table implementation that implements a system table which shows the dolt branches
|
||||
type StatusTable struct {
|
||||
ddb *doltdb.DoltDB
|
||||
workingSet *doltdb.WorkingSet
|
||||
rootsProvider env.RootsProvider
|
||||
dbName string
|
||||
}
|
||||
@@ -64,20 +64,24 @@ func (s StatusTable) PartitionRows(context *sql.Context, _ sql.Partition) (sql.R
|
||||
}
|
||||
|
||||
// NewStatusTable creates a StatusTable
|
||||
func NewStatusTable(_ *sql.Context, dbName string, ddb *doltdb.DoltDB, rp env.RootsProvider) sql.Table {
|
||||
func NewStatusTable(_ *sql.Context, dbName string, ddb *doltdb.DoltDB, ws *doltdb.WorkingSet, rp env.RootsProvider) sql.Table {
|
||||
return &StatusTable{
|
||||
ddb: ddb,
|
||||
dbName: dbName,
|
||||
workingSet: ws,
|
||||
rootsProvider: rp,
|
||||
}
|
||||
}
|
||||
|
||||
// StatusIter is a sql.RowItr implementation which iterates over each commit as if it's a row in the table.
|
||||
type StatusItr struct {
|
||||
tables []string
|
||||
isStaged []bool
|
||||
statuses []string
|
||||
idx int
|
||||
rows []statusTableRow
|
||||
}
|
||||
|
||||
type statusTableRow struct {
|
||||
tableName string
|
||||
isStaged bool
|
||||
status string
|
||||
}
|
||||
|
||||
func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) {
|
||||
@@ -93,81 +97,78 @@ func newStatusItr(ctx *sql.Context, st *StatusTable) (*StatusItr, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
workingTblsInConflict, _, _, err := merge.GetTablesInConflict(ctx, roots)
|
||||
rows := make([]statusTableRow, 0, len(stagedTables)+len(unstagedTables))
|
||||
for _, td := range stagedTables {
|
||||
rows = append(rows, statusTableRow{
|
||||
tableName: tableName(td),
|
||||
isStaged: true,
|
||||
status: statusString(td),
|
||||
})
|
||||
}
|
||||
for _, td := range unstagedTables {
|
||||
rows = append(rows, statusTableRow{
|
||||
tableName: tableName(td),
|
||||
isStaged: false,
|
||||
status: statusString(td),
|
||||
})
|
||||
}
|
||||
|
||||
if st.workingSet.MergeActive() {
|
||||
ms := st.workingSet.MergeState()
|
||||
for _, tbl := range ms.TablesWithSchemaConflicts() {
|
||||
rows = append(rows, statusTableRow{
|
||||
tableName: tbl,
|
||||
isStaged: false,
|
||||
status: "schema conflict",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cnfTables, err := roots.Working.TablesWithDataConflicts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tLength := len(stagedTables) + len(unstagedTables) + len(workingTblsInConflict)
|
||||
|
||||
tables := make([]string, tLength)
|
||||
isStaged := make([]bool, tLength)
|
||||
statuses := make([]string, tLength)
|
||||
|
||||
itr := &StatusItr{tables: tables, isStaged: isStaged, statuses: statuses, idx: 0}
|
||||
|
||||
idx := handleStagedUnstagedTables(stagedTables, unstagedTables, itr, 0)
|
||||
idx = handleWorkingTablesInConflict(workingTblsInConflict, itr, idx)
|
||||
return itr, nil
|
||||
}
|
||||
|
||||
var tblDiffTypeToLabel = map[diff.TableDiffType]string{
|
||||
diff.ModifiedTable: "modified",
|
||||
diff.RenamedTable: "renamed",
|
||||
diff.RemovedTable: "deleted",
|
||||
diff.AddedTable: "new table",
|
||||
}
|
||||
|
||||
func handleStagedUnstagedTables(staged, unstaged []diff.TableDelta, itr *StatusItr, idx int) int {
|
||||
combined := append(staged, unstaged...)
|
||||
for i, td := range combined {
|
||||
itr.isStaged[idx] = i < len(staged)
|
||||
if td.IsAdd() {
|
||||
itr.tables[idx] = td.CurName()
|
||||
itr.statuses[idx] = tblDiffTypeToLabel[diff.AddedTable]
|
||||
} else if td.IsDrop() {
|
||||
itr.tables[idx] = td.CurName()
|
||||
itr.statuses[idx] = tblDiffTypeToLabel[diff.RemovedTable]
|
||||
} else if td.IsRename() {
|
||||
itr.tables[idx] = fmt.Sprintf("%s -> %s", td.FromName, td.ToName)
|
||||
itr.statuses[idx] = tblDiffTypeToLabel[diff.RenamedTable]
|
||||
} else {
|
||||
itr.tables[idx] = td.CurName()
|
||||
itr.statuses[idx] = tblDiffTypeToLabel[diff.ModifiedTable]
|
||||
}
|
||||
|
||||
idx += 1
|
||||
for _, tbl := range cnfTables {
|
||||
rows = append(rows, statusTableRow{
|
||||
tableName: tbl,
|
||||
status: mergeConflictStatus,
|
||||
})
|
||||
}
|
||||
|
||||
return idx
|
||||
return &StatusItr{rows: rows}, nil
|
||||
}
|
||||
|
||||
func tableName(td diff.TableDelta) string {
|
||||
if td.IsRename() {
|
||||
return fmt.Sprintf("%s -> %s", td.FromName, td.ToName)
|
||||
} else {
|
||||
return td.CurName()
|
||||
}
|
||||
}
|
||||
|
||||
func statusString(td diff.TableDelta) string {
|
||||
if td.IsAdd() {
|
||||
return "new table"
|
||||
} else if td.IsDrop() {
|
||||
return "deleted"
|
||||
} else if td.IsRename() {
|
||||
return "renamed"
|
||||
} else {
|
||||
return "modified"
|
||||
}
|
||||
}
|
||||
|
||||
const mergeConflictStatus = "conflict"
|
||||
|
||||
func handleWorkingTablesInConflict(workingTables []string, itr *StatusItr, idx int) int {
|
||||
for _, tableName := range workingTables {
|
||||
itr.tables[idx] = tableName
|
||||
itr.isStaged[idx] = false
|
||||
itr.statuses[idx] = mergeConflictStatus
|
||||
|
||||
idx += 1
|
||||
}
|
||||
|
||||
return idx
|
||||
}
|
||||
|
||||
// Next retrieves the next row. It will return io.EOF if it's the last row.
|
||||
// After retrieving the last row, Close will be automatically closed.
|
||||
func (itr *StatusItr) Next(*sql.Context) (sql.Row, error) {
|
||||
if itr.idx >= len(itr.tables) {
|
||||
if len(itr.rows) <= 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
defer func() {
|
||||
itr.idx++
|
||||
}()
|
||||
|
||||
return sql.NewRow(itr.tables[itr.idx], itr.isStaged[itr.idx], itr.statuses[itr.idx]), nil
|
||||
row := itr.rows[0]
|
||||
itr.rows = itr.rows[1:]
|
||||
return sql.NewRow(row.tableName, row.isStaged, row.status), nil
|
||||
}
|
||||
|
||||
// Close closes the iterator.
|
||||
|
||||
@@ -122,11 +122,16 @@ func (dt *TableOfTablesInConflict) Partitions(ctx *sql.Context) (sql.PartitionIt
|
||||
}
|
||||
|
||||
root := ws.WorkingRoot()
|
||||
tblNames, err := root.TablesInConflict(ctx)
|
||||
tblNames, err := root.TablesWithDataConflicts(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ws.MergeActive() {
|
||||
schConflicts := ws.MergeState().TablesWithSchemaConflicts()
|
||||
tblNames = append(tblNames, schConflicts...)
|
||||
}
|
||||
|
||||
var partitions []*tableInConflict
|
||||
for _, tblName := range tblNames {
|
||||
tbl, ok, err := root.GetTable(ctx, tblName)
|
||||
|
||||
@@ -1322,6 +1322,11 @@ func TestDoltMergeArtifacts(t *testing.T) {
|
||||
enginetest.TestScript(t, h, script)
|
||||
}()
|
||||
}
|
||||
for _, script := range SchemaConflictScripts {
|
||||
h := newDoltHarness(t)
|
||||
enginetest.TestScript(t, h, script)
|
||||
h.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// these tests are temporary while there is a difference between the old format
|
||||
|
||||
@@ -2881,6 +2881,44 @@ var MergeArtifactsScripts = []queries.ScriptTest{
|
||||
},
|
||||
}
|
||||
|
||||
var SchemaConflictScripts = []queries.ScriptTest{
|
||||
{
|
||||
Name: "divergent type change causes schema conflict",
|
||||
SetUpScript: []string{
|
||||
"create table t (pk int primary key, c0 varchar(20))",
|
||||
"call dolt_commit('-Am', 'added tabele t')",
|
||||
"call dolt_checkout('-b', 'other')",
|
||||
"alter table t modify column c0 int",
|
||||
"call dolt_commit('-am', 'altered t on branch other')",
|
||||
"call dolt_checkout('main')",
|
||||
"alter table t modify column c0 datetime",
|
||||
"call dolt_commit('-am', 'altered t on branch main')",
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "call dolt_merge('other')",
|
||||
Expected: []sql.Row{{0, 1}},
|
||||
},
|
||||
{
|
||||
Query: "select * from dolt_schema_conflicts",
|
||||
Expected: []sql.Row{{
|
||||
"t",
|
||||
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `c0` varchar(20),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
|
||||
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `c0` datetime(6),\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
|
||||
"CREATE TABLE `t` (\n `pk` int NOT NULL,\n `c0` int,\n PRIMARY KEY (`pk`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_bin;",
|
||||
"",
|
||||
}},
|
||||
},
|
||||
{
|
||||
Query: "select * from dolt_status",
|
||||
Expected: []sql.Row{
|
||||
{"t", false, "schema conflict"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// OldFormatMergeConflictsAndCVsScripts tests old format merge behavior
|
||||
// where violations are appended and merges are aborted if there are existing
|
||||
// violations and/or conflicts.
|
||||
@@ -4164,8 +4202,12 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "call dolt_merge('right');",
|
||||
ExpectedErr: merge.ErrSchemaConflict,
|
||||
Query: "call dolt_merge('right');",
|
||||
Expected: []sql.Row{{0, 1}},
|
||||
},
|
||||
{
|
||||
Query: "select table_name from dolt_schema_conflicts",
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4182,8 +4224,12 @@ var ThreeWayMergeWithSchemaChangeTestScripts = []MergeScriptTest{
|
||||
},
|
||||
Assertions: []queries.ScriptTestAssertion{
|
||||
{
|
||||
Query: "call dolt_merge('right');",
|
||||
ExpectedErr: merge.ErrSchemaConflict,
|
||||
Query: "call dolt_merge('right');",
|
||||
Expected: []sql.Row{{0, 1}},
|
||||
},
|
||||
{
|
||||
Query: "select table_name from dolt_schema_conflicts",
|
||||
Expected: []sql.Row{{"t"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -36,6 +36,8 @@ table MergeState {
|
||||
// The spec that was used to identify the commit that we are merging. Optional
|
||||
// for backwards compatibility.
|
||||
from_commit_spec_str:string;
|
||||
|
||||
unmergable_tables:[string];
|
||||
}
|
||||
|
||||
// KEEP THIS IN SYNC WITH fileidentifiers.go
|
||||
|
||||
@@ -164,6 +164,7 @@ type MergeState struct {
|
||||
preMergeWorkingAddr *hash.Hash
|
||||
fromCommitAddr *hash.Hash
|
||||
fromCommitSpec string
|
||||
unmergableTables []string
|
||||
|
||||
nomsMergeStateRef *types.Ref
|
||||
nomsMergeState *types.Struct
|
||||
@@ -255,6 +256,13 @@ func (ms *MergeState) FromCommitSpec(ctx context.Context, vr types.ValueReader)
|
||||
return string(commitSpecStr.(types.String)), nil
|
||||
}
|
||||
|
||||
func (ms *MergeState) UnmergableTables(ctx context.Context, vr types.ValueReader) ([]string, error) {
|
||||
if vr.Format().UsesFlatbuffers() {
|
||||
return ms.unmergableTables, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type dsHead interface {
|
||||
TypeName() string
|
||||
Addr() hash.Hash
|
||||
@@ -369,6 +377,10 @@ func (h serialWorkingSetHead) HeadWorkingSet() (*WorkingSetHead, error) {
|
||||
}
|
||||
*ret.MergeState.preMergeWorkingAddr = hash.New(mergeState.PreWorkingRootAddrBytes())
|
||||
*ret.MergeState.fromCommitAddr = hash.New(mergeState.FromCommitAddrBytes())
|
||||
ret.MergeState.unmergableTables = make([]string, mergeState.UnmergableTablesLength())
|
||||
for i := range ret.MergeState.unmergableTables {
|
||||
ret.MergeState.unmergableTables[i] = string(mergeState.UnmergableTables(i))
|
||||
}
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
@@ -162,10 +162,12 @@ func workingset_flatbuffer(working hash.Hash, staged *hash.Hash, mergeState *Mer
|
||||
prerootaddroff := builder.CreateByteVector((*mergeState.preMergeWorkingAddr)[:])
|
||||
fromaddroff := builder.CreateByteVector((*mergeState.fromCommitAddr)[:])
|
||||
fromspecoff := builder.CreateString(mergeState.fromCommitSpec)
|
||||
unmergableoff := SerializeStringVector(builder, mergeState.unmergableTables)
|
||||
serial.MergeStateStart(builder)
|
||||
serial.MergeStateAddPreWorkingRootAddr(builder, prerootaddroff)
|
||||
serial.MergeStateAddFromCommitAddr(builder, fromaddroff)
|
||||
serial.MergeStateAddFromCommitSpecStr(builder, fromspecoff)
|
||||
serial.MergeStateAddUnmergableTables(builder, unmergableoff)
|
||||
mergeStateOff = serial.MergeStateEnd(builder)
|
||||
}
|
||||
|
||||
@@ -193,12 +195,20 @@ func workingset_flatbuffer(working hash.Hash, staged *hash.Hash, mergeState *Mer
|
||||
return serial.FinishMessage(builder, serial.WorkingSetEnd(builder), []byte(serial.WorkingSetFileID))
|
||||
}
|
||||
|
||||
func NewMergeState(ctx context.Context, vrw types.ValueReadWriter, preMergeWorking types.Ref, commit *Commit, commitSpecStr string) (*MergeState, error) {
|
||||
func NewMergeState(
|
||||
ctx context.Context,
|
||||
vrw types.ValueReadWriter,
|
||||
preMergeWorking types.Ref,
|
||||
commit *Commit,
|
||||
commitSpecStr string,
|
||||
unmergableTables []string,
|
||||
) (*MergeState, error) {
|
||||
if vrw.Format().UsesFlatbuffers() {
|
||||
ms := &MergeState{
|
||||
preMergeWorkingAddr: new(hash.Hash),
|
||||
fromCommitAddr: new(hash.Hash),
|
||||
fromCommitSpec: commitSpecStr,
|
||||
unmergableTables: unmergableTables,
|
||||
}
|
||||
*ms.preMergeWorkingAddr = preMergeWorking.TargetHash()
|
||||
*ms.fromCommitAddr = commit.Addr()
|
||||
|
||||
@@ -493,16 +493,13 @@ teardown() {
|
||||
dolt commit -m "added conflicting test row"
|
||||
dolt checkout main
|
||||
dolt merge test-branch
|
||||
run dolt checkout test
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
run dolt sql -q "select * from test"
|
||||
[[ "$output" =~ \|[[:space:]]+5 ]] || false
|
||||
run dolt conflicts cat test
|
||||
[[ ! "$output" =~ "ours" ]] || false
|
||||
[[ ! "$output" =~ "theirs" ]] || false
|
||||
[[ "$output" =~ "ours" ]] || false
|
||||
[[ "$output" =~ "theirs" ]] || false
|
||||
run dolt status
|
||||
[[ "$output" =~ "All conflicts and constraint violations fixed but you are still merging." ]] || false
|
||||
[[ "$output" =~ "You have unmerged tables" ]] || false
|
||||
run dolt merge --abort
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "" ]
|
||||
|
||||
@@ -208,7 +208,7 @@ SQL
|
||||
dolt sql -q "ALTER TABLE z ADD CONSTRAINT foreign_key1 FOREIGN KEY (c1) references t(c1)"
|
||||
run dolt status
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ "modified: z" ]] || false
|
||||
[[ "$output" =~ "modified: z" ]] || false
|
||||
|
||||
run dolt schema show z
|
||||
[ "$status" -eq 0 ]
|
||||
@@ -230,7 +230,7 @@ SQL
|
||||
dolt sql -q "alter table z drop constraint foreign_key1"
|
||||
run dolt status
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ "modified: z" ]] || false
|
||||
[[ "$output" =~ "modified: z" ]] || false
|
||||
|
||||
run dolt schema show z
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
@@ -103,8 +103,8 @@ SQL
|
||||
dolt commit -m "Added column c2 longtext"
|
||||
dolt checkout main
|
||||
dolt merge branch1
|
||||
run dolt merge branch2
|
||||
[ $status -ne 0 ]
|
||||
skip_nbf_not_dolt
|
||||
dolt merge branch2
|
||||
}
|
||||
|
||||
@test "column_tags: Merging branches that use the same tag referring to different column names fails" {
|
||||
@@ -129,8 +129,8 @@ SQL
|
||||
dolt commit -m "Added column c0 bigint"
|
||||
dolt checkout main
|
||||
dolt merge branch1
|
||||
run dolt merge branch2
|
||||
[ $status -eq 1 ]
|
||||
skip_nbf_not_dolt
|
||||
dolt merge branch2
|
||||
}
|
||||
|
||||
@test "column_tags: Merging branches that both created the same column succeeds" {
|
||||
|
||||
@@ -35,6 +35,7 @@ SQL
|
||||
[[ "$output" =~ "table,num_violations" ]] || false
|
||||
[[ "$output" =~ "test,2" ]] || false
|
||||
[[ "${#lines[@]}" = "2" ]] || false
|
||||
dolt status
|
||||
run dolt status
|
||||
log_status_eq "0"
|
||||
[[ "$output" =~ "fix constraint violations" ]] || false
|
||||
|
||||
@@ -84,7 +84,7 @@ teardown() {
|
||||
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "new table: dolt_docs" ]] || false
|
||||
[[ "$output" =~ "new table: dolt_docs" ]] || false
|
||||
|
||||
dolt commit -am "added a license file"
|
||||
|
||||
|
||||
@@ -145,10 +145,11 @@ SQL
|
||||
|
||||
dolt checkout main
|
||||
dolt merge other --no-commit
|
||||
dolt status
|
||||
run dolt status
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "still merging" ]] || false
|
||||
[[ "$output" =~ "modified: test" ]] || false
|
||||
[[ "$output" =~ "modified: test1" ]] || false
|
||||
|
||||
dolt merge --abort
|
||||
run dolt status
|
||||
@@ -597,8 +598,7 @@ SQL
|
||||
dolt commit -am "add data to test1, drop test2"
|
||||
|
||||
dolt checkout main
|
||||
run dolt merge feature-branch -m "merge feature-branch"
|
||||
log_status_eq 0
|
||||
dolt merge feature-branch -m "merge feature-branch"
|
||||
|
||||
run dolt sql -q "show tables"
|
||||
log_status_eq 0
|
||||
@@ -635,8 +635,7 @@ SQL
|
||||
dolt commit -am "add data to test1"
|
||||
|
||||
dolt checkout main
|
||||
run dolt merge feature-branch -m "merge feature-branch"
|
||||
log_status_eq 0
|
||||
dolt merge feature-branch -m "merge feature-branch"
|
||||
|
||||
run dolt sql -q "show tables"
|
||||
log_status_eq 0
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
|
||||
setup() {
|
||||
setup_common
|
||||
skip_nbf_not_dolt
|
||||
}
|
||||
|
||||
teardown() {
|
||||
teardown_common
|
||||
}
|
||||
|
||||
@test "schema-conflicts: dolt_schema_conflicts smoke test" {
|
||||
run dolt sql -q "select * from dolt_schema_conflicts" -r csv
|
||||
[[ "$output" =~ "table_name,base_schema,our_schema,their_schema,description" ]]
|
||||
}
|
||||
|
||||
setup_schema_conflict() {
|
||||
dolt sql -q "create table t (pk int primary key, c0 int);"
|
||||
dolt commit -Am "new table t"
|
||||
dolt branch other
|
||||
dolt sql -q "alter table t modify c0 varchar(20)"
|
||||
dolt commit -am "alter table t on branch main"
|
||||
dolt checkout other
|
||||
dolt sql -q "alter table t modify c0 datetime"
|
||||
dolt commit -am "alter table t on branch other"
|
||||
dolt checkout main
|
||||
}
|
||||
|
||||
@test "schema-conflicts: sql merge, query schema conflicts" {
|
||||
setup_schema_conflict
|
||||
|
||||
dolt sql -q "call dolt_merge('other')"
|
||||
|
||||
run dolt sql -q "select our_schema from dolt_schema_conflicts" -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "varchar(20)," ]]
|
||||
run dolt sql -q "select their_schema from dolt_schema_conflicts" -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "datetime(6)," ]]
|
||||
}
|
||||
|
||||
@test "schema-conflicts: cli merge, query schema conflicts" {
|
||||
setup_schema_conflict
|
||||
|
||||
dolt merge other
|
||||
|
||||
run dolt sql -q "select our_schema from dolt_schema_conflicts" -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "varchar(20)," ]]
|
||||
run dolt sql -q "select their_schema from dolt_schema_conflicts" -r csv
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "datetime(6)," ]]
|
||||
}
|
||||
|
||||
@test "schema-conflicts: resolve schema conflict with 'ours' via SQL" {
|
||||
setup_schema_conflict
|
||||
|
||||
dolt merge other
|
||||
|
||||
dolt sql -q "call dolt_conflicts_resolve('--ours', 't')"
|
||||
run dolt schema show t
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "varchar(20)" ]]
|
||||
}
|
||||
|
||||
@test "schema-conflicts: resolve schema conflict with 'theirs' via SQL" {
|
||||
setup_schema_conflict
|
||||
|
||||
dolt merge other
|
||||
|
||||
dolt sql -q "call dolt_conflicts_resolve('--theirs', 't')"
|
||||
run dolt schema show t
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "datetime" ]]
|
||||
}
|
||||
|
||||
@test "schema-conflicts: resolve schema conflict with 'ours' via CLI" {
|
||||
setup_schema_conflict
|
||||
|
||||
dolt merge other
|
||||
|
||||
dolt conflicts resolve --ours t
|
||||
run dolt schema show t
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "varchar(20)" ]]
|
||||
}
|
||||
|
||||
@test "schema-conflicts: resolve schema conflict with 'theirs' via CLI" {
|
||||
setup_schema_conflict
|
||||
|
||||
dolt merge other
|
||||
|
||||
dolt conflicts resolve --theirs t
|
||||
run dolt schema show t
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "datetime" ]]
|
||||
}
|
||||
@@ -38,7 +38,7 @@ teardown() {
|
||||
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
|
||||
dolt sql -q "call dclean('test2')"
|
||||
|
||||
@@ -48,15 +48,15 @@ teardown() {
|
||||
dolt clean test3
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
[[ ! "$output" =~ "new table: test3" ]] || false
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
[[ ! "$output" =~ "new table: test3" ]] || false
|
||||
|
||||
# don't touch staged root
|
||||
dolt add test2
|
||||
dolt clean
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-clean: DOLT_CLEAN unknown table name" {
|
||||
@@ -68,7 +68,7 @@ teardown() {
|
||||
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
|
||||
run dolt clean unknown
|
||||
[ $status -eq 1 ]
|
||||
@@ -76,6 +76,6 @@ teardown() {
|
||||
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
[[ "$output" =~ "new table: test2" ]] || false
|
||||
}
|
||||
|
||||
|
||||
@@ -909,9 +909,20 @@ SQL
|
||||
[[ "$output" =~ "CONSTRAINT \`c1\` CHECK ((\`i\` < 0))" ]] || false
|
||||
dolt commit -am "changes to main"
|
||||
|
||||
run dolt merge other -m "merge other"
|
||||
log_status_eq 1
|
||||
[[ "$output" =~ "our check 'c1' and their check 'c0' both reference the same column(s)" ]] || false
|
||||
skip_nbf_not_dolt
|
||||
dolt merge other -m "merge other"
|
||||
|
||||
run dolt status
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "schema conflict:" ]]
|
||||
run dolt sql -q "select count(*) from dolt_schema_conflicts"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "1" ]]
|
||||
dolt sql -q "call dolt_conflicts_resolve('--ours', 't')"
|
||||
dolt sql -q "show create table t"
|
||||
run dolt sql -q "show create table t"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "CONSTRAINT \`c1\` CHECK ((\`i\` < 0))" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-merge: dropping constraint on both branches merges successfully" {
|
||||
@@ -963,9 +974,18 @@ SQL
|
||||
[[ "$output" =~ "CONSTRAINT \`c\` CHECK ((\`i\` < 10))" ]] || false
|
||||
dolt commit -am "changes to main"
|
||||
|
||||
run dolt merge other -m "merge other"
|
||||
log_status_eq 1
|
||||
[[ "$output" =~ "check 'c' was deleted in theirs but modified in ours" ]] || false
|
||||
skip_nbf_not_dolt
|
||||
dolt merge other -m "merge other"
|
||||
run dolt status
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "schema conflict:" ]]
|
||||
run dolt sql -q "select count(*) from dolt_schema_conflicts"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "1" ]]
|
||||
dolt sql -q "call dolt_conflicts_resolve('--ours', 't')"
|
||||
run dolt sql -q "show create table t"
|
||||
log_status_eq 0
|
||||
[[ !("$output" =~ "CONSTRAINT \`c\` CHECK ((\`i\` > 0))") ]] || false
|
||||
}
|
||||
|
||||
@test "sql-merge: merging with not null and check constraints preserves both constraints" {
|
||||
@@ -1027,9 +1047,18 @@ SQL
|
||||
dolt checkout main
|
||||
run dolt merge b1 -m "merge b1" --commit
|
||||
log_status_eq 0
|
||||
run dolt merge b2 -m "merge b2"
|
||||
log_status_eq 1
|
||||
[[ "$output" =~ "two checks with the name 'c' but different definitions" ]] || false
|
||||
skip_nbf_not_dolt
|
||||
dolt merge b2 -m "merge b2"
|
||||
run dolt status
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "schema conflict:" ]]
|
||||
run dolt sql -q "select count(*) from dolt_schema_conflicts"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "1" ]]
|
||||
dolt sql -q "call dolt_conflicts_resolve('--ours', 't')"
|
||||
run dolt sql -q "show create table t"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "CONSTRAINT \`c\` CHECK ((\`i\` > 0))" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-merge: check constraint for deleted column in another table" {
|
||||
@@ -1054,9 +1083,18 @@ SQL
|
||||
dolt checkout main
|
||||
run dolt merge b1 -m "merge b1"
|
||||
log_status_eq 0
|
||||
run dolt merge b2 -m "merge b2"
|
||||
log_status_eq 1
|
||||
[[ "$output" =~ "check 'c' references a column that will be deleted after merge" ]] || false
|
||||
skip_nbf_not_dolt
|
||||
dolt merge b2 -m "merge b2"
|
||||
run dolt status
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "schema conflict:" ]]
|
||||
run dolt sql -q "select count(*) from dolt_schema_conflicts"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "1" ]]
|
||||
dolt sql -q "call dolt_conflicts_resolve('--ours', 't')"
|
||||
run dolt sql -q "show create table t"
|
||||
log_status_eq 0
|
||||
[[ "$output" =~ "CONSTRAINT \`c\` CHECK ((\`j\` > 0))" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-merge: DOLT_MERGE with author flag specified" {
|
||||
|
||||
@@ -146,6 +146,7 @@ teardown() {
|
||||
[ "$status" -eq 0 ]
|
||||
[[ $output =~ "CONFLICT" ]] || false
|
||||
|
||||
dolt status
|
||||
run dolt sql -r csv -q "select * from dolt_status ORDER BY status"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ 'dolt_docs,false,conflict' ]] || false
|
||||
|
||||
@@ -423,9 +423,9 @@ teardown() {
|
||||
run dolt stash pop
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "${lines[1]}" =~ "Changes to be committed:" ]] || false
|
||||
[[ "${lines[3]}" =~ "new table: new_table" ]] || false
|
||||
[[ "${lines[3]}" =~ "new table: new_table" ]] || false
|
||||
[[ "${lines[4]}" =~ "Untracked files:" ]] || false
|
||||
[[ "${lines[6]}" =~ "new table: test_table" ]] || false
|
||||
[[ "${lines[6]}" =~ "new table: test_table" ]] || false
|
||||
}
|
||||
|
||||
@test "stash: stashing staged new table changes and popping the stash, the added table should be staged" {
|
||||
@@ -625,7 +625,7 @@ teardown() {
|
||||
run dolt stash pop
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ "deleted: test" ]] || false
|
||||
[[ "$output" =~ "deleted: test" ]] || false
|
||||
[[ "$output" =~ "Dropped refs/stash@{0}" ]] || false
|
||||
}
|
||||
|
||||
|
||||
@@ -57,14 +57,14 @@ SQL
|
||||
[[ "$output" =~ "On branch main" ]] || false
|
||||
[[ "$output" =~ "Changes to be committed:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt reset <table>...\" to unstage)" ]] || false
|
||||
[[ "$output" =~ " modified: t" ]] || false
|
||||
[[ "$output" =~ " modified: t" ]] || false
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to update what will be committed)" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt checkout <table>\" to discard changes in working directory)" ]] || false
|
||||
[[ "$output" =~ " modified: u" ]] || false
|
||||
[[ "$output" =~ " modified: u" ]] || false
|
||||
[[ "$output" =~ "Untracked files:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to include in what will be committed)" ]] || false
|
||||
[[ "$output" =~ " new table: v" ]] || false
|
||||
[[ "$output" =~ " new table: v" ]] || false
|
||||
}
|
||||
|
||||
@test "status: deleted table" {
|
||||
@@ -80,11 +80,11 @@ SQL
|
||||
[[ "$output" =~ "On branch main" ]] || false
|
||||
[[ "$output" =~ "Changes to be committed:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt reset <table>...\" to unstage)" ]] || false
|
||||
[[ "$output" =~ " deleted: t" ]] || false
|
||||
[[ "$output" =~ " deleted: t" ]] || false
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to update what will be committed)" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt checkout <table>\" to discard changes in working directory)" ]] || false
|
||||
[[ "$output" =~ " deleted: u" ]] || false
|
||||
[[ "$output" =~ " deleted: u" ]] || false
|
||||
}
|
||||
|
||||
@test "status: checkout current branch" {
|
||||
@@ -116,7 +116,7 @@ SQL
|
||||
[[ "$output" =~ " (use \"dolt merge --abort\" to abort the merge)" ]] || false
|
||||
[[ "$output" =~ "Unmerged paths:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <file>...\" to mark resolution)" ]] || false
|
||||
[[ "$output" =~ " both modified: t" ]] || false
|
||||
[[ "$output" =~ " both modified: t" ]] || false
|
||||
}
|
||||
|
||||
@test "status: renamed table" {
|
||||
@@ -129,12 +129,12 @@ SQL
|
||||
[ "$status" -eq 0 ]
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "deleted: test" ]] || false
|
||||
[[ "$output" =~ "new table: quiz" ]] || false
|
||||
[[ "$output" =~ "deleted: test" ]] || false
|
||||
[[ "$output" =~ "new table: quiz" ]] || false
|
||||
dolt add .
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "renamed: test -> quiz" ]] || false
|
||||
[[ "$output" =~ "renamed: test -> quiz" ]] || false
|
||||
}
|
||||
|
||||
@test "status: unstaged changes after reset" {
|
||||
@@ -152,9 +152,9 @@ CREATE TABLE three (pk int PRIMARY KEY);
|
||||
SQL
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "modified: one" ]] || false
|
||||
[[ "$output" =~ "deleted: two" ]] || false
|
||||
[[ "$output" =~ "new table: three" ]] || false
|
||||
[[ "$output" =~ "modified: one" ]] || false
|
||||
[[ "$output" =~ "deleted: two" ]] || false
|
||||
[[ "$output" =~ "new table: three" ]] || false
|
||||
run dolt reset
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Unstaged changes after reset:" ]] || false
|
||||
@@ -290,10 +290,11 @@ SQL
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "3" ]] || false
|
||||
|
||||
dolt status
|
||||
run dolt status
|
||||
[[ "$output" =~ "Untracked files:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to include in what will be committed)" ]] || false
|
||||
[[ "$output" =~ " new table: test" ]] || false
|
||||
[[ "$output" =~ " new table: test" ]] || false
|
||||
|
||||
# Now verify that commit log has changes
|
||||
run dolt sql -q "SELECT count(*) from dolt_log"
|
||||
@@ -347,12 +348,13 @@ SQL
|
||||
|
||||
# Do a soft reset to commit 2.
|
||||
dolt reset $cm2
|
||||
dolt status
|
||||
run dolt status
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Untracked files:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to include in what will be committed)" ]] || false
|
||||
[[ "$output" =~ " new table: tb3" ]] || false
|
||||
! [[ "$output" =~ " new table: tb2" ]] || false
|
||||
[[ "$output" =~ " new table: tb3" ]] || false
|
||||
! [[ "$output" =~ "tb2" ]] || false
|
||||
|
||||
run dolt sql -q "SELECT COUNT(*) FROM tb3"
|
||||
[[ "$output" =~ "1" ]] || false
|
||||
@@ -369,9 +371,9 @@ SQL
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Untracked files:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to include in what will be committed)" ]] || false
|
||||
[[ "$output" =~ " new table: tb3" ]] || false
|
||||
[[ "$output" =~ " new table: tb2" ]] || false
|
||||
! [[ "$output" =~ " new table: tb1" ]] || false
|
||||
[[ "$output" =~ " new table: tb3" ]] || false
|
||||
[[ "$output" =~ " new table: tb2" ]] || false
|
||||
! [[ "$output" =~ "tb1" ]] || false
|
||||
|
||||
run dolt sql -q "SELECT COUNT(*) FROM dolt_log"
|
||||
[[ "$output" =~ "2" ]] || false # includes init commit
|
||||
@@ -404,7 +406,7 @@ SQL
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to update what will be committed)" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt checkout <table>\" to discard changes in working directory)" ]] || false
|
||||
[[ "$output" =~ " modified: tbl" ]] || false
|
||||
[[ "$output" =~ " modified: tbl" ]] || false
|
||||
! [[ "$output" =~ " new table: tb2" ]] || false
|
||||
}
|
||||
|
||||
@@ -424,7 +426,7 @@ SQL
|
||||
[[ "$output" =~ "Changes not staged for commit:" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt add <table>\" to update what will be committed)" ]] || false
|
||||
[[ "$output" =~ " (use \"dolt checkout <table>\" to discard changes in working directory)" ]] || false
|
||||
[[ "$output" =~ " modified: tbl" ]] || false
|
||||
[[ "$output" =~ " modified: tbl" ]] || false
|
||||
}
|
||||
|
||||
@test "status: dolt reset throws errors for unknown ref/table" {
|
||||
|
||||
Reference in New Issue
Block a user