Files
wehub-resource-sync 5357c39144
Fuzzer / Run Fuzzer (push) Has been cancelled
Race tests / Go race tests (ubuntu-22.04) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:01:40 +08:00

338 lines
9.9 KiB
Go

// 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 actions
import (
"context"
"fmt"
"time"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/doltcore/ref"
"github.com/dolthub/dolt/go/libraries/doltcore/schema"
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/resolve"
"github.com/dolthub/dolt/go/store/datas"
)
// resetHardTables resolves a new HEAD commit from a refSpec and updates working set roots by
// resetting the table contexts for tracked tables. New tables are ignored. Returns new HEAD
// Commit and Roots.
func resetHardTables[C doltdb.Context](ctx C, dbData env.DbData[C], cSpecStr string, roots doltdb.Roots) (*doltdb.Commit, doltdb.Roots, error) {
ddb := dbData.Ddb
rsr := dbData.Rsr
var newHead *doltdb.Commit
if cSpecStr != "" {
cs, err := doltdb.NewCommitSpec(cSpecStr)
if err != nil {
return nil, doltdb.Roots{}, err
}
headRef, err := rsr.CWBHeadRef(ctx)
if err != nil {
return nil, doltdb.Roots{}, err
}
optCmt, err := ddb.Resolve(ctx, cs, headRef)
if err != nil {
return nil, doltdb.Roots{}, err
}
var ok bool
if newHead, ok = optCmt.ToCommit(); !ok {
return nil, doltdb.Roots{}, doltdb.ErrGhostCommitEncountered
}
roots.Head, err = newHead.GetRootValue(ctx)
if err != nil {
return nil, doltdb.Roots{}, err
}
}
newWorking, err := MoveUntrackedTables(ctx, roots.Working, roots.Staged, roots.Head)
if err != nil {
return nil, doltdb.Roots{}, err
}
return newHead, doltdb.Roots{Head: roots.Head, Working: newWorking, Staged: roots.Head}, nil
}
// MoveUntrackedTables copies tables present in |sourceWorking| but absent from |sourceStaged|
// onto |target|, returning the resulting root. Tables that name-collide with one already in
// |target| are skipped so the target version wins. Tables whose column tags collide with one
// in |target| are dropped silently.
// TODO(elianddb): retag colliding columns instead of dropping the table.
func MoveUntrackedTables(ctx context.Context, sourceWorking, sourceStaged, target doltdb.RootValue) (doltdb.RootValue, error) {
untracked, err := doltdb.GetAllSchemas(ctx, sourceWorking)
if err != nil {
return nil, err
}
staged, err := doltdb.GetAllSchemas(ctx, sourceStaged)
if err != nil {
return nil, err
}
for name := range staged {
delete(untracked, name)
}
targetSchemas, err := doltdb.GetAllSchemas(ctx, target)
if err != nil {
return nil, err
}
targetTags := make(map[uint64]struct{})
for _, sch := range targetSchemas {
for _, tag := range sch.GetAllCols().Tags {
targetTags[tag] = struct{}{}
}
}
for name, sch := range untracked {
for _, col := range sch.GetAllCols().GetColumns() {
if _, ok := targetTags[col.Tag]; ok {
delete(untracked, name)
break
}
}
}
for name := range untracked {
// Skip when target has a table of the same name so the target version wins.
if _, exists := targetSchemas[name]; exists {
continue
}
tbl, exists, err := sourceWorking.GetTable(ctx, name)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("untracked table %s does not exist in working set", name)
}
target, err = target.PutTable(ctx, name, tbl)
if err != nil {
return nil, fmt.Errorf("failed to write table back to database: %s", err)
}
}
return target, nil
}
func GetAllTableNames(ctx context.Context, root doltdb.RootValue) []doltdb.TableName {
tableNames := make([]doltdb.TableName, 0)
_ = root.IterTables(ctx, func(name doltdb.TableName, table *doltdb.Table, sch schema.Schema) (stop bool, err error) {
tableNames = append(tableNames, name)
return false, nil
})
return tableNames
}
// ResetHardTables resets the tables in working, staged, and head based on the given parameters. Returns the new
// head commit and resulting roots
func ResetHardTables[C doltdb.Context](ctx C, dbData env.DbData[C], cSpecStr string, roots doltdb.Roots) (*doltdb.Commit, doltdb.Roots, error) {
return resetHardTables(ctx, dbData, cSpecStr, roots)
}
// ResetHard resets the working, staged, and head to the ones in the provided roots and head ref.
// The reset can be performed on a non-current branch and working set.
// Returns an error if the reset fails.
func ResetHard[C doltdb.Context](
ctx C,
dbData env.DbData[C],
doltDb *doltdb.DoltDB,
username, email string,
cSpecStr string,
roots doltdb.Roots,
headRef ref.DoltRef,
ws *doltdb.WorkingSet,
) error {
newHead, roots, err := resetHardTables(ctx, dbData, cSpecStr, roots)
if err != nil {
return err
}
currentWs, err := doltDb.ResolveWorkingSet(ctx, ws.Ref())
if err != nil {
return err
}
h, err := currentWs.HashOf()
if err != nil {
return err
}
// TODO - refactor this to ensure the update to the head and working set are transactional.
err = doltDb.UpdateWorkingSet(ctx, ws.Ref(), ws.WithWorkingRoot(roots.Working).WithStagedRoot(roots.Staged).ClearMerge().ClearRebase(), h, &datas.WorkingSetMeta{
Name: username,
Email: email,
Timestamp: uint64(time.Now().Unix()),
Description: "reset hard",
}, nil)
if err != nil {
return err
}
if newHead != nil {
err = doltDb.SetHeadToCommit(ctx, headRef, newHead)
if err != nil {
return err
}
}
return nil
}
func ResetSoftTables(ctx context.Context, tableNames []doltdb.TableName, roots doltdb.Roots) (doltdb.Roots, error) {
tables, err := getUnionedTables(ctx, tableNames, roots.Staged, roots.Head)
if err != nil {
return doltdb.Roots{}, err
}
err = ValidateTables(ctx, tables, roots.Staged, roots.Head)
if err != nil {
return doltdb.Roots{}, err
}
roots.Staged, err = MoveTablesBetweenRoots(ctx, tables, roots.Head, roots.Staged)
if err != nil {
return doltdb.Roots{}, err
}
return roots, nil
}
// MoveHeadToRef resolves |cSpecStr| to a commit and moves the current branch HEAD to it. The
// returned Roots has Head and Staged set to the resolved commit's root, available for callers
// composing further reset semantics on top of the HEAD move.
func MoveHeadToRef[C doltdb.Context](ctx C, dbData env.DbData[C], cSpecStr string) (doltdb.Roots, error) {
cs, err := doltdb.NewCommitSpec(cSpecStr)
if err != nil {
return doltdb.Roots{}, err
}
headRef, err := dbData.Rsr.CWBHeadRef(ctx)
if err != nil {
return doltdb.Roots{}, err
}
optCmt, err := dbData.Ddb.Resolve(ctx, cs, headRef)
if err != nil {
return doltdb.Roots{}, err
}
newHead, ok := optCmt.ToCommit()
if !ok {
return doltdb.Roots{}, doltdb.ErrGhostCommitEncountered
}
foundRoot, err := newHead.GetRootValue(ctx)
if err != nil {
return doltdb.Roots{}, err
}
// Update the head to this commit
if err = dbData.Ddb.SetHeadToCommit(ctx, headRef, newHead); err != nil {
return doltdb.Roots{}, err
}
return doltdb.Roots{
Head: foundRoot,
Staged: foundRoot,
}, err
}
func getUnionedTables(ctx context.Context, tables []doltdb.TableName, stagedRoot, headRoot doltdb.RootValue) ([]doltdb.TableName, error) {
if len(tables) == 0 {
var err error
tables, err = doltdb.UnionTableNames(ctx, stagedRoot, headRoot)
if err != nil {
return nil, err
}
}
return tables, nil
}
// CleanUntracked deletes from the working root the tables that are untracked (in working but not in staged/head). If
// |tables| is non-empty it uses only those names as candidates; otherwise it uses all working tables. Tables matching
// dolt_nonlocal_tables are always excluded. When |respectIgnoreRules| is true, tables matching dolt_ignore are also excluded. Does nothing when |dryrun| is true.
func CleanUntracked(ctx *sql.Context, roots doltdb.Roots, tables []string, dryrun bool, force bool, respectIgnoreRules bool) (doltdb.Roots, error) {
untrackedTables := make(map[doltdb.TableName]struct{})
for _, name := range tables {
resolvedName, tblExists, err := resolve.TableName(ctx, roots.Working, name)
if err != nil {
return doltdb.Roots{}, err
}
if !tblExists {
return doltdb.Roots{}, fmt.Errorf("%w: '%s'", doltdb.ErrTableNotFound, name)
}
untrackedTables[resolvedName] = struct{}{}
}
if len(tables) == 0 {
allTableNames, err := roots.Working.GetAllTableNames(ctx, true)
if err != nil {
return doltdb.Roots{}, err
}
var candidates []doltdb.TableName
if respectIgnoreRules {
candidates, err = doltdb.ExcludeIgnoredTables(ctx, roots, allTableNames)
if err != nil {
return doltdb.Roots{}, err
}
} else {
candidates = allTableNames
}
var nonlocalPatterns []string
err = doltdb.GetNonlocalTablePatterns(ctx, roots.Working, doltdb.DefaultSchemaName, func(p string) {
nonlocalPatterns = append(nonlocalPatterns, p)
})
if err != nil {
return doltdb.Roots{}, err
}
compiled, err := doltdb.CompileTablePatterns(nonlocalPatterns)
if err != nil {
return doltdb.Roots{}, err
}
for _, tableName := range candidates {
if compiled.TableMatchesAny(tableName.Name) {
continue
}
untrackedTables[tableName] = struct{}{}
}
}
headTblNames := GetAllTableNames(ctx, roots.Staged)
for _, name := range headTblNames {
delete(untrackedTables, name)
}
toDelete := make([]doltdb.TableName, 0, len(untrackedTables))
for t := range untrackedTables {
toDelete = append(toDelete, t)
}
if dryrun {
return roots, nil
}
newRoot, err := roots.Working.RemoveTables(ctx, force, force, toDelete...)
if err != nil {
return doltdb.Roots{}, fmt.Errorf("failed to remove tables; %w", err)
}
roots.Working = newRoot
return roots, nil
}