From 6251b3513f32b9f15daa38394206e547376b3b8e Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Mon, 2 Jun 2025 16:07:04 -0700 Subject: [PATCH 01/14] First pass at dolt_stash() --- go/cmd/dolt/cli/arg_parser_helpers.go | 7 + .../doltcore/sqle/dprocedures/dolt_stash.go | 530 ++++++++++++++++++ .../doltcore/sqle/dprocedures/init.go | 1 + 3 files changed, 538 insertions(+) create mode 100644 go/libraries/doltcore/sqle/dprocedures/dolt_stash.go diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index 31be2b8b7dd..7b6ef77ddd4 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -105,6 +105,13 @@ func CreateMergeArgParser() *argparser.ArgParser { return ap } +func CreateStashArgParser() *argparser.ArgParser { + ap := argparser.NewArgParserWithMaxArgs("stash", 2) + ap.SupportsFlag("include-untracked", "u", "Untracked tables are also stashed.") //TODO: Add Constant + ap.SupportsFlag(AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") + return ap +} + func CreateRebaseArgParser() *argparser.ArgParser { ap := argparser.NewArgParserWithMaxArgs("rebase", 1) ap.TooManyArgsErrorFunc = func(receivedArgs []string) error { diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go new file mode 100644 index 00000000000..0af85c67e22 --- /dev/null +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -0,0 +1,530 @@ +// Copyright 2022 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 dprocedures + +import ( + "context" + "fmt" + "github.com/dolthub/dolt/go/cmd/dolt/cli" + "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/env/actions" + "github.com/dolthub/dolt/go/libraries/doltcore/merge" + "github.com/dolthub/dolt/go/libraries/doltcore/ref" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" + "github.com/dolthub/dolt/go/libraries/doltcore/table/editor" + "github.com/dolthub/dolt/go/libraries/utils/argparser" + "github.com/dolthub/dolt/go/store/datas" + "github.com/dolthub/dolt/go/store/hash" + "github.com/dolthub/go-mysql-server/sql" + "strconv" + "strings" + "time" +) + +const ( + includeUntrackedFlag = "include-untracked" +) + +// doltStash is the stored procedure version for the CLI command `dolt stash` +// and its options push, pop, drop, and clear +func doltStash(ctx *sql.Context, args ...string) (sql.RowIter, error) { + res, err := doDoltStash(ctx, args) + if err != nil { + return nil, err + } + + return rowToIter(res), nil +} + +func doDoltStash(ctx *sql.Context, args []string) (string, error) { + dbName := ctx.GetCurrentDatabase() + + dSess := dsess.DSessFromSess(ctx.Session) + dbData, ok := dSess.GetDbData(ctx, dbName) + if !ok { + return "", fmt.Errorf("Could not load database %s", dbName) + } + if !dbData.Ddb.Format().UsesFlatbuffers() { + return "", fmt.Errorf("stash is not supported for old storage format") + } + + roots, ok := dSess.GetRoots(ctx, dbName) + if !ok { + return "", fmt.Errorf("Could not load roots for database %s", dbName) + } + + apr, err := cli.CreateStashArgParser().Parse(args) + if err != nil { + return "", err + } + + if apr.NArg() == 0 { + return "", fmt.Errorf("error: invalid arguments. Must provide valid stash subcommand") + } + + var status string + switch apr.Arg(0) { + case "push": + status, err = doStashPush(ctx, dSess, dbData, roots, apr) + case "pop": + status, err = doStashPop(ctx, dbData, apr, roots) + case "drop": + status, err = doStashDrop(ctx, dbData, apr) + case "clear": + err = doStashClear(ctx, dbData) + default: + return "", fmt.Errorf("unknown stash subcommand %s", apr.Arg(0)) + } + + if err != nil { + return "", err + } + + return status, nil +} + +func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults) (string, error) { + + hasChanges, err := hasLocalChanges(ctx, dSess, dbData, roots, apr) + if err != nil { + return "", err + } + if !hasChanges { + return "", fmt.Errorf("no local changes to save") //What should I do here? + } + + roots, err = actions.StageModifiedAndDeletedTables(ctx, roots) + if err != nil { + return "", err + } + + // all tables with changes that are going to be stashed are staged at this point + + allTblsToBeStashed, addedTblsToStage, err := stashedTableSets(ctx, roots) + if err != nil { + return "", err + } + + if apr.Contains(includeUntrackedFlag) || apr.Contains(cli.AllFlag) { + allTblsToBeStashed, err = doltdb.UnionTableNames(ctx, roots.Staged, roots.Working) + if err != nil { + return "", err + } + + roots, err = actions.StageTables(ctx, roots, allTblsToBeStashed, !apr.Contains("all")) + if err != nil { + return "", err + } + } + + curHeadRef, err := dbData.Rsr.CWBHeadRef(ctx) + if err != nil { + return "", err + } + + curBranchName := curHeadRef.String() + commitSpec, err := doltdb.NewCommitSpec(curBranchName) + if err != nil { + return "", err + } + optCmt, err := dbData.Ddb.Resolve(ctx, commitSpec, curHeadRef) + if err != nil { + return "", err + } + commit, ok := optCmt.ToCommit() + if !ok { + return "", doltdb.ErrGhostCommitEncountered + } + + commitMeta, err := commit.GetCommitMeta(ctx) + if err != nil { + return "", err + } + + err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage))) + if err != nil { + return "", err + } + + roots.Staged = roots.Head + roots, err = actions.MoveTablesFromHeadToWorking(ctx, roots, allTblsToBeStashed) + if err != nil { + return "", err + } + + err = updateWorkingSetFromRoots(ctx, dbData, roots) + if err != nil { + return "", err + } + + commitHash, err := commit.HashOf() + if err != nil { + return "", err + } + + status := fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", curBranchName, commitHash.String(), commitMeta.Description) + return status, nil +} + +func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparser.ArgParseResults, roots doltdb.Roots) (string, error) { + var idx = 0 + var err error + if apr.NArg() == 2 { + idx, err = parseStashIndex(apr.Args[1]) + } + + headRef, err := dbData.Rsr.CWBHeadRef(ctx) + if err != nil { + return "", err + } + workingSetRef, err := ref.WorkingSetRefForHead(headRef) + if err != nil { + return "", err + } + workingSet, err := dbData.Ddb.ResolveWorkingSet(ctx, workingSetRef) + if err != nil { + return "", err + } + curWorkingRoot := workingSet.WorkingRoot() + + stashRoot, headCommit, meta, err := dbData.Ddb.GetStashRootAndHeadCommitAtIdx(ctx, idx) + if err != nil { + return "", err + } + + hch, err := headCommit.HashOf() + if err != nil { + return "", err + } + headCommitSpec, err := doltdb.NewCommitSpec(hch.String()) + if err != nil { + return "", err + } + + optCmt, err := dbData.Ddb.Resolve(ctx, headCommitSpec, headRef) + if err != nil { + return "", err + } + parentCommit, ok := optCmt.ToCommit() + if !ok { + // Should not be possible to get into this situation. The parent of the stashed commit + // Must have been present at the time it was created + return "", doltdb.ErrGhostCommitEncountered + } + + parentRoot, err := parentCommit.GetRootValue(ctx) + if err != nil { + return "", err + } + + tmpDir, err := dbData.Rsw.TempTableFilesDir() + if err != nil { + return "", err + } + + opts := editor.Options{Deaf: bulkDbEaFactory(dbData), Tempdir: tmpDir} + result, err := merge.MergeRoots(ctx, curWorkingRoot, stashRoot, parentRoot, stashRoot, parentCommit, opts, merge.MergeOpts{IsCherryPick: false}) + if err != nil { + return "", err + } + + var tablesWithConflict []doltdb.TableName + for tbl, stats := range result.Stats { + if stats.HasConflicts() { + tablesWithConflict = append(tablesWithConflict, tbl) + } + } + + if len(tablesWithConflict) > 0 { + tblNames := strings.Join(doltdb.FlattenTableNames(tablesWithConflict), "', '") + status := fmt.Sprintf("error: Your local changes to the following tables would be overwritten by applying stash %d:\n"+ + "\t{'%s'}\n"+ + "Please commit your changes or stash them before you merge.\nAborting\n", idx, tblNames) + return status, nil + } + + //err = dEnv.UpdateWorkingRoot(ctx, result.Root) + var h hash.Hash + var wsRef ref.WorkingSetRef + + ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) + if err == doltdb.ErrWorkingSetNotFound { + // first time updating root + wsRef, err = ref.WorkingSetRefForHead(headRef) + if err != nil { + return "", err + } + ws = doltdb.EmptyWorkingSet(wsRef).WithWorkingRoot(result.Root).WithStagedRoot(result.Root) + } else if err != nil { + return "", err + } else { + h, err = ws.HashOf() + if err != nil { + return "", err + } + + wsRef = ws.Ref() + } + + wsm := &datas.WorkingSetMeta{ + Timestamp: uint64(time.Now().Unix()), + Description: "updated from dolt environment", + } + + err = dbData.Ddb.UpdateWorkingSet(ctx, wsRef, ws.WithWorkingRoot(result.Root), h, wsm, nil) + if err != nil { + return "", err + } + + // added tables need to be staged + // since these tables are coming from a stash, don't filter for ignored table names. + roots, err = actions.StageTables(ctx, roots, doltdb.ToTableNames(meta.TablesToStage, doltdb.DefaultSchemaName), false) + if err != nil { + return "", err + } + + err = updateWorkingSetFromRoots(ctx, dbData, roots) + if err != nil { + return "", err + } + + stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx) + if err != nil { + return "", err + } + + err = dbData.Ddb.RemoveStashAtIdx(ctx, idx) + if err != nil { + return "", err + } + + status := fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String()) + return status, err +} + +func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparser.ArgParseResults) (string, error) { + var idx = 0 + var err error + + if apr.NArg() == 2 { + idx, err = parseStashIndex(apr.Args[1]) + if err != nil { + return "", err + } + } + + stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx) + if err != nil { + return "", err + } + + err = dbData.Ddb.RemoveStashAtIdx(ctx, idx) + if err != nil { + return "", err + } + + status := fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String()) + + return status, nil +} + +func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context]) error { + err := dbData.Ddb.RemoveAllStashes(ctx) + if err != nil { + return err + } + return nil +} + +func stashedTableSets(ctx context.Context, roots doltdb.Roots) ([]doltdb.TableName, []doltdb.TableName, error) { + var addedTblsInStaged []doltdb.TableName + var allTbls []doltdb.TableName + staged, _, err := diff.GetStagedUnstagedTableDeltas(ctx, roots) + if err != nil { + return nil, nil, err + } + + for _, tableDelta := range staged { + tblName := tableDelta.ToName + if tableDelta.IsAdd() { + addedTblsInStaged = append(addedTblsInStaged, tableDelta.ToName) + } + if tableDelta.IsDrop() { + tblName = tableDelta.FromName + } + allTbls = append(allTbls, tblName) + } + + return allTbls, addedTblsInStaged, nil +} + +func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { + dbName := ctx.GetCurrentDatabase() + + headCommit, err := dSess.GetHeadCommit(ctx, dbName) + if err != nil { + return false, err + } + + headRoot, err := headCommit.GetRootValue(ctx) + if err != nil { + return false, err + } + + workingSet, err := dSess.WorkingSet(ctx, dbName) + if err != nil { + return false, err + } + workingRoot := workingSet.WorkingRoot() + stagedRoot := workingSet.StagedRoot() + + headHash, err := headRoot.HashOf() + if err != nil { + return false, err + } + workingHash, err := workingRoot.HashOf() + if err != nil { + return false, err + } + stagedHash, err := stagedRoot.HashOf() + if err != nil { + return false, err + } + + // Are there staged changes? If so, stash them. + if !headHash.Equal(stagedHash) { + return true, nil + } + + // No staged changes, but are there any unstaged changes? If not, no work is needed. + if headHash.Equal(workingHash) { + return false, nil + } + + // There are unstaged changes, is --all set? If so, nothing else matters. Stash them. + if apr.Contains(cli.AllFlag) { + return true, nil + } + + // --all was not set, so we can ignore tables. Is every table ignored? + allIgnored, err := diff.WorkingSetContainsOnlyIgnoredTables(ctx, roots) + if err != nil { + return false, err + } + + if allIgnored { + return false, nil + } + + // There are unignored, unstaged tables. Is --include-untracked set? If so, nothing else matters. Stash them. + if apr.Contains(includeUntrackedFlag) { + return true, nil + } + + // --include-untracked was not set, so we can skip untracked tables. Is every table untracked? + allUntracked, err := workingSetContainsOnlyUntrackedTables(ctx, roots) + if err != nil { + return false, err + } + + if allUntracked { + return false, nil + } + + // There are changes to tracked tables. Stash them. + return true, nil +} + +func workingSetContainsOnlyUntrackedTables(ctx context.Context, roots doltdb.Roots) (bool, error) { + _, unstaged, err := diff.GetStagedUnstagedTableDeltas(ctx, roots) + if err != nil { + return false, err + } + + // All ignored files are also untracked files + for _, tableDelta := range unstaged { + if !tableDelta.IsAdd() { + return false, nil + } + } + + return true, nil +} + +func updateWorkingSetFromRoots(ctx *sql.Context, dbData env.DbData[*sql.Context], roots doltdb.Roots) error { + ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) + if err == doltdb.ErrWorkingSetNotFound { + // first time updating roots + headRef, err := dbData.Rsr.CWBHeadRef(ctx) + if err != nil { + return err + } + wsRef, err := ref.WorkingSetRefForHead(headRef) + if err != nil { + return err + } + ws = doltdb.EmptyWorkingSet(wsRef) + } else if err != nil { + return err + } + + ws = ws.WithWorkingRoot(roots.Working).WithStagedRoot(roots.Staged) + + currentWs, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) + if err != doltdb.ErrWorkingSetNotFound && err != nil { + return err + } + + var h hash.Hash + if currentWs != nil { + h, err = currentWs.HashOf() + if err != nil { + return err + } + } + + wsm := &datas.WorkingSetMeta{ + Timestamp: uint64(time.Now().Unix()), + Description: "updated from dolt environment", + } + + err = dbData.Ddb.UpdateWorkingSet(ctx, ws.Ref(), ws, h, wsm, nil) + if err != nil { + return err + } + + return nil +} + +func parseStashIndex(stashName string) (int, error) { + var idx = 0 + stashName = strings.TrimSuffix(strings.TrimPrefix(stashName, "stash@{"), "}") + idx, err := strconv.Atoi(stashName) + if err != nil { + return 0, fmt.Errorf("error: %s is not a valid reference", stashName) + } + + return idx, nil +} + +func bulkDbEaFactory(dbData env.DbData[*sql.Context]) editor.DbEaFactory { + tmpDir, err := dbData.Rsw.TempTableFilesDir() + if err != nil { + return nil + } + return editor.NewBulkImportTEAFactory(dbData.Ddb.ValueReadWriter(), tmpDir) +} diff --git a/go/libraries/doltcore/sqle/dprocedures/init.go b/go/libraries/doltcore/sqle/dprocedures/init.go index 61aa99c562e..48e6f661922 100644 --- a/go/libraries/doltcore/sqle/dprocedures/init.go +++ b/go/libraries/doltcore/sqle/dprocedures/init.go @@ -46,6 +46,7 @@ var DoltProcedures = []sql.ExternalStoredProcedureDetails{ {Name: "dolt_remote", Schema: int64Schema("status"), Function: doltRemote, AdminOnly: true}, {Name: "dolt_reset", Schema: int64Schema("status"), Function: doltReset}, {Name: "dolt_revert", Schema: int64Schema("status"), Function: doltRevert}, + {Name: "dolt_stash", Schema: stringSchema("status"), Function: doltStash}, {Name: "dolt_tag", Schema: int64Schema("status"), Function: doltTag}, {Name: "dolt_verify_constraints", Schema: int64Schema("violations"), Function: doltVerifyConstraints}, From 32b529bfc353bc1a34c36033dfda818a0127ef46 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Wed, 4 Jun 2025 13:38:28 -0700 Subject: [PATCH 02/14] Stash list table + (mostly) function procedure --- go/cmd/dolt/commands/stashcmds/clear.go | 2 +- go/cmd/dolt/commands/stashcmds/drop.go | 4 +- go/cmd/dolt/commands/stashcmds/pop.go | 2 +- go/cmd/dolt/commands/stashcmds/stash.go | 2 +- go/libraries/doltcore/doltdb/doltdb.go | 42 ++-- go/libraries/doltcore/doltdb/system_table.go | 8 + go/libraries/doltcore/ref/ref.go | 2 +- go/libraries/doltcore/ref/stash_ref.go | 6 +- go/libraries/doltcore/sqle/database.go | 8 + .../doltcore/sqle/dprocedures/dolt_stash.go | 97 +++++--- .../doltcore/sqle/dtables/stashes_table.go | 210 ++++++++++++++++++ .../sqle/enginetest/dolt_engine_test.go | 6 + .../sqle/enginetest/dolt_engine_tests.go | 10 + .../doltcore/sqle/enginetest/dolt_queries.go | 200 +++++++++++++++++ 14 files changed, 537 insertions(+), 62 deletions(-) create mode 100644 go/libraries/doltcore/sqle/dtables/stashes_table.go diff --git a/go/cmd/dolt/commands/stashcmds/clear.go b/go/cmd/dolt/commands/stashcmds/clear.go index 5beb5b245fc..6c2d796c767 100644 --- a/go/cmd/dolt/commands/stashcmds/clear.go +++ b/go/cmd/dolt/commands/stashcmds/clear.go @@ -77,7 +77,7 @@ func (cmd StashClearCmd) Exec(ctx context.Context, commandStr string, args []str return 1 } - err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx) + err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx, "dolt-cli") if err != nil { return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } diff --git a/go/cmd/dolt/commands/stashcmds/drop.go b/go/cmd/dolt/commands/stashcmds/drop.go index 3ab46dff04c..a4e0e430c8b 100644 --- a/go/cmd/dolt/commands/stashcmds/drop.go +++ b/go/cmd/dolt/commands/stashcmds/drop.go @@ -95,12 +95,12 @@ func (cmd StashDropCmd) Exec(ctx context.Context, commandStr string, args []stri } func dropStashAtIdx(ctx context.Context, dEnv *env.DoltEnv, idx int) error { - stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx) + stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, "dolt-cli") if err != nil { return err } - err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx) + err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx, "dolt-cli") if err != nil { return err } diff --git a/go/cmd/dolt/commands/stashcmds/pop.go b/go/cmd/dolt/commands/stashcmds/pop.go index 77aa51957af..060f4d6de38 100644 --- a/go/cmd/dolt/commands/stashcmds/pop.go +++ b/go/cmd/dolt/commands/stashcmds/pop.go @@ -125,7 +125,7 @@ func (cmd StashPopCmd) Exec(ctx context.Context, commandStr string, args []strin } func applyStashAtIdx(ctx *sql.Context, dEnv *env.DoltEnv, curWorkingRoot doltdb.RootValue, idx int) (bool, error) { - stashRoot, headCommit, meta, err := dEnv.DoltDB(ctx).GetStashRootAndHeadCommitAtIdx(ctx, idx) + stashRoot, headCommit, meta, err := dEnv.DoltDB(ctx).GetStashRootAndHeadCommitAtIdx(ctx, idx, "dolt-cli") if err != nil { return false, err } diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 94ff40cd487..5942a30d93d 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -242,7 +242,7 @@ func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars return err } - err = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage))) + err = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), "dolt-cli") if err != nil { return err } diff --git a/go/libraries/doltcore/doltdb/doltdb.go b/go/libraries/doltcore/doltdb/doltdb.go index 6539d9c9180..c0a6420a9e2 100644 --- a/go/libraries/doltcore/doltdb/doltdb.go +++ b/go/libraries/doltcore/doltdb/doltdb.go @@ -2076,8 +2076,8 @@ func (ddb *DoltDB) GetBranchesByRootHash(ctx context.Context, rootHash hash.Hash // AddStash takes current branch head commit, stash root value and stash metadata to create a new stash. // It stores the new stash object in stash list Dataset, which can be created if it does not exist. // Otherwise, it updates the stash list Dataset as there can only be one stashes Dataset. -func (ddb *DoltDB) AddStash(ctx context.Context, head *Commit, stash RootValue, meta *datas.StashMeta) error { - stashesDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef().String()) +func (ddb *DoltDB) AddStash(ctx context.Context, head *Commit, stash RootValue, meta *datas.StashMeta, stashName string) error { + stashesDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stashName).String()) if err != nil { return err } @@ -2159,8 +2159,8 @@ func (ddb *DoltDB) GetStatistics(ctx context.Context) (prolly.Map, error) { // It removes a Stash message from stash list Dataset, which cannot be performed // by database Delete function. This function removes a single stash only and stash // list dataset does not get removed if there are no entries left. -func (ddb *DoltDB) RemoveStashAtIdx(ctx context.Context, idx int) error { - stashesDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef().String()) +func (ddb *DoltDB) RemoveStashAtIdx(ctx context.Context, idx int, stashName string) error { + stashesDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stashName).String()) if err != nil { return err } @@ -2186,7 +2186,7 @@ func (ddb *DoltDB) RemoveStashAtIdx(ctx context.Context, idx int) error { } // if the stash list is empty, remove the stash list Dataset from the database if stashListCount == 0 { - return ddb.RemoveAllStashes(ctx) + return ddb.RemoveAllStashes(ctx, stashName) } stashesDS, err = ddb.db.UpdateStashList(ctx, stashesDS, stashListAddr) @@ -2195,31 +2195,41 @@ func (ddb *DoltDB) RemoveStashAtIdx(ctx context.Context, idx int) error { // RemoveAllStashes removes the stash list Dataset from the database, // which equivalent to removing Stash entries from the stash list. -func (ddb *DoltDB) RemoveAllStashes(ctx context.Context) error { - err := ddb.deleteRef(ctx, ref.NewStashRef(), nil, "") +func (ddb *DoltDB) RemoveAllStashes(ctx context.Context, stashName string) error { + err := ddb.deleteRef(ctx, ref.NewStashRef(stashName), nil, "") if err == ErrBranchNotFound { return nil } return err } +var stashRefFilter = map[ref.RefType]struct{}{ref.StashRefType: {}} + // GetStashes returns array of Stash objects containing all stash entries in the stash list Dataset. func (ddb *DoltDB) GetStashes(ctx context.Context) ([]*Stash, error) { - stashesDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef().String()) + stashRefs, err := ddb.GetRefsOfType(ctx, stashRefFilter) if err != nil { return nil, err } - - if !stashesDS.HasHead() { - return []*Stash{}, nil + var stashList []*Stash + for _, stash := range stashRefs { + stashDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stash.String()).String()) + if err != nil { + return nil, err + } + newStashes, err := getStashList(ctx, stashDS, ddb.vrw, ddb.NodeStore()) + if err != nil { + return nil, err + } + stashList = append(stashList, newStashes...) } - return getStashList(ctx, stashesDS, ddb.vrw, ddb.NodeStore()) + return stashList, nil } // GetStashHashAtIdx returns hash address only of the stash at given index. -func (ddb *DoltDB) GetStashHashAtIdx(ctx context.Context, idx int) (hash.Hash, error) { - ds, err := ddb.db.GetDataset(ctx, ref.NewStashRef().String()) +func (ddb *DoltDB) GetStashHashAtIdx(ctx context.Context, idx int, stashName string) (hash.Hash, error) { + ds, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stashName).String()) if err != nil { return hash.Hash{}, err } @@ -2233,8 +2243,8 @@ func (ddb *DoltDB) GetStashHashAtIdx(ctx context.Context, idx int) (hash.Hash, e // GetStashRootAndHeadCommitAtIdx returns root value of stash working set and head commit of the branch that the stash was made on // of the stash at given index. -func (ddb *DoltDB) GetStashRootAndHeadCommitAtIdx(ctx context.Context, idx int) (RootValue, *Commit, *datas.StashMeta, error) { - ds, err := ddb.db.GetDataset(ctx, ref.NewStashRef().String()) +func (ddb *DoltDB) GetStashRootAndHeadCommitAtIdx(ctx context.Context, idx int, stashName string) (RootValue, *Commit, *datas.StashMeta, error) { + ds, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stashName).String()) if err != nil { return nil, nil, nil, err } diff --git a/go/libraries/doltcore/doltdb/system_table.go b/go/libraries/doltcore/doltdb/system_table.go index 8b0561208b3..65c1c9d6aec 100644 --- a/go/libraries/doltcore/doltdb/system_table.go +++ b/go/libraries/doltcore/doltdb/system_table.go @@ -221,6 +221,7 @@ var getGeneratedSystemTables = func() []string { GetRemotesTableName(), GetHelpTableName(), GetBackupsTableName(), + GetStashesTableName(), } } @@ -393,6 +394,10 @@ var GetBackupsTableName = func() string { return BackupsTableName } +var GetStashesTableName = func() string { + return StashesTableName +} + const ( // LogTableName is the log system table name LogTableName = "dolt_log" @@ -444,6 +449,9 @@ const ( // StatisticsTableName is the statistics system table name StatisticsTableName = "dolt_statistics" + + // StashesTableName is the stashes system table name + StashesTableName = "dolt_stashes" ) const ( diff --git a/go/libraries/doltcore/ref/ref.go b/go/libraries/doltcore/ref/ref.go index 502ef416ff5..72eef681feb 100644 --- a/go/libraries/doltcore/ref/ref.go +++ b/go/libraries/doltcore/ref/ref.go @@ -197,7 +197,7 @@ func Parse(str string) (DoltRef, error) { str = str[len(prefix):] switch rType { case StashRefType: - return NewStashRef(), nil + return NewStashRef(str), nil default: panic("unknown type " + rType) } diff --git a/go/libraries/doltcore/ref/stash_ref.go b/go/libraries/doltcore/ref/stash_ref.go index ade5287d0ad..02f6ea63028 100644 --- a/go/libraries/doltcore/ref/stash_ref.go +++ b/go/libraries/doltcore/ref/stash_ref.go @@ -18,9 +18,6 @@ import ( "strings" ) -// StashRefName is a dummy name, and there cannot be more than one stash ref. -const StashRefName = "stashes" - type StashRef struct { stash string } @@ -28,8 +25,7 @@ type StashRef struct { var _ DoltRef = StashRef{} // NewStashRef creates a reference to a stashes list. There cannot be more than one stashRef. -func NewStashRef() StashRef { - stashName := StashRefName +func NewStashRef(stashName string) StashRef { if IsRef(stashName) { prefix := PrefixForType(StashRefType) if strings.HasPrefix(stashName, prefix) { diff --git a/go/libraries/doltcore/sqle/database.go b/go/libraries/doltcore/sqle/database.go index 8b39cce636a..37a18079deb 100644 --- a/go/libraries/doltcore/sqle/database.go +++ b/go/libraries/doltcore/sqle/database.go @@ -582,6 +582,14 @@ func (db Database) getTableInsensitive(ctx *sql.Context, head *doltdb.Commit, ds if !resolve.UseSearchPath || isDoltgresSystemTable { dt, found = dtables.NewRemotesTable(ctx, db.ddb, lwrName), true } + case doltdb.StashesTableName, doltdb.GetStashesTableName(): + isDoltgresSystemTable, err := resolve.IsDoltgresSystemTable(ctx, tname, root) + if err != nil { + return nil, false, err + } + if !resolve.UseSearchPath || isDoltgresSystemTable { + dt, found = dtables.NewStashesTable(ctx, db.ddb, lwrName), true + } case doltdb.CommitsTableName, doltdb.GetCommitsTableName(): isDoltgresSystemTable, err := resolve.IsDoltgresSystemTable(ctx, tname, root) if err != nil { diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 0af85c67e22..b68010e4ac9 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -81,7 +81,7 @@ func doDoltStash(ctx *sql.Context, args []string) (string, error) { case "push": status, err = doStashPush(ctx, dSess, dbData, roots, apr) case "pop": - status, err = doStashPop(ctx, dbData, apr, roots) + status, err = doStashPop(ctx, dbData, apr) case "drop": status, err = doStashDrop(ctx, dbData, apr) case "clear": @@ -104,7 +104,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return "", err } if !hasChanges { - return "", fmt.Errorf("no local changes to save") //What should I do here? + return "", fmt.Errorf("no local changes to save") } roots, err = actions.StageModifiedAndDeletedTables(ctx, roots) @@ -155,7 +155,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return "", err } - err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage))) + err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), "stashes") if err != nil { return "", err } @@ -180,7 +180,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return status, nil } -func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparser.ArgParseResults, roots doltdb.Roots) (string, error) { +func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparser.ArgParseResults) (string, error) { var idx = 0 var err error if apr.NArg() == 2 { @@ -201,7 +201,7 @@ func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparse } curWorkingRoot := workingSet.WorkingRoot() - stashRoot, headCommit, meta, err := dbData.Ddb.GetStashRootAndHeadCommitAtIdx(ctx, idx) + stashRoot, headCommit, meta, err := dbData.Ddb.GetStashRootAndHeadCommitAtIdx(ctx, idx, "stashes") if err != nil { return "", err } @@ -257,38 +257,24 @@ func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparse return status, nil } - //err = dEnv.UpdateWorkingRoot(ctx, result.Root) - var h hash.Hash - var wsRef ref.WorkingSetRef - - ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) - if err == doltdb.ErrWorkingSetNotFound { - // first time updating root - wsRef, err = ref.WorkingSetRefForHead(headRef) - if err != nil { - return "", err - } - ws = doltdb.EmptyWorkingSet(wsRef).WithWorkingRoot(result.Root).WithStagedRoot(result.Root) - } else if err != nil { + err = updateWorkingRoot(ctx, dbData, result.Root) + if err != nil { return "", err - } else { - h, err = ws.HashOf() - if err != nil { - return "", err - } - - wsRef = ws.Ref() } - wsm := &datas.WorkingSetMeta{ - Timestamp: uint64(time.Now().Unix()), - Description: "updated from dolt environment", + headRoot, err := headCommit.GetRootValue(ctx) + if err != nil { + return "", err } - - err = dbData.Ddb.UpdateWorkingSet(ctx, wsRef, ws.WithWorkingRoot(result.Root), h, wsm, nil) + ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) if err != nil { return "", err } + roots := doltdb.Roots{ + Head: headRoot, + Working: ws.WorkingRoot(), + Staged: ws.StagedRoot(), + } // added tables need to be staged // since these tables are coming from a stash, don't filter for ignored table names. @@ -302,12 +288,12 @@ func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparse return "", err } - stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx) + stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, "stashes") if err != nil { return "", err } - err = dbData.Ddb.RemoveStashAtIdx(ctx, idx) + err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, "stashes") if err != nil { return "", err } @@ -327,12 +313,12 @@ func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argpars } } - stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx) + stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, "stashes") if err != nil { return "", err } - err = dbData.Ddb.RemoveStashAtIdx(ctx, idx) + err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, "stashes") if err != nil { return "", err } @@ -343,7 +329,7 @@ func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argpars } func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context]) error { - err := dbData.Ddb.RemoveAllStashes(ctx) + err := dbData.Ddb.RemoveAllStashes(ctx, "stashes") if err != nil { return err } @@ -528,3 +514,44 @@ func bulkDbEaFactory(dbData env.DbData[*sql.Context]) editor.DbEaFactory { } return editor.NewBulkImportTEAFactory(dbData.Ddb.ValueReadWriter(), tmpDir) } + +func updateWorkingRoot(ctx *sql.Context, dbData env.DbData[*sql.Context], newRoot doltdb.RootValue) error { + //err = dEnv.UpdateWorkingRoot(ctx, result.Root) + var h hash.Hash + var wsRef ref.WorkingSetRef + headRef, err := dbData.Rsr.CWBHeadRef(ctx) + if err != nil { + return err + } + + ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) + if err == doltdb.ErrWorkingSetNotFound { + // first time updating root + wsRef, err = ref.WorkingSetRefForHead(headRef) + if err != nil { + return err + } + ws = doltdb.EmptyWorkingSet(wsRef).WithWorkingRoot(newRoot).WithStagedRoot(newRoot) + } else if err != nil { + return err + } else { + h, err = ws.HashOf() + if err != nil { + return err + } + + wsRef = ws.Ref() + } + + wsm := &datas.WorkingSetMeta{ + Timestamp: uint64(time.Now().Unix()), + Description: "updated from dolt environment", + } + + err = dbData.Ddb.UpdateWorkingSet(ctx, wsRef, ws.WithWorkingRoot(newRoot), h, wsm, nil) + if err != nil { + return err + } + + return nil +} diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go new file mode 100644 index 00000000000..92be31f32f1 --- /dev/null +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -0,0 +1,210 @@ +// Copyright 2025 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 ( + "fmt" + "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/index" + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" + "io" +) + +const stashesDefaultRowCount = 5 + +var _ sql.Table = (*StashesTable)(nil) +var _ sql.UpdatableTable = (*StashesTable)(nil) +var _ sql.DeletableTable = (*StashesTable)(nil) +var _ sql.InsertableTable = (*StashesTable)(nil) +var _ sql.ReplaceableTable = (*StashesTable)(nil) +var _ sql.StatisticsTable = (*StashesTable)(nil) + +type StashesTable struct { + ddb *doltdb.DoltDB + tableName string +} + +func NewStashesTable(_ *sql.Context, ddb *doltdb.DoltDB, tableName string) sql.Table { + return &StashesTable{ddb, tableName} +} + +func (st *StashesTable) DataLength(ctx *sql.Context) (uint64, error) { + numBytesPerRow := schema.SchemaAvgLength(st.Schema()) + numRows, _, err := st.RowCount(ctx) + if err != nil { + return 0, err + } + return numBytesPerRow * numRows, nil +} + +func (st *StashesTable) RowCount(_ *sql.Context) (uint64, bool, error) { + return stashesDefaultRowCount, false, nil +} //Todo: What row count? + +// Name is a sql.Table interface function which returns the name of the table +func (st *StashesTable) Name() string { + return st.tableName +} + +// String is a sql.Table interface function which returns the name of the table +func (st *StashesTable) String() string { + return st.tableName +} + +// Schema is a sql.Table interface function that gets the sql.Schema of the remotes system table +func (st *StashesTable) Schema() sql.Schema { + return []*sql.Column{ + {Name: "stash reference", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "stash id", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "branch", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "hash", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "commit message", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: true}, + } +} + +// Collation implements the sql.Table interface. +func (st *StashesTable) Collation() sql.CollationID { + return sql.Collation_Default +} + +// Partitions is a sql.Table interface function that returns a partition of the data. Currently the data is unpartitioned. +func (st *StashesTable) Partitions(*sql.Context) (sql.PartitionIter, error) { + return index.SinglePartitionIterFromNomsMap(nil), nil +} + +// PartitionRows is a sql.Table interface function that gets a row iterator for a partition +func (st *StashesTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) { + return NewStashItr(ctx, st.ddb) +} + +func (st *StashesTable) Updater(ctx *sql.Context) sql.RowUpdater { + return stashWriter{st} +} + +func (st *StashesTable) Inserter(*sql.Context) sql.RowInserter { + return stashWriter{st} +} + +// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted, +// and will end with a call to Close() to finalize the delete operation. +func (st *StashesTable) Deleter(*sql.Context) sql.RowDeleter { + return stashWriter{st} +} + +// Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once +// for each row, followed by a call to Close() when all rows have been processed. +func (st *StashesTable) Replacer(ctx *sql.Context) sql.RowReplacer { + return stashWriter{st} +} + +type StashItr struct { + stashes []*doltdb.Stash + idx int +} + +// NewStashItr creates a StashItr from the current environment. +func NewStashItr(ctx *sql.Context, ddb *doltdb.DoltDB) (*StashItr, error) { + dbName := ctx.GetCurrentDatabase() + + if len(dbName) == 0 { + return nil, fmt.Errorf("Empty database name.") + } + + sess := dsess.DSessFromSess(ctx.Session) + dbData, ok := sess.GetDbData(ctx, dbName) + if !ok { + return nil, sql.ErrDatabaseNotFound.New(dbName) + } + + stashes, err := dbData.Ddb.GetStashes(ctx) + if err != nil { + return nil, err + } + + return &StashItr{stashes, 0}, nil +} + +// 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 *StashItr) Next(ctx *sql.Context) (sql.Row, error) { + if itr.idx >= len(itr.stashes) { + return nil, io.EOF + } + + defer func() { + itr.idx++ + }() + + stash := itr.stashes[itr.idx] + commitHash, err := stash.HeadCommit.HashOf() + if err != nil { + return nil, err + } + return sql.NewRow("stashes/stashes", stash.Name, stash.BranchName, commitHash.String(), stash.Description), nil +} + +// Close closes the iterator. +func (itr *StashItr) Close(*sql.Context) error { + return nil +} + +var _ sql.RowReplacer = stashWriter{nil} +var _ sql.RowUpdater = stashWriter{nil} +var _ sql.RowInserter = stashWriter{nil} +var _ sql.RowDeleter = stashWriter{nil} + +type stashWriter struct { + rt *StashesTable +} + +// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process +// for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close +// is called. +func (bWr stashWriter) Insert(ctx *sql.Context, r sql.Row) error { + return fmt.Errorf("the dolt_stashes table is read-only; use the dolt_stash stored procedure to edit remotes") +} + +// Update the given row. Provides both the old and new rows. +func (bWr stashWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error { + return fmt.Errorf("the dolt_stash table is read-only; use the dolt_stash stored procedure to edit remotes") +} + +// Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for +// each row to process for the delete operation, which may involve many rows. After all rows have been processed, +// Close is called. +func (bWr stashWriter) Delete(ctx *sql.Context, r sql.Row) error { + return fmt.Errorf("the dolt_stash table is read-only; use the dolt_stash stored procedure to edit remotes") +} + +// StatementBegin implements the interface sql.TableEditor. Currently a no-op. +func (bWr stashWriter) StatementBegin(ctx *sql.Context) {} + +// DiscardChanges implements the interface sql.TableEditor. Currently a no-op. +func (bWr stashWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error { + return nil +} + +// StatementComplete implements the interface sql.TableEditor. Currently a no-op. +func (bWr stashWriter) StatementComplete(ctx *sql.Context) error { + return nil +} + +// Close finalizes the delete operation, persisting the result. +func (bWr stashWriter) Close(*sql.Context) error { + return nil +} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go b/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go index 2971986b29d..16c450aaf66 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_engine_test.go @@ -2119,3 +2119,9 @@ func TestDoltHelpSystemTable(t *testing.T) { defer harness.Close() RunDoltHelpSystemTableTests(t, harness) } + +func TestDoltStash(t *testing.T) { + harness := newDoltEnginetestHarness(t) + defer harness.Close() + RunDoltStashSystemTableTests(t, harness) +} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go b/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go index aeb61a19083..21101586ab3 100755 --- a/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_engine_tests.go @@ -1992,3 +1992,13 @@ func RunDoltHelpSystemTableTests(t *testing.T, harness DoltEnginetestHarness) { }) } } + +func RunDoltStashSystemTableTests(t *testing.T, h DoltEnginetestHarness) { + for _, script := range DoltStashTests { + func() { + h := h.NewHarness(t) + defer h.Close() + enginetest.TestScript(t, h, script) + }() + } +} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index 4afe8b42883..5cf0b555eae 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -8142,3 +8142,203 @@ end; }, }, } + +var DoltStashTests = []queries.ScriptTest{ + { + Name: "DOLT_STASH() subcommands error on empty space.", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + ExpectedErrStr: "no local changes to save", + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + ExpectedErrStr: "No stash entries found.", + }, + { + Query: "CALL DOLT_STASH('drop', 'myStash');", + ExpectedErrStr: "No stash entries found.", + }, + { + Query: "CALL DOLT_STASH('clear','myStash');", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "Simple push and pop with DOLT_STASH()", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "SELECT * FROM test", + Expected: []sql.Row{{1, 'a'}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{{"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "Clearing stash removes all entries in stash list", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'anotherStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (3, 'c')", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'anotherStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM dolt_stashes;", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"stashes/anotherStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"stashes/anotherStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('clear', 'anotherStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM dolt_stashes;", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + }, + }, + { + Name: "Clearing and stashing again", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('clear', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + }, + }, + /*{ + Name: "Popping specific stashes", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (3, 'c');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + },{ + Query: "INSERT INTO test VALUES (4, 'd');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash', stash@{3}", + SkipResultsCheck: true + }, + { + Query: "SELECT * FROM DOLT_STASHES", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + + }, + },*/ +} From c042c0c073a51f58f4f0d749422a81eb8d3649e9 Mon Sep 17 00:00:00 2001 From: NathanGabrielson Date: Wed, 4 Jun 2025 20:50:17 +0000 Subject: [PATCH 03/14] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/sqle/dprocedures/dolt_stash.go | 10 ++++++---- go/libraries/doltcore/sqle/dtables/stashes_table.go | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index b68010e4ac9..10191e32788 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -17,6 +17,12 @@ package dprocedures import ( "context" "fmt" + "strconv" + "strings" + "time" + + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/libraries/doltcore/diff" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" @@ -29,10 +35,6 @@ import ( "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/store/datas" "github.com/dolthub/dolt/go/store/hash" - "github.com/dolthub/go-mysql-server/sql" - "strconv" - "strings" - "time" ) const ( diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index 92be31f32f1..e6ffcd743d3 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -16,13 +16,15 @@ package dtables import ( "fmt" + "io" + + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" + "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/index" - "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/types" - "io" ) const stashesDefaultRowCount = 5 From 406812d1e0ce4dec176e6fb3a56ca12c9b2c9f09 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Thu, 5 Jun 2025 16:34:44 -0700 Subject: [PATCH 04/14] Many engine tests & a (mostly) working procedure --- go/cmd/dolt/cli/arg_parser_helpers.go | 2 +- .../doltcore/sqle/dprocedures/dolt_stash.go | 298 +++++---- .../doltcore/sqle/enginetest/dolt_queries.go | 606 +++++++++++++++++- 3 files changed, 749 insertions(+), 157 deletions(-) diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index 7b6ef77ddd4..3c5b1457536 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -106,7 +106,7 @@ func CreateMergeArgParser() *argparser.ArgParser { } func CreateStashArgParser() *argparser.ArgParser { - ap := argparser.NewArgParserWithMaxArgs("stash", 2) + ap := argparser.NewArgParserWithMaxArgs("stash", 3) ap.SupportsFlag("include-untracked", "u", "Untracked tables are also stashed.") //TODO: Add Constant ap.SupportsFlag(AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") return ap diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 10191e32788..bcf62578f2f 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -74,20 +74,35 @@ func doDoltStash(ctx *sql.Context, args []string) (string, error) { return "", err } - if apr.NArg() == 0 { - return "", fmt.Errorf("error: invalid arguments. Must provide valid stash subcommand") + if apr.NArg() < 2 { + return "", fmt.Errorf("error: invalid arguments. Must provide valid subcommand and stash name") } var status string + stashName := apr.Arg(1) switch apr.Arg(0) { case "push": - status, err = doStashPush(ctx, dSess, dbData, roots, apr) + if apr.NArg() > 2 { // Push does not take extra arguments + return "", fmt.Errorf("error: invalid arguments. Push takes only subcommand and stash name=") + } + status, err = doStashPush(ctx, dSess, dbData, roots, apr, stashName) case "pop": - status, err = doStashPop(ctx, dbData, apr) + idx, err := parseStashIndex(apr.Arg(2)) + if err != nil { + return "", err + } + status, err = doStashPop(ctx, dbData, stashName, idx) case "drop": - status, err = doStashDrop(ctx, dbData, apr) + idx, err := parseStashIndex(apr.Arg(2)) + if err != nil { + return "", err + } + status, err = doStashDrop(ctx, dbData, stashName, idx) case "clear": - err = doStashClear(ctx, dbData) + if apr.NArg() > 2 { // Clear does not take extra arguments + return "", fmt.Errorf("error: invalid arguments. Clear takes only subcommand and stash name") + } + err = doStashClear(ctx, dbData, stashName) default: return "", fmt.Errorf("unknown stash subcommand %s", apr.Arg(0)) } @@ -99,9 +114,8 @@ func doDoltStash(ctx *sql.Context, args []string) (string, error) { return status, nil } -func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults) (string, error) { - - hasChanges, err := hasLocalChanges(ctx, dSess, dbData, roots, apr) +func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults, stashName string) (string, error) { + hasChanges, err := hasLocalChanges(ctx, dSess, roots, apr) if err != nil { return "", err } @@ -115,7 +129,6 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* } // all tables with changes that are going to be stashed are staged at this point - allTblsToBeStashed, addedTblsToStage, err := stashedTableSets(ctx, roots) if err != nil { return "", err @@ -133,31 +146,12 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* } } - curHeadRef, err := dbData.Rsr.CWBHeadRef(ctx) - if err != nil { - return "", err - } - - curBranchName := curHeadRef.String() - commitSpec, err := doltdb.NewCommitSpec(curBranchName) - if err != nil { - return "", err - } - optCmt, err := dbData.Ddb.Resolve(ctx, commitSpec, curHeadRef) - if err != nil { - return "", err - } - commit, ok := optCmt.ToCommit() - if !ok { - return "", doltdb.ErrGhostCommitEncountered - } - - commitMeta, err := commit.GetCommitMeta(ctx) + commit, commitMeta, curBranchName, err := gatherCommitData(ctx, dbData) if err != nil { return "", err } - err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), "stashes") + err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), apr.Arg(1)) if err != nil { return "", err } @@ -182,101 +176,21 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return status, nil } -func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparser.ArgParseResults) (string, error) { - var idx = 0 - var err error - if apr.NArg() == 2 { - idx, err = parseStashIndex(apr.Args[1]) - } - - headRef, err := dbData.Rsr.CWBHeadRef(ctx) - if err != nil { - return "", err - } - workingSetRef, err := ref.WorkingSetRefForHead(headRef) - if err != nil { - return "", err - } - workingSet, err := dbData.Ddb.ResolveWorkingSet(ctx, workingSetRef) - if err != nil { - return "", err - } - curWorkingRoot := workingSet.WorkingRoot() - - stashRoot, headCommit, meta, err := dbData.Ddb.GetStashRootAndHeadCommitAtIdx(ctx, idx, "stashes") +func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) (string, error) { + headCommit, result, meta, err := handleMerge(ctx, dbData, stashName, idx) if err != nil { return "", err } - hch, err := headCommit.HashOf() - if err != nil { - return "", err - } - headCommitSpec, err := doltdb.NewCommitSpec(hch.String()) - if err != nil { - return "", err - } - - optCmt, err := dbData.Ddb.Resolve(ctx, headCommitSpec, headRef) - if err != nil { - return "", err - } - parentCommit, ok := optCmt.ToCommit() - if !ok { - // Should not be possible to get into this situation. The parent of the stashed commit - // Must have been present at the time it was created - return "", doltdb.ErrGhostCommitEncountered - } - - parentRoot, err := parentCommit.GetRootValue(ctx) - if err != nil { - return "", err - } - - tmpDir, err := dbData.Rsw.TempTableFilesDir() - if err != nil { - return "", err - } - - opts := editor.Options{Deaf: bulkDbEaFactory(dbData), Tempdir: tmpDir} - result, err := merge.MergeRoots(ctx, curWorkingRoot, stashRoot, parentRoot, stashRoot, parentCommit, opts, merge.MergeOpts{IsCherryPick: false}) - if err != nil { - return "", err - } - - var tablesWithConflict []doltdb.TableName - for tbl, stats := range result.Stats { - if stats.HasConflicts() { - tablesWithConflict = append(tablesWithConflict, tbl) - } - } - - if len(tablesWithConflict) > 0 { - tblNames := strings.Join(doltdb.FlattenTableNames(tablesWithConflict), "', '") - status := fmt.Sprintf("error: Your local changes to the following tables would be overwritten by applying stash %d:\n"+ - "\t{'%s'}\n"+ - "Please commit your changes or stash them before you merge.\nAborting\n", idx, tblNames) - return status, nil - } - err = updateWorkingRoot(ctx, dbData, result.Root) if err != nil { return "", err } - headRoot, err := headCommit.GetRootValue(ctx) - if err != nil { - return "", err - } - ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) + roots, err := getRoots(ctx, dbData, headCommit) if err != nil { return "", err } - roots := doltdb.Roots{ - Head: headRoot, - Working: ws.WorkingRoot(), - Staged: ws.StagedRoot(), - } // added tables need to be staged // since these tables are coming from a stash, don't filter for ignored table names. @@ -290,12 +204,12 @@ func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparse return "", err } - stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, "stashes") + stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, stashName) if err != nil { return "", err } - err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, "stashes") + err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) if err != nil { return "", err } @@ -304,23 +218,13 @@ func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparse return status, err } -func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argparser.ArgParseResults) (string, error) { - var idx = 0 - var err error - - if apr.NArg() == 2 { - idx, err = parseStashIndex(apr.Args[1]) - if err != nil { - return "", err - } - } - - stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, "stashes") +func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) (string, error) { + stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, stashName) if err != nil { return "", err } - err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, "stashes") + err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) if err != nil { return "", err } @@ -330,8 +234,8 @@ func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], apr *argpars return status, nil } -func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context]) error { - err := dbData.Ddb.RemoveAllStashes(ctx, "stashes") +func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string) error { + err := dbData.Ddb.RemoveAllStashes(ctx, stashName) if err != nil { return err } @@ -360,7 +264,7 @@ func stashedTableSets(ctx context.Context, roots doltdb.Roots) ([]doltdb.TableNa return allTbls, addedTblsInStaged, nil } -func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { +func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { dbName := ctx.GetCurrentDatabase() headCommit, err := dSess.GetHeadCommit(ctx, dbName) @@ -498,12 +402,12 @@ func updateWorkingSetFromRoots(ctx *sql.Context, dbData env.DbData[*sql.Context] return nil } -func parseStashIndex(stashName string) (int, error) { +func parseStashIndex(stashID string) (int, error) { var idx = 0 - stashName = strings.TrimSuffix(strings.TrimPrefix(stashName, "stash@{"), "}") - idx, err := strconv.Atoi(stashName) + stashID = strings.TrimSuffix(strings.TrimPrefix(stashID, "stash@{"), "}") + idx, err := strconv.Atoi(stashID) if err != nil { - return 0, fmt.Errorf("error: %s is not a valid reference", stashName) + return 0, fmt.Errorf("error: %s is not a valid reference", stashID) } return idx, nil @@ -557,3 +461,125 @@ func updateWorkingRoot(ctx *sql.Context, dbData env.DbData[*sql.Context], newRoo return nil } + +func gatherCommitData(ctx *sql.Context, dbData env.DbData[*sql.Context]) (*doltdb.Commit, *datas.CommitMeta, string, error) { + curHeadRef, err := dbData.Rsr.CWBHeadRef(ctx) + if err != nil { + return nil, nil, "", err + } + + curBranchName := curHeadRef.String() + commitSpec, err := doltdb.NewCommitSpec(curBranchName) + if err != nil { + return nil, nil, "", err + } + optCmt, err := dbData.Ddb.Resolve(ctx, commitSpec, curHeadRef) + if err != nil { + return nil, nil, "", err + } + commit, ok := optCmt.ToCommit() + if !ok { + return nil, nil, "", doltdb.ErrGhostCommitEncountered + } + + commitMeta, err := commit.GetCommitMeta(ctx) + if err != nil { + return nil, nil, "", err + } + + return commit, commitMeta, curBranchName, nil + +} + +func handleMerge(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) (*doltdb.Commit, *merge.Result, *datas.StashMeta, error) { + headRef, err := dbData.Rsr.CWBHeadRef(ctx) + if err != nil { + return nil, nil, nil, err + } + workingSetRef, err := ref.WorkingSetRefForHead(headRef) + if err != nil { + return nil, nil, nil, err + } + workingSet, err := dbData.Ddb.ResolveWorkingSet(ctx, workingSetRef) + if err != nil { + return nil, nil, nil, err + } + curWorkingRoot := workingSet.WorkingRoot() + + stashRoot, headCommit, meta, err := dbData.Ddb.GetStashRootAndHeadCommitAtIdx(ctx, idx, stashName) + if err != nil { + return nil, nil, nil, err + } + + hch, err := headCommit.HashOf() + if err != nil { + return nil, nil, nil, err + } + headCommitSpec, err := doltdb.NewCommitSpec(hch.String()) + if err != nil { + return nil, nil, nil, err + } + + optCmt, err := dbData.Ddb.Resolve(ctx, headCommitSpec, headRef) + if err != nil { + return nil, nil, nil, err + } + parentCommit, ok := optCmt.ToCommit() + if !ok { + // Should not be possible to get into this situation. The parent of the stashed commit + // Must have been present at the time it was created + return nil, nil, nil, err + } + + parentRoot, err := parentCommit.GetRootValue(ctx) + if err != nil { + return nil, nil, nil, err + } + + tmpDir, err := dbData.Rsw.TempTableFilesDir() + if err != nil { + return nil, nil, nil, err + } + + opts := editor.Options{Deaf: bulkDbEaFactory(dbData), Tempdir: tmpDir} + result, err := merge.MergeRoots(ctx, curWorkingRoot, stashRoot, parentRoot, stashRoot, parentCommit, opts, merge.MergeOpts{IsCherryPick: false}) + if err != nil { + return nil, nil, nil, err + } + + var tablesWithConflict []doltdb.TableName + for tbl, stats := range result.Stats { + if stats.HasConflicts() { + tablesWithConflict = append(tablesWithConflict, tbl) + } + } + + if len(tablesWithConflict) > 0 { + tblNames := strings.Join(doltdb.FlattenTableNames(tablesWithConflict), "', '") + status := fmt.Errorf("error: Your local changes to the following tables would be overwritten by applying stash %d:\n"+ + "\t{'%s'}\n"+ + "Please commit your changes or stash them before you merge.\nAborting\n", idx, tblNames) + return nil, nil, nil, status + } + + return headCommit, result, meta, nil +} + +func getRoots(ctx *sql.Context, dbData env.DbData[*sql.Context], headCommit *doltdb.Commit) (doltdb.Roots, error) { + roots := doltdb.Roots{} + + headRoot, err := headCommit.GetRootValue(ctx) + if err != nil { + return roots, err + } + ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) + if err != nil { + return roots, err + } + + roots.Head = headRoot + roots.Working = ws.WorkingRoot() + roots.Staged = ws.StagedRoot() + + return roots, nil +} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index 5cf0b555eae..618cfd377d1 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -8147,6 +8147,14 @@ var DoltStashTests = []queries.ScriptTest{ { Name: "DOLT_STASH() subcommands error on empty space.", Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + ExpectedErrStr: "no local changes to save", + }, + { + Query: "CREATE TABLE test (i int)", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, { Query: "CALL DOLT_STASH('push', 'myStash');", ExpectedErrStr: "no local changes to save", @@ -8289,7 +8297,7 @@ var DoltStashTests = []queries.ScriptTest{ }, }, }, - /*{ + { Name: "Popping specific stashes", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", @@ -8298,47 +8306,605 @@ var DoltStashTests = []queries.ScriptTest{ }, Assertions: []queries.ScriptTestAssertion{ { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "CALL DOLT_STASH('push', 'myStash');", + Query: "CALL DOLT_STASH('push', 'myStash');", SkipResultsCheck: true, }, { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "CALL DOLT_STASH('push', 'myStash');", + Query: "CALL DOLT_STASH('push', 'myStash');", SkipResultsCheck: true, }, { - Query: "INSERT INTO test VALUES (3, 'c');", - Expected: []sql.Row{{types.NewOkResult(1)}}, + Query: "INSERT INTO test VALUES (3, 'c');", + Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "CALL DOLT_STASH('push', 'myStash');", + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, { + Query: "INSERT INTO test VALUES (4, 'd');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", SkipResultsCheck: true, - },{ - Query: "INSERT INTO test VALUES (4, 'd');", - Expected: []sql.Row{{types.NewOkResult(1)}}, }, { - Query: "CALL DOLT_STASH('push', 'myStash');", + Query: "CALL DOLT_STASH('pop', 'myStash', 'stash@{3}')", SkipResultsCheck: true, }, { - Query: "CALL DOLT_STASH('pop', 'myStash', stash@{3}", - SkipResultsCheck: true + Query: "CALL DOLT_STASH('pop', 'myStash', 'stash@{1}');", + SkipResultsCheck: true, }, { - Query: "SELECT * FROM DOLT_STASHES", - Expected: []sql.Row{ + Query: "SELECT * FROM test", + Expected: []sql.Row{ + {1, 'a'}, + {4, 'd'}, + }, + }, + }, + }, + { + Name: "Stashing on different branches", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('-b', 'br1');", + Expected: []sql.Row{{0, "Switched to branch 'br1'"}}, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Created table"}, {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, }, }, - }, - },*/ + }, + { + Name: "Popping stash onto different branch", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES();", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_CHECKOUT('-b', 'br1');", + Expected: []sql.Row{{0, "Switched to branch 'br1'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM TEST;", + Expected: []sql.Row{ + {1, 'a'}, + }, + }, + }, + }, + { + Name: "Can drop specific stash", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_COMMIT('-a', '-m', 'Added 2 b');", + Expected: []sql.Row{{doltCommit}}, + }, + { + Query: "INSERT INTO test VALUES (3, 'c');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES();", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Added 2 b"}, + {"stashes/myStash", "stash@{1}", "refs/heads/br1", doltCommit, "Created table"}, + }, + }, + { + Query: "INSERT INTO test VALUES (4, 'd');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_COMMIT('-a','-m', 'Added 4 d');", + Expected: []sql.Row{{doltCommit}}, + }, + { + Query: "INSERT INTO test VALUES (5, 'c');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_STASH('drop', 'myStash', 'stash@{1}');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES();", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Added 4 d"}, + {"stashes/myStash", "stash@{1}", "refs/heads/br1", doltCommit, "Created table"}, + }, + }, + }, + }, + { + Name: "Can pop into dirty working set without conflict", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash')", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM test", + Expected: []sql.Row{ + {1, 'a'}, + {2, 'b'}, + }, + }, + }, + }, + { + Name: "Can't pop into dirty working set with conflict", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (1, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + ExpectedErrStr: "error: Your local changes to the following tables would be overwritten by applying stash 0:\n\t{'test'}\n" + + "Please commit your changes or stash them before you merge.\nAborting\n", + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Created table"}, + }, + }, + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{ + {1, 'a'}, + }, + }, + }, + }, + { + Name: "Can stash modified staged and working set of changes", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_ADD('.')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"t", true, "modified"}, + {"t", false, "modified"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM dolt_status;", + Expected: []sql.Row{ + {"t", false, "modified"}, + {"t", false, "modified"}, + }, + }, + }, + }, + { + Name: "Can use --all and --include-untracked on push", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CREATE TABLE new(id int primary key)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", true, "new table"}, + {"new", false, "new table"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash', '--include-untracked');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{ + {"test", true, "new table"}, + {"new", false, "new table"}, + }, + }, + }, + }, + { + Name: "Stash with tracked and untracked tables", + SetUpScript: []string{ + "CREATE TABLE new(i INT PRIMARY KEY)", + "CALL DOLT_ADD('.')", + "INSERT INTO new VALUES (1),(2)", + "CREATE TABLE test(id INT)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"new", true, "new table"}, + {"test", false, "new table"}, + {"new", false, "modified"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash')", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", false, "new table"}, + }, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"new", true, "new table"}, + {"test", false, "new table"}, + }, + }, + }, + }, + { + Name: "stashing working set with deleted table and popping it", + SetUpScript: []string{ + "CREATE TABLE new_tab(id INT PRIMARY KEY)", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", + "DROP TABLE new_tab", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STATUS;", + Expected: []sql.Row{ + {"new_tab", false, "deleted"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SHOW TABLES;", + Expected: []sql.Row{ + {"new_tab"}, + }, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SHOW TABLES;", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "simple stashing and popping stash after running GC", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{ + {1, 'a'}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_GC();", + Expected: []sql.Row{{1}}, + }, + { + Query: "SELECT * FROM DOLT_STASHES", + Expected: []sql.Row{ + {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + Expected: []sql.Row{}, + }, + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{ + {1, 'a'}, + {2, 'b'}, + }, + }, + }, + }, + { + Name: "popping stash with deleted table that is deleted already on current head", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1');", + "CALL DOLT_CHECKOUT('-b', 'branch2');", + "DROP TABLE test;", + "CALL DOLT_COMMIT('A','-m','Dropped test');", + "CALL DOLT_CHECKOUT('branch1');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SHOW TABLES;", + Expected: []sql.Row{ + {"test"}, + }, + }, + { + Query: "DROP TABLE test;", + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "popping stash with deleted table that the same table exists on current head", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1');", + "CALL DOLT_BRANCH('branch2');", + "CALL DOLT_CHECKOUT('branch1');", + "DROP TABLE test;", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", false, "deleted"}, + }, + }, + }, + }, + { + Name: "popping stash with deleted table that different table with same name on current head gives conflict", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1')", + "CALL DOLT_BRANCH('branch2')", + "CALL DOLT_CHECKOUT('branch1')", + "DROP TABLE test;", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push, 'myStash')", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "DROP TABLE test;", + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CREATE TABLE test (id BIGINT PRIMARY KEY);", + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + ExpectedErrStr: "merge aborted: schema conflict found for table t\n " + + "please resolve schema conflicts before merging:\n" + + "\ttable was modified in one branch and deleted in the other", + }, + }, + }, + { + Name: "popping stash with added table with PK on current head with the exact same table is added already", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1')", + "CALL DOLT_CHECKOUT('-b', 'branch2')", + "CREATE TABLE new_test(id INT PRIMARY KEY)", + "INSERT INTO new_test VALUES (1)", + "CALL DOLT_COMMIT('-A', '-m', 'Created new_test')", + "CALL DOLT_CHECKOUT('branch1')", + "CREATE TABLE new_test(id INT PRIMARY KEY)", + "INSERT INTO new_test VALUES (1)", + "CALL DOLT_ADD('.')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{}, + }, + }, + }, } From f2bb0baf71bfd7972b6006e7eb8c318ac04e176c Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Fri, 6 Jun 2025 17:52:34 -0700 Subject: [PATCH 05/14] Better tests & documentatioin & working stash reference --- go/cmd/dolt/commands/stashcmds/clear.go | 2 +- go/cmd/dolt/commands/stashcmds/drop.go | 4 +- go/cmd/dolt/commands/stashcmds/pop.go | 2 +- go/cmd/dolt/commands/stashcmds/stash.go | 3 +- go/libraries/doltcore/doltdb/doltdb.go | 5 +- go/libraries/doltcore/doltdb/stash.go | 12 +- .../doltcore/sqle/dprocedures/dolt_stash.go | 40 +- .../doltcore/sqle/dtables/stashes_table.go | 26 +- .../doltcore/sqle/enginetest/dolt_queries.go | 766 ------------------ .../sqle/enginetest/dolt_queries_stash.go | 613 ++++++++++++++ 10 files changed, 656 insertions(+), 817 deletions(-) create mode 100644 go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go diff --git a/go/cmd/dolt/commands/stashcmds/clear.go b/go/cmd/dolt/commands/stashcmds/clear.go index 6c2d796c767..464e4b9f8ce 100644 --- a/go/cmd/dolt/commands/stashcmds/clear.go +++ b/go/cmd/dolt/commands/stashcmds/clear.go @@ -77,7 +77,7 @@ func (cmd StashClearCmd) Exec(ctx context.Context, commandStr string, args []str return 1 } - err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx, "dolt-cli") + err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx, DoltCliRef) if err != nil { return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } diff --git a/go/cmd/dolt/commands/stashcmds/drop.go b/go/cmd/dolt/commands/stashcmds/drop.go index a4e0e430c8b..3eb536ecbd9 100644 --- a/go/cmd/dolt/commands/stashcmds/drop.go +++ b/go/cmd/dolt/commands/stashcmds/drop.go @@ -95,12 +95,12 @@ func (cmd StashDropCmd) Exec(ctx context.Context, commandStr string, args []stri } func dropStashAtIdx(ctx context.Context, dEnv *env.DoltEnv, idx int) error { - stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, "dolt-cli") + stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, DoltCliRef) if err != nil { return err } - err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx, "dolt-cli") + err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx, DoltCliRef) if err != nil { return err } diff --git a/go/cmd/dolt/commands/stashcmds/pop.go b/go/cmd/dolt/commands/stashcmds/pop.go index 060f4d6de38..89ca06d54cf 100644 --- a/go/cmd/dolt/commands/stashcmds/pop.go +++ b/go/cmd/dolt/commands/stashcmds/pop.go @@ -125,7 +125,7 @@ func (cmd StashPopCmd) Exec(ctx context.Context, commandStr string, args []strin } func applyStashAtIdx(ctx *sql.Context, dEnv *env.DoltEnv, curWorkingRoot doltdb.RootValue, idx int) (bool, error) { - stashRoot, headCommit, meta, err := dEnv.DoltDB(ctx).GetStashRootAndHeadCommitAtIdx(ctx, idx, "dolt-cli") + stashRoot, headCommit, meta, err := dEnv.DoltDB(ctx).GetStashRootAndHeadCommitAtIdx(ctx, idx, DoltCliRef) if err != nil { return false, err } diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 5942a30d93d..2d6db25a079 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -42,6 +42,7 @@ var StashCommands = cli.NewSubCommandHandlerWithUnspecified("stash", "Stash the const ( IncludeUntrackedFlag = "include-untracked" AllFlag = "all" + DoltCliRef = "dolt-cli" ) var stashDocs = cli.CommandDocumentationContent{ @@ -242,7 +243,7 @@ func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars return err } - err = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), "dolt-cli") + err = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), DoltCliRef) if err != nil { return err } diff --git a/go/libraries/doltcore/doltdb/doltdb.go b/go/libraries/doltcore/doltdb/doltdb.go index c0a6420a9e2..b2306ab9212 100644 --- a/go/libraries/doltcore/doltdb/doltdb.go +++ b/go/libraries/doltcore/doltdb/doltdb.go @@ -2213,11 +2213,12 @@ func (ddb *DoltDB) GetStashes(ctx context.Context) ([]*Stash, error) { } var stashList []*Stash for _, stash := range stashRefs { - stashDS, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stash.String()).String()) + reference := ref.NewStashRef(stash.String()).String() + stashDS, err := ddb.db.GetDataset(ctx, reference) if err != nil { return nil, err } - newStashes, err := getStashList(ctx, stashDS, ddb.vrw, ddb.NodeStore()) + newStashes, err := getStashList(ctx, stashDS, ddb.vrw, ddb.NodeStore(), reference) if err != nil { return nil, err } diff --git a/go/libraries/doltcore/doltdb/stash.go b/go/libraries/doltcore/doltdb/stash.go index c4e6c4b2131..1d1cb5c0625 100644 --- a/go/libraries/doltcore/doltdb/stash.go +++ b/go/libraries/doltcore/doltdb/stash.go @@ -26,14 +26,15 @@ import ( ) type Stash struct { - Name string - BranchName string - Description string - HeadCommit *Commit + Name string + BranchName string + Description string + HeadCommit *Commit + StashReference string } // getStashList returns array of Stash objects containing all stash entries in the stash list map. -func getStashList(ctx context.Context, ds datas.Dataset, vrw types.ValueReadWriter, ns tree.NodeStore) ([]*Stash, error) { +func getStashList(ctx context.Context, ds datas.Dataset, vrw types.ValueReadWriter, ns tree.NodeStore, reference string) ([]*Stash, error) { v, ok := ds.MaybeHead() if !ok { return nil, errors.New("stashes not found") @@ -75,6 +76,7 @@ func getStashList(ctx context.Context, ds datas.Dataset, vrw types.ValueReadWrit s.HeadCommit = headCommit s.BranchName = meta.BranchName s.Description = meta.Description + s.StashReference = reference sl[i] = &s } diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index bcf62578f2f..189cf6ef6c1 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -79,21 +79,25 @@ func doDoltStash(ctx *sql.Context, args []string) (string, error) { } var status string + + cmdName := apr.Arg(0) stashName := apr.Arg(1) - switch apr.Arg(0) { + idx, err := parseStashIndex(apr) + if err != nil { + return "", err + } + + switch cmdName { case "push": if apr.NArg() > 2 { // Push does not take extra arguments return "", fmt.Errorf("error: invalid arguments. Push takes only subcommand and stash name=") } status, err = doStashPush(ctx, dSess, dbData, roots, apr, stashName) case "pop": - idx, err := parseStashIndex(apr.Arg(2)) - if err != nil { - return "", err - } status, err = doStashPop(ctx, dbData, stashName, idx) case "drop": - idx, err := parseStashIndex(apr.Arg(2)) + var idx int + idx, err = parseStashIndex(apr) if err != nil { return "", err } @@ -104,7 +108,7 @@ func doDoltStash(ctx *sql.Context, args []string) (string, error) { } err = doStashClear(ctx, dbData, stashName) default: - return "", fmt.Errorf("unknown stash subcommand %s", apr.Arg(0)) + return "", fmt.Errorf("unknown stash subcommand %s", cmdName) } if err != nil { @@ -151,7 +155,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return "", err } - err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), apr.Arg(1)) + err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), stashName) if err != nil { return "", err } @@ -402,12 +406,18 @@ func updateWorkingSetFromRoots(ctx *sql.Context, dbData env.DbData[*sql.Context] return nil } -func parseStashIndex(stashID string) (int, error) { - var idx = 0 - stashID = strings.TrimSuffix(strings.TrimPrefix(stashID, "stash@{"), "}") - idx, err := strconv.Atoi(stashID) - if err != nil { - return 0, fmt.Errorf("error: %s is not a valid reference", stashID) +func parseStashIndex(apr *argparser.ArgParseResults) (int, error) { + idx := 0 + + if apr.NArg() > 2 { + stashID := apr.Arg(2) + var err error + + stashID = strings.TrimSuffix(strings.TrimPrefix(stashID, "stash@{"), "}") + idx, err = strconv.Atoi(stashID) + if err != nil { + return 0, fmt.Errorf("error: %s is not a valid reference", stashID) + } } return idx, nil @@ -462,6 +472,8 @@ func updateWorkingRoot(ctx *sql.Context, dbData env.DbData[*sql.Context], newRoo return nil } +// gatherCommitData is a helper function that returns the commit and commit metadata associated with the current head +// reference as well as the current branch in the form of a string. func gatherCommitData(ctx *sql.Context, dbData env.DbData[*sql.Context]) (*doltdb.Commit, *datas.CommitMeta, string, error) { curHeadRef, err := dbData.Rsr.CWBHeadRef(ctx) if err != nil { diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index e6ffcd743d3..d2f43523823 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -30,10 +30,6 @@ import ( const stashesDefaultRowCount = 5 var _ sql.Table = (*StashesTable)(nil) -var _ sql.UpdatableTable = (*StashesTable)(nil) -var _ sql.DeletableTable = (*StashesTable)(nil) -var _ sql.InsertableTable = (*StashesTable)(nil) -var _ sql.ReplaceableTable = (*StashesTable)(nil) var _ sql.StatisticsTable = (*StashesTable)(nil) type StashesTable struct { @@ -94,26 +90,6 @@ func (st *StashesTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql return NewStashItr(ctx, st.ddb) } -func (st *StashesTable) Updater(ctx *sql.Context) sql.RowUpdater { - return stashWriter{st} -} - -func (st *StashesTable) Inserter(*sql.Context) sql.RowInserter { - return stashWriter{st} -} - -// Deleter returns a RowDeleter for this table. The RowDeleter will get one call to Delete for each row to be deleted, -// and will end with a call to Close() to finalize the delete operation. -func (st *StashesTable) Deleter(*sql.Context) sql.RowDeleter { - return stashWriter{st} -} - -// Replacer returns a RowReplacer for this table. The RowReplacer will have Insert and optionally Delete called once -// for each row, followed by a call to Close() when all rows have been processed. -func (st *StashesTable) Replacer(ctx *sql.Context) sql.RowReplacer { - return stashWriter{st} -} - type StashItr struct { stashes []*doltdb.Stash idx int @@ -157,7 +133,7 @@ func (itr *StashItr) Next(ctx *sql.Context) (sql.Row, error) { if err != nil { return nil, err } - return sql.NewRow("stashes/stashes", stash.Name, stash.BranchName, commitHash.String(), stash.Description), nil + return sql.NewRow(stash.StashReference, stash.Name, stash.BranchName, commitHash.String(), stash.Description), nil } // Close closes the iterator. diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index 618cfd377d1..4afe8b42883 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -8142,769 +8142,3 @@ end; }, }, } - -var DoltStashTests = []queries.ScriptTest{ - { - Name: "DOLT_STASH() subcommands error on empty space.", - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL DOLT_STASH('push', 'myStash');", - ExpectedErrStr: "no local changes to save", - }, - { - Query: "CREATE TABLE test (i int)", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - ExpectedErrStr: "no local changes to save", - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - ExpectedErrStr: "No stash entries found.", - }, - { - Query: "CALL DOLT_STASH('drop', 'myStash');", - ExpectedErrStr: "No stash entries found.", - }, - { - Query: "CALL DOLT_STASH('clear','myStash');", - Expected: []sql.Row{}, - }, - }, - }, - { - Name: "Simple push and pop with DOLT_STASH()", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "SELECT * FROM test", - Expected: []sql.Row{{1, 'a'}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES;", - Expected: []sql.Row{{"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES", - Expected: []sql.Row{}, - }, - }, - }, - { - Name: "Clearing stash removes all entries in stash list", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a')", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (2, 'b')", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'anotherStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (3, 'c')", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'anotherStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM dolt_stashes;", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - {"stashes/anotherStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - {"stashes/anotherStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - { - Query: "CALL DOLT_STASH('clear', 'anotherStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM dolt_stashes;", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - }, - }, - { - Name: "Clearing and stashing again", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES;", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - { - Query: "CALL DOLT_STASH('clear', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "SELECT * FROM DOLT_STASHES;", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - }, - }, - { - Name: "Popping specific stashes", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (3, 'c');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, { - Query: "INSERT INTO test VALUES (4, 'd');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash', 'stash@{3}')", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash', 'stash@{1}');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM test", - Expected: []sql.Row{ - {1, 'a'}, - {4, 'd'}, - }, - }, - }, - }, - { - Name: "Stashing on different branches", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_CHECKOUT('-b', 'br1');", - Expected: []sql.Row{{0, "Switched to branch 'br1'"}}, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES;", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Created table"}, - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - }, - }, - { - Name: "Popping stash onto different branch", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES();", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - { - Query: "CALL DOLT_CHECKOUT('-b', 'br1');", - Expected: []sql.Row{{0, "Switched to branch 'br1'"}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM TEST;", - Expected: []sql.Row{ - {1, 'a'}, - }, - }, - }, - }, - { - Name: "Can drop specific stash", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_COMMIT('-a', '-m', 'Added 2 b');", - Expected: []sql.Row{{doltCommit}}, - }, - { - Query: "INSERT INTO test VALUES (3, 'c');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES();", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Added 2 b"}, - {"stashes/myStash", "stash@{1}", "refs/heads/br1", doltCommit, "Created table"}, - }, - }, - { - Query: "INSERT INTO test VALUES (4, 'd');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_COMMIT('-a','-m', 'Added 4 d');", - Expected: []sql.Row{{doltCommit}}, - }, - { - Query: "INSERT INTO test VALUES (5, 'c');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_STASH('drop', 'myStash', 'stash@{1}');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STASHES();", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Added 4 d"}, - {"stashes/myStash", "stash@{1}", "refs/heads/br1", doltCommit, "Created table"}, - }, - }, - }, - }, - { - Name: "Can pop into dirty working set without conflict", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash')", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM test", - Expected: []sql.Row{ - {1, 'a'}, - {2, 'b'}, - }, - }, - }, - }, - { - Name: "Can't pop into dirty working set with conflict", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (1, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - ExpectedErrStr: "error: Your local changes to the following tables would be overwritten by applying stash 0:\n\t{'test'}\n" + - "Please commit your changes or stash them before you merge.\nAborting\n", - }, - { - Query: "SELECT * FROM DOLT_STASHES;", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Created table"}, - }, - }, - { - Query: "SELECT * FROM test;", - Expected: []sql.Row{ - {1, 'a'}, - }, - }, - }, - }, - { - Name: "Can stash modified staged and working set of changes", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - "INSERT INTO test VALUES (1, 'a')", - "CALL DOLT_ADD('.')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{ - {"t", true, "modified"}, - {"t", false, "modified"}, - }, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM test;", - Expected: []sql.Row{}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM dolt_status;", - Expected: []sql.Row{ - {"t", false, "modified"}, - {"t", false, "modified"}, - }, - }, - }, - }, - { - Name: "Can use --all and --include-untracked on push", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CREATE TABLE new(id int primary key)", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{ - {"test", true, "new table"}, - {"new", false, "new table"}, - }, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash', '--include-untracked');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM test;", - Expected: []sql.Row{ - {"test", true, "new table"}, - {"new", false, "new table"}, - }, - }, - }, - }, - { - Name: "Stash with tracked and untracked tables", - SetUpScript: []string{ - "CREATE TABLE new(i INT PRIMARY KEY)", - "CALL DOLT_ADD('.')", - "INSERT INTO new VALUES (1),(2)", - "CREATE TABLE test(id INT)", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{ - {"new", true, "new table"}, - {"test", false, "new table"}, - {"new", false, "modified"}, - }, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash')", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{ - {"test", false, "new table"}, - }, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{ - {"new", true, "new table"}, - {"test", false, "new table"}, - }, - }, - }, - }, - { - Name: "stashing working set with deleted table and popping it", - SetUpScript: []string{ - "CREATE TABLE new_tab(id INT PRIMARY KEY)", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-A', '-m', 'Created table')", - "DROP TABLE new_tab", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL DOLT_STATUS;", - Expected: []sql.Row{ - {"new_tab", false, "deleted"}, - }, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SHOW TABLES;", - Expected: []sql.Row{ - {"new_tab"}, - }, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SHOW TABLES;", - Expected: []sql.Row{}, - }, - }, - }, - { - Name: "simple stashing and popping stash after running GC", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - "INSERT INTO test VALUES (1, 'a')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SELECT * FROM test;", - Expected: []sql.Row{ - {1, 'a'}, - }, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_GC();", - Expected: []sql.Row{{1}}, - }, - { - Query: "SELECT * FROM DOLT_STASHES", - Expected: []sql.Row{ - {"stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - }, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - Expected: []sql.Row{}, - }, - { - Query: "SELECT * FROM test;", - Expected: []sql.Row{ - {1, 'a'}, - {2, 'b'}, - }, - }, - }, - }, - { - Name: "popping stash with deleted table that is deleted already on current head", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - "CALL DOLT_BRANCH('branch1');", - "CALL DOLT_CHECKOUT('-b', 'branch2');", - "DROP TABLE test;", - "CALL DOLT_COMMIT('A','-m','Dropped test');", - "CALL DOLT_CHECKOUT('branch1');", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "SHOW TABLES;", - Expected: []sql.Row{ - {"test"}, - }, - }, - { - Query: "DROP TABLE test;", - Expected: []sql.Row{{types.NewOkResult(0)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_CHECKOUT('branch2');", - Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{}, - }, - }, - }, - { - Name: "popping stash with deleted table that the same table exists on current head", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - "CALL DOLT_BRANCH('branch1');", - "CALL DOLT_BRANCH('branch2');", - "CALL DOLT_CHECKOUT('branch1');", - "DROP TABLE test;", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_CHECKOUT('branch2');", - Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{ - {"test", false, "deleted"}, - }, - }, - }, - }, - { - Name: "popping stash with deleted table that different table with same name on current head gives conflict", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - "CALL DOLT_BRANCH('branch1')", - "CALL DOLT_BRANCH('branch2')", - "CALL DOLT_CHECKOUT('branch1')", - "DROP TABLE test;", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL DOLT_STASH('push, 'myStash')", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_CHECKOUT('branch2');", - Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, - }, - { - Query: "DROP TABLE test;", - Expected: []sql.Row{{types.NewOkResult(0)}}, - }, - { - Query: "CREATE TABLE test (id BIGINT PRIMARY KEY);", - Expected: []sql.Row{{types.NewOkResult(0)}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - ExpectedErrStr: "merge aborted: schema conflict found for table t\n " + - "please resolve schema conflicts before merging:\n" + - "\ttable was modified in one branch and deleted in the other", - }, - }, - }, - { - Name: "popping stash with added table with PK on current head with the exact same table is added already", - SetUpScript: []string{ - "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", - "CALL DOLT_BRANCH('branch1')", - "CALL DOLT_CHECKOUT('-b', 'branch2')", - "CREATE TABLE new_test(id INT PRIMARY KEY)", - "INSERT INTO new_test VALUES (1)", - "CALL DOLT_COMMIT('-A', '-m', 'Created new_test')", - "CALL DOLT_CHECKOUT('branch1')", - "CREATE TABLE new_test(id INT PRIMARY KEY)", - "INSERT INTO new_test VALUES (1)", - "CALL DOLT_ADD('.')", - }, - Assertions: []queries.ScriptTestAssertion{ - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_CHECKOUT('branch2');", - Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, - }, - { - Query: "CALL DOLT_STASH('pop', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "SELECT * FROM DOLT_STATUS", - Expected: []sql.Row{}, - }, - }, - }, -} diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go new file mode 100644 index 00000000000..49ca087845b --- /dev/null +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go @@ -0,0 +1,613 @@ +// Copyright 2025 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 enginetest + +import ( + "github.com/dolthub/go-mysql-server/enginetest/queries" + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" +) + +var DoltStashTests = []queries.ScriptTest{ + { + Name: "DOLT_STASH() subcommands error on empty space.", + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + ExpectedErrStr: "no local changes to save", + }, + { + Query: "CREATE TABLE test (i int)", + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + ExpectedErrStr: "no local changes to save", + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + ExpectedErrStr: "No stash entries found.", + }, + { + Query: "CALL DOLT_STASH('drop', 'myStash');", + ExpectedErrStr: "No stash entries found.", + }, + { + Query: "CALL DOLT_STASH('clear','myStash');", + Expected: []sql.Row{{""}}, + }, + }, + }, + { + Name: "Simple push and pop with DOLT_STASH()", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{{"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "Clearing stash removes all entries in stash list", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'stash1')", + "INSERT INTO test VALUES (2, 'b')", + "CALL DOLT_STASH('push', 'stash2')", + " INSERT INTO test VALUES (3, 'c')", + "CALL DOLT_STASH('push', 'stash2')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM dolt_stashes;", + Expected: []sql.Row{ + {"refs/stashes/stash1", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"refs/stashes/stash2", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"refs/stashes/stash2", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('clear', 'stash2');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM dolt_stashes;", + Expected: []sql.Row{ + {"refs/stashes/stash1", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + }, + }, + { + Name: "Clearing and stashing again", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('clear', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + }, + }, + { + Name: "Popping specific stashes", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (2, 'b')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (3, 'c')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (4, 'd')", + "CALL DOLT_STASH('push', 'myStash')", + "CALL DOLT_STASH('pop', 'myStash', 'stash@{3}')", + "CALL DOLT_STASH('pop', 'myStash', 'stash@{1}')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM test", + Expected: []sql.Row{ + {1, "a"}, + {3, "c"}, + }, + }, + }, + }, + { + Name: "Stashing on different branches", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "INSERT INTO test VALUES (1, 'a');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('-b', 'br1');", + Expected: []sql.Row{{0, "Switched to branch 'br1'"}}, + }, + { + Query: "INSERT INTO test VALUES (2, 'b');", + Expected: []sql.Row{{types.NewOkResult(1)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Created table"}, + {"refs/stashes/myStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + }, + }, + { + Name: "Popping stash onto different branch", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + "CALL DOLT_CHECKOUT('-b', 'br1')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM TEST;", + Expected: []sql.Row{ + {1, "a"}, + }, + }, + }, + }, + { + Name: "Can drop specific stash", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (2, 'b')", + "CALL DOLT_COMMIT('-a', '-m', 'Added 2 b')", + "INSERT INTO test VALUES (3, 'c')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (4, 'd')", + "CALL DOLT_COMMIT('-a','-m', 'Added 4 d')", + "INSERT INTO test VALUES (5, 'c')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Added 2 b"}, + {"refs/stashes/myStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_STASH('drop', 'myStash', 'stash@{1}');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Added 4 d"}, + {"refs/stashes/myStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + }, + }, + { + Name: "Can pop into dirty working set without conflict", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (2, 'b')", + "CALL DOLT_STASH('pop', 'myStash')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM test", + Expected: []sql.Row{ + {1, "a"}, + {2, "b"}, + }, + }, + }, + }, + { + Name: "Can't pop into dirty working set with conflict", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + "INSERT INTO test VALUES (1, 'b')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + ExpectedErrStr: "error: Your local changes to the following tables would be overwritten by applying stash 0:\n\t{'test'}\n" + + "Please commit your changes or stash them before you merge.\nAborting\n", + }, + { + Query: "SELECT * FROM DOLT_STASHES;", + Expected: []sql.Row{ + {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + }, + }, + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{ + {1, "b"}, + }, + }, + }, + }, + { + Name: "Can stash modified staged and working set of changes", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_ADD('.')", + "INSERT INTO test VALUES (2, 'b')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", true, "modified"}, + {"test", false, "modified"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM test;", + Expected: []sql.Row{}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM dolt_status;", + Expected: []sql.Row{ + {"test", false, "modified"}, + }, + }, + }, + }, + { + Name: "Can use --include-untracked on push", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CREATE TABLE new(id int primary key)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", true, "new table"}, + {"new", false, "new table"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash', '--include-untracked');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM dolt_status;", + Expected: []sql.Row{ + {"test", true, "new table"}, + {"new", false, "new table"}, + }, + }, + }, + }, + { + Name: "Stash with tracked and untracked tables", + SetUpScript: []string{ + "CREATE TABLE new(i INT PRIMARY KEY)", + "CALL DOLT_ADD('.')", + "INSERT INTO new VALUES (1),(2)", + "CREATE TABLE test(id INT)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"new", true, "new table"}, + {"test", false, "new table"}, + {"new", false, "modified"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash')", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", false, "new table"}, + }, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"new", true, "new table"}, + {"test", false, "new table"}, + }, + }, + }, + }, + { + Name: "stashing working set with deleted table and popping it", + SetUpScript: []string{ + "CREATE TABLE new_tab(id INT PRIMARY KEY)", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", + "DROP TABLE new_tab", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SELECT * FROM DOLT_STATUS;", + Expected: []sql.Row{ + {"new_tab", false, "deleted"}, + }, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SHOW TABLES;", + Expected: []sql.Row{ + {"new_tab"}, + }, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SHOW TABLES;", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "popping stash with deleted table that is deleted already on current head", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1');", + "CALL DOLT_CHECKOUT('-b', 'branch2');", + "DROP TABLE test;", + "CALL DOLT_COMMIT('-A','-m','Dropped test');", + "CALL DOLT_CHECKOUT('branch1');", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "SHOW TABLES;", + Expected: []sql.Row{ + {"test"}, + }, + }, + { + Query: "DROP TABLE test;", + Expected: []sql.Row{{types.NewOkResult(0)}}, + }, + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{}, + }, + }, + }, + { + Name: "popping stash with deleted table that the same table exists on current head", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1');", + "CALL DOLT_BRANCH('branch2');", + "CALL DOLT_CHECKOUT('branch1');", + "DROP TABLE test;", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{ + {"test", false, "deleted"}, + }, + }, + }, + }, + { + Name: "popping stash with deleted table that different table with same name on current head gives conflict", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1')", + "CALL DOLT_BRANCH('branch2')", + "CALL DOLT_CHECKOUT('branch1')", + "DROP TABLE test", + "CALL DOLT_STASH('push', 'myStash')", + "CALL DOLT_CHECKOUT('branch2')", + "DROP TABLE test", + "CREATE TABLE test (id BIGINT PRIMARY KEY)", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + ExpectedErrStr: "merge aborted: schema conflict found for table test \n " + + "please resolve schema conflicts before merging: \n" + + "\ttable was modified in one branch and deleted in the other", + }, + }, + }, + { + Name: "popping stash with added table with PK on current head with the exact same table is added already", + SetUpScript: []string{ + "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", + "CALL DOLT_ADD('.')", + "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_BRANCH('branch1')", + "CALL DOLT_CHECKOUT('-b', 'branch2')", + "CREATE TABLE new_test(id INT PRIMARY KEY)", + "INSERT INTO new_test VALUES (1)", + "CALL DOLT_COMMIT('-A', '-m', 'Created new_test')", + "CALL DOLT_CHECKOUT('branch1')", + "CREATE TABLE new_test(id INT PRIMARY KEY)", + "INSERT INTO new_test VALUES (1)", + "CALL DOLT_ADD('.')", + }, + Assertions: []queries.ScriptTestAssertion{ + { + Query: "CALL DOLT_STASH('push', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "CALL DOLT_CHECKOUT('branch2');", + Expected: []sql.Row{{0, "Switched to branch 'branch2'"}}, + }, + { + Query: "CALL DOLT_STASH('pop', 'myStash');", + SkipResultsCheck: true, + }, + { + Query: "SELECT * FROM DOLT_STATUS", + Expected: []sql.Row{}, + }, + }, + }, +} From 01ca03baa60efd5ad46f1eb0b2d40a7f116083c2 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Mon, 9 Jun 2025 12:06:59 -0700 Subject: [PATCH 06/14] Small fixes --- go/cmd/dolt/commands/stashcmds/list.go | 2 +- go/libraries/doltcore/doltdb/doltdb.go | 19 +++++++++++ .../doltcore/sqle/dtables/stashes_table.go | 6 ++-- integration-tests/bats/ls.bats | 3 +- integration-tests/bats/sql-stash.bats | 34 +++++++++++++++++++ 5 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 integration-tests/bats/sql-stash.bats diff --git a/go/cmd/dolt/commands/stashcmds/list.go b/go/cmd/dolt/commands/stashcmds/list.go index 965f2ca0d83..9bde54702fb 100644 --- a/go/cmd/dolt/commands/stashcmds/list.go +++ b/go/cmd/dolt/commands/stashcmds/list.go @@ -80,7 +80,7 @@ func (cmd StashListCmd) Exec(ctx context.Context, commandStr string, args []stri } func listStashes(ctx context.Context, dEnv *env.DoltEnv) error { - stashes, err := dEnv.DoltDB(ctx).GetStashes(ctx) + stashes, err := dEnv.DoltDB(ctx).GetCommandLineStashes(ctx) if err != nil { return err } diff --git a/go/libraries/doltcore/doltdb/doltdb.go b/go/libraries/doltcore/doltdb/doltdb.go index b2306ab9212..f5cd877a72b 100644 --- a/go/libraries/doltcore/doltdb/doltdb.go +++ b/go/libraries/doltcore/doltdb/doltdb.go @@ -2228,6 +2228,25 @@ func (ddb *DoltDB) GetStashes(ctx context.Context) ([]*Stash, error) { return stashList, nil } +func (ddb *DoltDB) GetCommandLineStashes(ctx context.Context) ([]*Stash, error) { + var stashList []*Stash + reference := "refs/stashes/dolt-cli" + stashDS, err := ddb.db.GetDataset(ctx, reference) + if err != nil { + return nil, err + } + if !stashDS.HasHead() { + return stashList, nil + } + newStashes, err := getStashList(ctx, stashDS, ddb.vrw, ddb.NodeStore(), reference) + if err != nil { + return nil, err + } + stashList = append(stashList, newStashes...) + + return stashList, nil +} + // GetStashHashAtIdx returns hash address only of the stash at given index. func (ddb *DoltDB) GetStashHashAtIdx(ctx context.Context, idx int, stashName string) (hash.Hash, error) { ds, err := ddb.db.GetDataset(ctx, ref.NewStashRef(stashName).String()) diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index d2f43523823..200b20d400d 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -67,11 +67,11 @@ func (st *StashesTable) String() string { // Schema is a sql.Table interface function that gets the sql.Schema of the remotes system table func (st *StashesTable) Schema() sql.Schema { return []*sql.Column{ - {Name: "stash reference", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, - {Name: "stash id", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "stash_reference", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "stash_id", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, {Name: "branch", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, {Name: "hash", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, - {Name: "commit message", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: true}, + {Name: "commit_message", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: true}, } } diff --git a/integration-tests/bats/ls.bats b/integration-tests/bats/ls.bats index afcc60f620f..fe6d8a42332 100755 --- a/integration-tests/bats/ls.bats +++ b/integration-tests/bats/ls.bats @@ -60,7 +60,7 @@ teardown() { @test "ls: --system shows system tables" { run dolt ls --system [ "$status" -eq 0 ] - [ "${#lines[@]}" -eq 24 ] + [ "${#lines[@]}" -eq 25 ] [[ "$output" =~ "System tables:" ]] || false [[ "$output" =~ "dolt_status" ]] || false [[ "$output" =~ "dolt_commits" ]] || false @@ -85,6 +85,7 @@ teardown() { [[ "$output" =~ "dolt_commit_diff_table_two" ]] || false [[ "$output" =~ "dolt_workspace_table_one" ]] || false [[ "$output" =~ "dolt_workspace_table_two" ]] || false + [[ "$output" =~ "dolt_stashes" ]] || false } @test "ls: --all shows tables in working set and system tables" { diff --git a/integration-tests/bats/sql-stash.bats b/integration-tests/bats/sql-stash.bats new file mode 100644 index 00000000000..c780e0251c0 --- /dev/null +++ b/integration-tests/bats/sql-stash.bats @@ -0,0 +1,34 @@ +#!/usr/bin/env bats +load $BATS_TEST_DIRNAME/helper/common.bash + +setup() { + setup_common +} + +teardown() { + assert_feature_version + teardown_common +} + +@test "sql-stash: push does not affect stash" { + TESTDIRS=$(pwd)/testdirs + mkdir -p $TESTDIRS/{rem1,repo1} + + cd $TESTDIRS/repo1 + dolt init + dolt remote add origin file://../rem1 + dolt remote add test-remote file://../rem1 + dolt push origin main + dolt sql -q "create table t1 (a int primary key, b int)" + dolt add . + dolt sql -q "call dolt_stash('push', 'stash1');" + + run dolt sql -q "select * from dolt_stashes" + result=$output + + dolt push origin main + + run dolt sql -q "select * from dolt_stashes" + [ "$status" -eq 0 ] + [ "$output" = "$result" ] +} \ No newline at end of file From 25d2e6eadb146faab8b1237ab6fd0523630b0c9e Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Mon, 9 Jun 2025 14:16:49 -0700 Subject: [PATCH 07/14] Fixed tests + cleanup --- go/libraries/doltcore/ref/stash_ref.go | 2 +- go/libraries/doltcore/sqle/dprocedures/dolt_stash.go | 5 ----- go/libraries/doltcore/sqle/enginetest/dolt_queries.go | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/go/libraries/doltcore/ref/stash_ref.go b/go/libraries/doltcore/ref/stash_ref.go index 02f6ea63028..2e18d90be00 100644 --- a/go/libraries/doltcore/ref/stash_ref.go +++ b/go/libraries/doltcore/ref/stash_ref.go @@ -24,7 +24,7 @@ type StashRef struct { var _ DoltRef = StashRef{} -// NewStashRef creates a reference to a stashes list. There cannot be more than one stashRef. +// NewStashRef creates a reference to a stashes list. func NewStashRef(stashName string) StashRef { if IsRef(stashName) { prefix := PrefixForType(StashRefType) diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 189cf6ef6c1..b6ce00d64ea 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -96,11 +96,6 @@ func doDoltStash(ctx *sql.Context, args []string) (string, error) { case "pop": status, err = doStashPop(ctx, dbData, stashName, idx) case "drop": - var idx int - idx, err = parseStashIndex(apr) - if err != nil { - return "", err - } status, err = doStashDrop(ctx, dbData, stashName, idx) case "clear": if apr.NArg() > 2 { // Clear does not take extra arguments diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go index 4afe8b42883..f40045d6ccf 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries.go @@ -7951,6 +7951,7 @@ var DoltSystemVariables = []queries.ScriptTest{ {"dolt_log"}, {"dolt_remote_branches"}, {"dolt_remotes"}, + {"dolt_stashes"}, {"dolt_status"}, {"dolt_workspace_test"}, {"test"}, From f954cffa92ff3817a1c142069441baf04e804180 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Tue, 10 Jun 2025 10:59:52 -0700 Subject: [PATCH 08/14] Requested changes --- go/cmd/dolt/commands/stashcmds/clear.go | 3 +- go/cmd/dolt/commands/stashcmds/drop.go | 5 +- go/cmd/dolt/commands/stashcmds/pop.go | 2 +- go/cmd/dolt/commands/stashcmds/stash.go | 3 +- go/libraries/doltcore/doltdb/doltdb.go | 7 +- go/libraries/doltcore/doltdb/stash.go | 4 + .../doltcore/sqle/dprocedures/dolt_stash.go | 102 +++++++---------- .../doltcore/sqle/dprocedures/init.go | 2 +- .../doltcore/sqle/dtables/stashes_table.go | 31 ++++- .../sqle/enginetest/dolt_queries_stash.go | 107 +++++++----------- integration-tests/bats/sql-stash.bats | 9 +- 11 files changed, 126 insertions(+), 149 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/clear.go b/go/cmd/dolt/commands/stashcmds/clear.go index 464e4b9f8ce..ba96c89d9a3 100644 --- a/go/cmd/dolt/commands/stashcmds/clear.go +++ b/go/cmd/dolt/commands/stashcmds/clear.go @@ -16,6 +16,7 @@ package stashcmds import ( "context" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" @@ -77,7 +78,7 @@ func (cmd StashClearCmd) Exec(ctx context.Context, commandStr string, args []str return 1 } - err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx, DoltCliRef) + err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx, doltdb.DoltCliRef) if err != nil { return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) } diff --git a/go/cmd/dolt/commands/stashcmds/drop.go b/go/cmd/dolt/commands/stashcmds/drop.go index 3eb536ecbd9..83516b05008 100644 --- a/go/cmd/dolt/commands/stashcmds/drop.go +++ b/go/cmd/dolt/commands/stashcmds/drop.go @@ -17,6 +17,7 @@ package stashcmds import ( "context" "fmt" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "strconv" "strings" @@ -95,12 +96,12 @@ func (cmd StashDropCmd) Exec(ctx context.Context, commandStr string, args []stri } func dropStashAtIdx(ctx context.Context, dEnv *env.DoltEnv, idx int) error { - stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, DoltCliRef) + stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, doltdb.DoltCliRef) if err != nil { return err } - err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx, DoltCliRef) + err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx, doltdb.DoltCliRef) if err != nil { return err } diff --git a/go/cmd/dolt/commands/stashcmds/pop.go b/go/cmd/dolt/commands/stashcmds/pop.go index 89ca06d54cf..18e2b0caa67 100644 --- a/go/cmd/dolt/commands/stashcmds/pop.go +++ b/go/cmd/dolt/commands/stashcmds/pop.go @@ -125,7 +125,7 @@ func (cmd StashPopCmd) Exec(ctx context.Context, commandStr string, args []strin } func applyStashAtIdx(ctx *sql.Context, dEnv *env.DoltEnv, curWorkingRoot doltdb.RootValue, idx int) (bool, error) { - stashRoot, headCommit, meta, err := dEnv.DoltDB(ctx).GetStashRootAndHeadCommitAtIdx(ctx, idx, DoltCliRef) + stashRoot, headCommit, meta, err := dEnv.DoltDB(ctx).GetStashRootAndHeadCommitAtIdx(ctx, idx, doltdb.DoltCliRef) if err != nil { return false, err } diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 2d6db25a079..0c92bdecb1f 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -42,7 +42,6 @@ var StashCommands = cli.NewSubCommandHandlerWithUnspecified("stash", "Stash the const ( IncludeUntrackedFlag = "include-untracked" AllFlag = "all" - DoltCliRef = "dolt-cli" ) var stashDocs = cli.CommandDocumentationContent{ @@ -243,7 +242,7 @@ func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars return err } - err = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), DoltCliRef) + err = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), doltdb.DoltCliRef) if err != nil { return err } diff --git a/go/libraries/doltcore/doltdb/doltdb.go b/go/libraries/doltcore/doltdb/doltdb.go index f5cd877a72b..86da87f9ec4 100644 --- a/go/libraries/doltcore/doltdb/doltdb.go +++ b/go/libraries/doltcore/doltdb/doltdb.go @@ -2230,13 +2230,16 @@ func (ddb *DoltDB) GetStashes(ctx context.Context) ([]*Stash, error) { func (ddb *DoltDB) GetCommandLineStashes(ctx context.Context) ([]*Stash, error) { var stashList []*Stash - reference := "refs/stashes/dolt-cli" + reference := ref.NewStashRef(DoltCliRef).String() stashDS, err := ddb.db.GetDataset(ctx, reference) if err != nil { return nil, err } + + // If the refs/stashes/dolt-cli is empty, hasHead will return false + // and in this case we want to end early with no stashes. if !stashDS.HasHead() { - return stashList, nil + return nil, nil } newStashes, err := getStashList(ctx, stashDS, ddb.vrw, ddb.NodeStore(), reference) if err != nil { diff --git a/go/libraries/doltcore/doltdb/stash.go b/go/libraries/doltcore/doltdb/stash.go index 1d1cb5c0625..af11e77d185 100644 --- a/go/libraries/doltcore/doltdb/stash.go +++ b/go/libraries/doltcore/doltdb/stash.go @@ -33,6 +33,10 @@ type Stash struct { StashReference string } +const ( + DoltCliRef = "dolt-cli" +) + // getStashList returns array of Stash objects containing all stash entries in the stash list map. func getStashList(ctx context.Context, ds datas.Dataset, vrw types.ValueReadWriter, ns tree.NodeStore, reference string) ([]*Stash, error) { v, ok := ds.MaybeHead() diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index b6ce00d64ea..845f1e95205 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -52,185 +52,164 @@ func doltStash(ctx *sql.Context, args ...string) (sql.RowIter, error) { return rowToIter(res), nil } -func doDoltStash(ctx *sql.Context, args []string) (string, error) { +func doDoltStash(ctx *sql.Context, args []string) (int, error) { dbName := ctx.GetCurrentDatabase() dSess := dsess.DSessFromSess(ctx.Session) dbData, ok := dSess.GetDbData(ctx, dbName) if !ok { - return "", fmt.Errorf("Could not load database %s", dbName) + return cmdFailure, fmt.Errorf("Could not load database %s", dbName) } if !dbData.Ddb.Format().UsesFlatbuffers() { - return "", fmt.Errorf("stash is not supported for old storage format") + return cmdFailure, fmt.Errorf("stash is not supported for old storage format") } roots, ok := dSess.GetRoots(ctx, dbName) if !ok { - return "", fmt.Errorf("Could not load roots for database %s", dbName) + return cmdFailure, fmt.Errorf("Could not load roots for database %s", dbName) } apr, err := cli.CreateStashArgParser().Parse(args) if err != nil { - return "", err + return cmdFailure, err } if apr.NArg() < 2 { - return "", fmt.Errorf("error: invalid arguments. Must provide valid subcommand and stash name") + return cmdFailure, fmt.Errorf("error: invalid arguments. Must provide valid subcommand and stash name") } - var status string - cmdName := apr.Arg(0) stashName := apr.Arg(1) idx, err := parseStashIndex(apr) if err != nil { - return "", err + return cmdFailure, err } switch cmdName { case "push": if apr.NArg() > 2 { // Push does not take extra arguments - return "", fmt.Errorf("error: invalid arguments. Push takes only subcommand and stash name=") + return cmdFailure, fmt.Errorf("error: invalid arguments. Push takes only subcommand and stash name=") } - status, err = doStashPush(ctx, dSess, dbData, roots, apr, stashName) + err = doStashPush(ctx, dSess, dbData, roots, apr, stashName) case "pop": - status, err = doStashPop(ctx, dbData, stashName, idx) + err = doStashPop(ctx, dbData, stashName, idx) case "drop": - status, err = doStashDrop(ctx, dbData, stashName, idx) + err = doStashDrop(ctx, dbData, stashName, idx) case "clear": if apr.NArg() > 2 { // Clear does not take extra arguments - return "", fmt.Errorf("error: invalid arguments. Clear takes only subcommand and stash name") + return cmdFailure, fmt.Errorf("error: invalid arguments. Clear takes only subcommand and stash name") } err = doStashClear(ctx, dbData, stashName) default: - return "", fmt.Errorf("unknown stash subcommand %s", cmdName) + return cmdFailure, fmt.Errorf("unknown stash subcommand %s", cmdName) } if err != nil { - return "", err + return cmdFailure, err } - return status, nil + return cmdSuccess, nil } -func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults, stashName string) (string, error) { +func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[*sql.Context], roots doltdb.Roots, apr *argparser.ArgParseResults, stashName string) error { hasChanges, err := hasLocalChanges(ctx, dSess, roots, apr) if err != nil { - return "", err + return err } if !hasChanges { - return "", fmt.Errorf("no local changes to save") + return fmt.Errorf("no local changes to save") } roots, err = actions.StageModifiedAndDeletedTables(ctx, roots) if err != nil { - return "", err + return err } // all tables with changes that are going to be stashed are staged at this point allTblsToBeStashed, addedTblsToStage, err := stashedTableSets(ctx, roots) if err != nil { - return "", err + return err } if apr.Contains(includeUntrackedFlag) || apr.Contains(cli.AllFlag) { allTblsToBeStashed, err = doltdb.UnionTableNames(ctx, roots.Staged, roots.Working) if err != nil { - return "", err + return err } roots, err = actions.StageTables(ctx, roots, allTblsToBeStashed, !apr.Contains("all")) if err != nil { - return "", err + return err } } commit, commitMeta, curBranchName, err := gatherCommitData(ctx, dbData) if err != nil { - return "", err + return err } err = dbData.Ddb.AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), stashName) if err != nil { - return "", err + return err } roots.Staged = roots.Head roots, err = actions.MoveTablesFromHeadToWorking(ctx, roots, allTblsToBeStashed) if err != nil { - return "", err + return err } err = updateWorkingSetFromRoots(ctx, dbData, roots) if err != nil { - return "", err - } - - commitHash, err := commit.HashOf() - if err != nil { - return "", err + return err } - status := fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", curBranchName, commitHash.String(), commitMeta.Description) - return status, nil + return nil } -func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) (string, error) { +func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) error { headCommit, result, meta, err := handleMerge(ctx, dbData, stashName, idx) if err != nil { - return "", err + return err } err = updateWorkingRoot(ctx, dbData, result.Root) if err != nil { - return "", err + return err } roots, err := getRoots(ctx, dbData, headCommit) if err != nil { - return "", err + return err } // added tables need to be staged // since these tables are coming from a stash, don't filter for ignored table names. roots, err = actions.StageTables(ctx, roots, doltdb.ToTableNames(meta.TablesToStage, doltdb.DefaultSchemaName), false) if err != nil { - return "", err + return err } err = updateWorkingSetFromRoots(ctx, dbData, roots) if err != nil { - return "", err - } - - stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, stashName) - if err != nil { - return "", err + return err } err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) if err != nil { - return "", err + return err } - status := fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String()) - return status, err + return nil } -func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) (string, error) { - stashHash, err := dbData.Ddb.GetStashHashAtIdx(ctx, idx, stashName) +func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) error { + err := dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) if err != nil { - return "", err - } - - err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) - if err != nil { - return "", err + return err } - status := fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String()) - - return status, nil + return nil } func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string) error { @@ -359,7 +338,6 @@ func workingSetContainsOnlyUntrackedTables(ctx context.Context, roots doltdb.Roo func updateWorkingSetFromRoots(ctx *sql.Context, dbData env.DbData[*sql.Context], roots doltdb.Roots) error { ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) if err == doltdb.ErrWorkingSetNotFound { - // first time updating roots headRef, err := dbData.Rsr.CWBHeadRef(ctx) if err != nil { return err @@ -427,7 +405,6 @@ func bulkDbEaFactory(dbData env.DbData[*sql.Context]) editor.DbEaFactory { } func updateWorkingRoot(ctx *sql.Context, dbData env.DbData[*sql.Context], newRoot doltdb.RootValue) error { - //err = dEnv.UpdateWorkingRoot(ctx, result.Root) var h hash.Hash var wsRef ref.WorkingSetRef headRef, err := dbData.Rsr.CWBHeadRef(ctx) @@ -437,7 +414,6 @@ func updateWorkingRoot(ctx *sql.Context, dbData env.DbData[*sql.Context], newRoo ws, err := env.WorkingSet(ctx, dbData.Ddb, dbData.Rsr) if err == doltdb.ErrWorkingSetNotFound { - // first time updating root wsRef, err = ref.WorkingSetRefForHead(headRef) if err != nil { return err diff --git a/go/libraries/doltcore/sqle/dprocedures/init.go b/go/libraries/doltcore/sqle/dprocedures/init.go index 48e6f661922..e1bb381db37 100644 --- a/go/libraries/doltcore/sqle/dprocedures/init.go +++ b/go/libraries/doltcore/sqle/dprocedures/init.go @@ -46,7 +46,7 @@ var DoltProcedures = []sql.ExternalStoredProcedureDetails{ {Name: "dolt_remote", Schema: int64Schema("status"), Function: doltRemote, AdminOnly: true}, {Name: "dolt_reset", Schema: int64Schema("status"), Function: doltReset}, {Name: "dolt_revert", Schema: int64Schema("status"), Function: doltRevert}, - {Name: "dolt_stash", Schema: stringSchema("status"), Function: doltStash}, + {Name: "dolt_stash", Schema: int64Schema("status"), Function: doltStash}, {Name: "dolt_tag", Schema: int64Schema("status"), Function: doltTag}, {Name: "dolt_verify_constraints", Schema: int64Schema("violations"), Function: doltVerifyConstraints}, diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index 200b20d400d..de5c4ea630e 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -17,6 +17,7 @@ package dtables import ( "fmt" "io" + "strings" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" @@ -50,9 +51,25 @@ func (st *StashesTable) DataLength(ctx *sql.Context) (uint64, error) { return numBytesPerRow * numRows, nil } -func (st *StashesTable) RowCount(_ *sql.Context) (uint64, bool, error) { - return stashesDefaultRowCount, false, nil -} //Todo: What row count? +func (st *StashesTable) RowCount(ctx *sql.Context) (uint64, bool, error) { + dbName := ctx.GetCurrentDatabase() + + if len(dbName) == 0 { + return 0, false, fmt.Errorf("Empty database name.") + } + + sess := dsess.DSessFromSess(ctx.Session) + dbData, ok := sess.GetDbData(ctx, dbName) + if !ok { + return 0, false, sql.ErrDatabaseNotFound.New(dbName) + } + + stashes, err := dbData.Ddb.GetStashes(ctx) + if err != nil { + return 0, false, err + } + return uint64(len(stashes)), true, nil +} // Name is a sql.Table interface function which returns the name of the table func (st *StashesTable) Name() string { @@ -67,7 +84,7 @@ func (st *StashesTable) String() string { // Schema is a sql.Table interface function that gets the sql.Schema of the remotes system table func (st *StashesTable) Schema() sql.Schema { return []*sql.Column{ - {Name: "stash_reference", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, + {Name: "name", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, {Name: "stash_id", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, {Name: "branch", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, {Name: "hash", Type: types.Text, Source: st.tableName, PrimaryKey: false, Nullable: false}, @@ -133,7 +150,11 @@ func (itr *StashItr) Next(ctx *sql.Context) (sql.Row, error) { if err != nil { return nil, err } - return sql.NewRow(stash.StashReference, stash.Name, stash.BranchName, commitHash.String(), stash.Description), nil + + branch := strings.Split(stash.BranchName, "/")[2] + stashRef := strings.Split(stash.StashReference, "/")[2] + + return sql.NewRow(stashRef, stash.Name, branch, commitHash.String(), stash.Description), nil } // Close closes the iterator. diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go index 49ca087845b..cb984efdc32 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go @@ -46,7 +46,7 @@ var DoltStashTests = []queries.ScriptTest{ }, { Query: "CALL DOLT_STASH('clear','myStash');", - Expected: []sql.Row{{""}}, + Expected: []sql.Row{{0}}, }, }, }, @@ -54,15 +54,14 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Simple push and pop with DOLT_STASH()", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'myStash');", }, Assertions: []queries.ScriptTestAssertion{ { Query: "SELECT * FROM DOLT_STASHES;", - Expected: []sql.Row{{"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}}, + Expected: []sql.Row{{"myStash", "stash@{0}", "main", doltCommit, "Created table"}}, }, { Query: "CALL DOLT_STASH('pop', 'myStash');", @@ -78,8 +77,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Clearing stash removes all entries in stash list", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A','-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'stash1')", "INSERT INTO test VALUES (2, 'b')", @@ -91,9 +89,9 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM dolt_stashes;", Expected: []sql.Row{ - {"refs/stashes/stash1", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - {"refs/stashes/stash2", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, - {"refs/stashes/stash2", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + {"stash1", "stash@{0}", "main", doltCommit, "Created table"}, + {"stash2", "stash@{0}", "main", doltCommit, "Created table"}, + {"stash2", "stash@{1}", "main", doltCommit, "Created table"}, }, }, { @@ -103,7 +101,7 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM dolt_stashes;", Expected: []sql.Row{ - {"refs/stashes/stash1", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"stash1", "stash@{0}", "main", doltCommit, "Created table"}, }, }, }, @@ -112,8 +110,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Clearing and stashing again", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'myStash')", }, @@ -121,7 +118,7 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "main", doltCommit, "Created table"}, }, }, { @@ -139,7 +136,7 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "main", doltCommit, "Created table"}, }, }, }, @@ -148,8 +145,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Popping specific stashes", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A','-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'myStash')", "INSERT INTO test VALUES (2, 'b')", @@ -175,35 +171,19 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Stashing on different branches", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", + "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_STASH('push', 'myStash')", + "CALL DOLT_CHECKOUT('-b', 'br1')", + "INSERT INTO test VALUES (2, 'b')", + "CALL DOLT_STASH('push', 'myStash')", }, Assertions: []queries.ScriptTestAssertion{ - { - Query: "INSERT INTO test VALUES (1, 'a');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, - { - Query: "CALL DOLT_CHECKOUT('-b', 'br1');", - Expected: []sql.Row{{0, "Switched to branch 'br1'"}}, - }, - { - Query: "INSERT INTO test VALUES (2, 'b');", - Expected: []sql.Row{{types.NewOkResult(1)}}, - }, - { - Query: "CALL DOLT_STASH('push', 'myStash');", - SkipResultsCheck: true, - }, { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/br1", doltCommit, "Created table"}, - {"refs/stashes/myStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "br1", doltCommit, "Created table"}, + {"myStash", "stash@{1}", "main", doltCommit, "Created table"}, }, }, }, @@ -212,17 +192,19 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Popping stash onto different branch", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", + "CALL DOLT_BRANCH('br1')", "INSERT INTO test VALUES (1, 'a')", + "CALL DOLT_COMMIT('-A', '-m', 'Added a row')", + "INSERT INTO test VALUES (2, 'b')", "CALL DOLT_STASH('push', 'myStash')", - "CALL DOLT_CHECKOUT('-b', 'br1')", + "CALL DOLT_CHECKOUT('br1')", }, Assertions: []queries.ScriptTestAssertion{ { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "main", doltCommit, "Added a row"}, }, }, { @@ -232,7 +214,7 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM TEST;", Expected: []sql.Row{ - {1, "a"}, + {2, "b"}, }, }, }, @@ -241,8 +223,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Can drop specific stash", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'myStash')", "INSERT INTO test VALUES (2, 'b')", @@ -257,8 +238,8 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Added 2 b"}, - {"refs/stashes/myStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "main", doltCommit, "Added 2 b"}, + {"myStash", "stash@{1}", "main", doltCommit, "Created table"}, }, }, { @@ -272,8 +253,8 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Added 4 d"}, - {"refs/stashes/myStash", "stash@{1}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "main", doltCommit, "Added 4 d"}, + {"myStash", "stash@{1}", "main", doltCommit, "Created table"}, }, }, }, @@ -282,8 +263,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Can pop into dirty working set without conflict", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'myStash')", "INSERT INTO test VALUES (2, 'b')", @@ -303,8 +283,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Can't pop into dirty working set with conflict", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A','-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_STASH('push', 'myStash')", "INSERT INTO test VALUES (1, 'b')", @@ -318,7 +297,7 @@ var DoltStashTests = []queries.ScriptTest{ { Query: "SELECT * FROM DOLT_STASHES;", Expected: []sql.Row{ - {"refs/stashes/myStash", "stash@{0}", "refs/heads/main", doltCommit, "Created table"}, + {"myStash", "stash@{0}", "main", doltCommit, "Created table"}, }, }, { @@ -333,8 +312,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "Can stash modified staged and working set of changes", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "INSERT INTO test VALUES (1, 'a')", "CALL DOLT_ADD('.')", "INSERT INTO test VALUES (2, 'b')", @@ -447,7 +425,6 @@ var DoltStashTests = []queries.ScriptTest{ Name: "stashing working set with deleted table and popping it", SetUpScript: []string{ "CREATE TABLE new_tab(id INT PRIMARY KEY)", - "CALL DOLT_ADD('.')", "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "DROP TABLE new_tab", }, @@ -482,8 +459,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "popping stash with deleted table that is deleted already on current head", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "CALL DOLT_BRANCH('branch1');", "CALL DOLT_CHECKOUT('-b', 'branch2');", "DROP TABLE test;", @@ -523,8 +499,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "popping stash with deleted table that the same table exists on current head", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "CALL DOLT_BRANCH('branch1');", "CALL DOLT_BRANCH('branch2');", "CALL DOLT_CHECKOUT('branch1');", @@ -555,8 +530,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "popping stash with deleted table that different table with same name on current head gives conflict", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "CALL DOLT_BRANCH('branch1')", "CALL DOLT_BRANCH('branch2')", "CALL DOLT_CHECKOUT('branch1')", @@ -579,8 +553,7 @@ var DoltStashTests = []queries.ScriptTest{ Name: "popping stash with added table with PK on current head with the exact same table is added already", SetUpScript: []string{ "CREATE TABLE test(pk BIGINT PRIMARY KEY, v varchar(10))", - "CALL DOLT_ADD('.')", - "CALL DOLT_COMMIT('-m', 'Created table')", + "CALL DOLT_COMMIT('-A', '-m', 'Created table')", "CALL DOLT_BRANCH('branch1')", "CALL DOLT_CHECKOUT('-b', 'branch2')", "CREATE TABLE new_test(id INT PRIMARY KEY)", diff --git a/integration-tests/bats/sql-stash.bats b/integration-tests/bats/sql-stash.bats index c780e0251c0..73067da1c54 100644 --- a/integration-tests/bats/sql-stash.bats +++ b/integration-tests/bats/sql-stash.bats @@ -22,13 +22,12 @@ teardown() { dolt sql -q "create table t1 (a int primary key, b int)" dolt add . dolt sql -q "call dolt_stash('push', 'stash1');" - - run dolt sql -q "select * from dolt_stashes" - result=$output - dolt push origin main + cd $TESTDIRS + dolt clone file://rem1 repo2 + cd repo2 run dolt sql -q "select * from dolt_stashes" [ "$status" -eq 0 ] - [ "$output" = "$result" ] + [ "${#lines[@]}" -eq 0 ] } \ No newline at end of file From b0e5eee98006dc0b322e32b687a5cc068e9925e3 Mon Sep 17 00:00:00 2001 From: NathanGabrielson Date: Tue, 10 Jun 2025 18:08:40 +0000 Subject: [PATCH 09/14] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/cmd/dolt/commands/stashcmds/clear.go | 1 + go/cmd/dolt/commands/stashcmds/drop.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go/cmd/dolt/commands/stashcmds/clear.go b/go/cmd/dolt/commands/stashcmds/clear.go index ba96c89d9a3..e11805f4b1e 100644 --- a/go/cmd/dolt/commands/stashcmds/clear.go +++ b/go/cmd/dolt/commands/stashcmds/clear.go @@ -16,6 +16,7 @@ package stashcmds import ( "context" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/cmd/dolt/cli" diff --git a/go/cmd/dolt/commands/stashcmds/drop.go b/go/cmd/dolt/commands/stashcmds/drop.go index 83516b05008..98f00621225 100644 --- a/go/cmd/dolt/commands/stashcmds/drop.go +++ b/go/cmd/dolt/commands/stashcmds/drop.go @@ -17,10 +17,11 @@ package stashcmds import ( "context" "fmt" - "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "strconv" "strings" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" "github.com/dolthub/dolt/go/cmd/dolt/errhand" From 37deab0d41b086dbeeb6608ce6b97c3c4104f46d Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Tue, 10 Jun 2025 13:04:10 -0700 Subject: [PATCH 10/14] Some more small fixes --- go/cmd/dolt/cli/arg_parser_helpers.go | 2 +- go/cmd/dolt/cli/flags.go | 1 + go/cmd/dolt/commands/stashcmds/stash.go | 9 ++-- .../doltcore/sqle/dprocedures/dolt_stash.go | 10 ++--- .../doltcore/sqle/dtables/stashes_table.go | 44 ++++++++++++------- 5 files changed, 37 insertions(+), 29 deletions(-) diff --git a/go/cmd/dolt/cli/arg_parser_helpers.go b/go/cmd/dolt/cli/arg_parser_helpers.go index 3c5b1457536..ed2aeb1acab 100644 --- a/go/cmd/dolt/cli/arg_parser_helpers.go +++ b/go/cmd/dolt/cli/arg_parser_helpers.go @@ -107,7 +107,7 @@ func CreateMergeArgParser() *argparser.ArgParser { func CreateStashArgParser() *argparser.ArgParser { ap := argparser.NewArgParserWithMaxArgs("stash", 3) - ap.SupportsFlag("include-untracked", "u", "Untracked tables are also stashed.") //TODO: Add Constant + ap.SupportsFlag(IncludeUntrackedFlag, "u", "Untracked tables are also stashed.") ap.SupportsFlag(AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") return ap } diff --git a/go/cmd/dolt/cli/flags.go b/go/cmd/dolt/cli/flags.go index 07bcc9748cf..1b68267ec5c 100644 --- a/go/cmd/dolt/cli/flags.go +++ b/go/cmd/dolt/cli/flags.go @@ -42,6 +42,7 @@ const ( GraphFlag = "graph" HardResetParam = "hard" HostFlag = "host" + IncludeUntrackedFlag = "include-untracked" InteractiveFlag = "interactive" ListFlag = "list" MergesFlag = "merges" diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 0c92bdecb1f..f858b4ebe6a 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -40,8 +40,7 @@ var StashCommands = cli.NewSubCommandHandlerWithUnspecified("stash", "Stash the }) const ( - IncludeUntrackedFlag = "include-untracked" - AllFlag = "all" + AllFlag = "all" ) var stashDocs = cli.CommandDocumentationContent{ @@ -78,7 +77,7 @@ func (cmd StashCmd) Docs() *cli.CommandDocumentation { func (cmd StashCmd) ArgParser() *argparser.ArgParser { ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0) - ap.SupportsFlag(IncludeUntrackedFlag, "u", "Untracked tables are also stashed.") + ap.SupportsFlag(cli.IncludeUntrackedFlag, "u", "Untracked tables are also stashed.") ap.SupportsFlag(AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") return ap } @@ -159,7 +158,7 @@ func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, } // There are unignored, unstaged tables. Is --include-untracked set. If so, nothing else matters. Stash them. - if apr.Contains(IncludeUntrackedFlag) { + if apr.Contains(cli.IncludeUntrackedFlag) { return true, nil } @@ -207,7 +206,7 @@ func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars // stage untracked files to include them in the stash, // but do not include them in added table set, // because they should not be staged when popped. - if apr.Contains(IncludeUntrackedFlag) || apr.Contains(AllFlag) { + if apr.Contains(cli.IncludeUntrackedFlag) || apr.Contains(AllFlag) { allTblsToBeStashed, err = doltdb.UnionTableNames(ctx, roots.Staged, roots.Working) if err != nil { return err diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 845f1e95205..185156ade0e 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -37,10 +37,6 @@ import ( "github.com/dolthub/dolt/go/store/hash" ) -const ( - includeUntrackedFlag = "include-untracked" -) - // doltStash is the stored procedure version for the CLI command `dolt stash` // and its options push, pop, drop, and clear func doltStash(ctx *sql.Context, args ...string) (sql.RowIter, error) { @@ -88,7 +84,7 @@ func doDoltStash(ctx *sql.Context, args []string) (int, error) { switch cmdName { case "push": if apr.NArg() > 2 { // Push does not take extra arguments - return cmdFailure, fmt.Errorf("error: invalid arguments. Push takes only subcommand and stash name=") + return cmdFailure, fmt.Errorf("error: invalid arguments. Push takes only subcommand and stash name") } err = doStashPush(ctx, dSess, dbData, roots, apr, stashName) case "pop": @@ -131,7 +127,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return err } - if apr.Contains(includeUntrackedFlag) || apr.Contains(cli.AllFlag) { + if apr.Contains(cli.IncludeUntrackedFlag) || apr.Contains(cli.AllFlag) { allTblsToBeStashed, err = doltdb.UnionTableNames(ctx, roots.Staged, roots.Working) if err != nil { return err @@ -301,7 +297,7 @@ func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Ro } // There are unignored, unstaged tables. Is --include-untracked set? If so, nothing else matters. Stash them. - if apr.Contains(includeUntrackedFlag) { + if apr.Contains(cli.IncludeUntrackedFlag) { return true, nil } diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index de5c4ea630e..062034128aa 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -28,8 +28,6 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/sqle/index" ) -const stashesDefaultRowCount = 5 - var _ sql.Table = (*StashesTable)(nil) var _ sql.StatisticsTable = (*StashesTable)(nil) @@ -103,7 +101,7 @@ func (st *StashesTable) Partitions(*sql.Context) (sql.PartitionIter, error) { } // PartitionRows is a sql.Table interface function that gets a row iterator for a partition -func (st *StashesTable) PartitionRows(ctx *sql.Context, part sql.Partition) (sql.RowIter, error) { +func (st *StashesTable) PartitionRows(ctx *sql.Context, _ sql.Partition) (sql.RowIter, error) { return NewStashItr(ctx, st.ddb) } @@ -113,7 +111,7 @@ type StashItr struct { } // NewStashItr creates a StashItr from the current environment. -func NewStashItr(ctx *sql.Context, ddb *doltdb.DoltDB) (*StashItr, error) { +func NewStashItr(ctx *sql.Context, _ *doltdb.DoltDB) (*StashItr, error) { dbName := ctx.GetCurrentDatabase() if len(dbName) == 0 { @@ -136,7 +134,7 @@ func NewStashItr(ctx *sql.Context, ddb *doltdb.DoltDB) (*StashItr, error) { // 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 *StashItr) Next(ctx *sql.Context) (sql.Row, error) { +func (itr *StashItr) Next(*sql.Context) (sql.Row, error) { if itr.idx >= len(itr.stashes) { return nil, io.EOF } @@ -151,9 +149,23 @@ func (itr *StashItr) Next(ctx *sql.Context) (sql.Row, error) { return nil, err } - branch := strings.Split(stash.BranchName, "/")[2] - stashRef := strings.Split(stash.StashReference, "/")[2] + // BranchName and StashReference are of the form refs/heads/name + // or refs/stashes/name, so we need to parse them + parsedRef := strings.Split(stash.BranchName, "/") + var branch string + if len(parsedRef) > 2 { + branch = parsedRef[2] + } else { + return nil, fmt.Errorf("Bad reference: %s", stash.BranchName) + } + parsedRef = strings.Split(stash.BranchName, "/") + var stashRef string + if len(parsedRef) > 2 { + stashRef = parsedRef[2] + } else { + return nil, fmt.Errorf("Bad reference: %s", stash.StashReference) + } return sql.NewRow(stashRef, stash.Name, branch, commitHash.String(), stash.Description), nil } @@ -174,32 +186,32 @@ type stashWriter struct { // Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process // for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close // is called. -func (bWr stashWriter) Insert(ctx *sql.Context, r sql.Row) error { - return fmt.Errorf("the dolt_stashes table is read-only; use the dolt_stash stored procedure to edit remotes") +func (bWr stashWriter) Insert(_ *sql.Context, _ sql.Row) error { + return fmt.Errorf("the dolt_stashes table is read-only; use the dolt_stash stored procedure to edit stashes") } // Update the given row. Provides both the old and new rows. -func (bWr stashWriter) Update(ctx *sql.Context, old sql.Row, new sql.Row) error { - return fmt.Errorf("the dolt_stash table is read-only; use the dolt_stash stored procedure to edit remotes") +func (bWr stashWriter) Update(_ *sql.Context, _ sql.Row, _ sql.Row) error { + return fmt.Errorf("the dolt_stash table is read-only; use the dolt_stash stored procedure to edit stashes") } // Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for // each row to process for the delete operation, which may involve many rows. After all rows have been processed, // Close is called. -func (bWr stashWriter) Delete(ctx *sql.Context, r sql.Row) error { - return fmt.Errorf("the dolt_stash table is read-only; use the dolt_stash stored procedure to edit remotes") +func (bWr stashWriter) Delete(_ *sql.Context, _ sql.Row) error { + return fmt.Errorf("the dolt_stash table is read-only; use the dolt_stash stored procedure to edit stashes") } // StatementBegin implements the interface sql.TableEditor. Currently a no-op. -func (bWr stashWriter) StatementBegin(ctx *sql.Context) {} +func (bWr stashWriter) StatementBegin(*sql.Context) {} // DiscardChanges implements the interface sql.TableEditor. Currently a no-op. -func (bWr stashWriter) DiscardChanges(ctx *sql.Context, errorEncountered error) error { +func (bWr stashWriter) DiscardChanges(_ *sql.Context, _ error) error { return nil } // StatementComplete implements the interface sql.TableEditor. Currently a no-op. -func (bWr stashWriter) StatementComplete(ctx *sql.Context) error { +func (bWr stashWriter) StatementComplete(*sql.Context) error { return nil } From 503c1379ec7d01fe64d04ac7c6aa07f5af291c8c Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Tue, 10 Jun 2025 15:02:22 -0700 Subject: [PATCH 11/14] Some cleanup --- go/libraries/doltcore/doltdb/doltdb.go | 4 ++-- .../doltcore/sqle/dprocedures/dolt_stash.go | 12 ++---------- .../doltcore/sqle/dtables/stashes_table.go | 19 ++++++++----------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/go/libraries/doltcore/doltdb/doltdb.go b/go/libraries/doltcore/doltdb/doltdb.go index 86da87f9ec4..7ed3e0dc6a4 100644 --- a/go/libraries/doltcore/doltdb/doltdb.go +++ b/go/libraries/doltcore/doltdb/doltdb.go @@ -2236,8 +2236,8 @@ func (ddb *DoltDB) GetCommandLineStashes(ctx context.Context) ([]*Stash, error) return nil, err } - // If the refs/stashes/dolt-cli is empty, hasHead will return false - // and in this case we want to end early with no stashes. + // If the refs/stashes/dolt-cli is empty, hasHead will return false. + // In this case we want to end early and return no stashes. if !stashDS.HasHead() { return nil, nil } diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 185156ade0e..632dc9766c0 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -288,14 +288,10 @@ func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Ro // --all was not set, so we can ignore tables. Is every table ignored? allIgnored, err := diff.WorkingSetContainsOnlyIgnoredTables(ctx, roots) - if err != nil { + if err != nil || allIgnored { return false, err } - if allIgnored { - return false, nil - } - // There are unignored, unstaged tables. Is --include-untracked set? If so, nothing else matters. Stash them. if apr.Contains(cli.IncludeUntrackedFlag) { return true, nil @@ -303,14 +299,10 @@ func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Ro // --include-untracked was not set, so we can skip untracked tables. Is every table untracked? allUntracked, err := workingSetContainsOnlyUntrackedTables(ctx, roots) - if err != nil { + if err != nil || allUntracked { return false, err } - if allUntracked { - return false, nil - } - // There are changes to tracked tables. Stash them. return true, nil } diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index 062034128aa..e66b86c8779 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -150,22 +150,19 @@ func (itr *StashItr) Next(*sql.Context) (sql.Row, error) { } // BranchName and StashReference are of the form refs/heads/name - // or refs/stashes/name, so we need to parse them + // or refs/stashes/name, so we need to parse them to get names parsedRef := strings.Split(stash.BranchName, "/") - var branch string - if len(parsedRef) > 2 { - branch = parsedRef[2] - } else { - return nil, fmt.Errorf("Bad reference: %s", stash.BranchName) + if len(parsedRef) != 3 { + return nil, fmt.Errorf("Invalid branch name: %s", stash.BranchName) } + branch := parsedRef[2] - parsedRef = strings.Split(stash.BranchName, "/") - var stashRef string - if len(parsedRef) > 2 { - stashRef = parsedRef[2] - } else { + parsedRef = strings.Split(stash.StashReference, "/") + if len(parsedRef) != 3 { return nil, fmt.Errorf("Bad reference: %s", stash.StashReference) } + stashRef := parsedRef[2] + return sql.NewRow(stashRef, stash.Name, branch, commitHash.String(), stash.Description), nil } From a0c800690da8f1b5670edf262152ed3749095e71 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Tue, 10 Jun 2025 16:39:51 -0700 Subject: [PATCH 12/14] Requested changes pt 2 --- go/cmd/dolt/commands/stashcmds/stash.go | 12 ++---- .../doltcore/sqle/dprocedures/dolt_stash.go | 41 +++---------------- .../doltcore/sqle/dtables/stashes_table.go | 18 ++------ 3 files changed, 14 insertions(+), 57 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index f858b4ebe6a..f1f260ccdc4 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -39,10 +39,6 @@ var StashCommands = cli.NewSubCommandHandlerWithUnspecified("stash", "Stash the StashPopCmd{}, }) -const ( - AllFlag = "all" -) - var stashDocs = cli.CommandDocumentationContent{ ShortDesc: "Stash the changes in a dirty working directory away.", LongDesc: `Use dolt stash when you want to record the current state of the working directory and the index, but want to go back to a clean working directory. @@ -78,7 +74,7 @@ func (cmd StashCmd) Docs() *cli.CommandDocumentation { func (cmd StashCmd) ArgParser() *argparser.ArgParser { ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0) ap.SupportsFlag(cli.IncludeUntrackedFlag, "u", "Untracked tables are also stashed.") - ap.SupportsFlag(AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") + ap.SupportsFlag(cli.AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") return ap } @@ -143,7 +139,7 @@ func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, } // There are unstaged changes, is --all set? If so, nothing else matters. Stash them. - if apr.Contains(AllFlag) { + if apr.Contains(cli.AllFlag) { return true, nil } @@ -206,13 +202,13 @@ func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPars // stage untracked files to include them in the stash, // but do not include them in added table set, // because they should not be staged when popped. - if apr.Contains(cli.IncludeUntrackedFlag) || apr.Contains(AllFlag) { + if apr.Contains(cli.IncludeUntrackedFlag) || apr.Contains(cli.AllFlag) { allTblsToBeStashed, err = doltdb.UnionTableNames(ctx, roots.Staged, roots.Working) if err != nil { return err } - roots, err = actions.StageTables(ctx, roots, allTblsToBeStashed, !apr.Contains(AllFlag)) + roots, err = actions.StageTables(ctx, roots, allTblsToBeStashed, !apr.Contains(cli.AllFlag)) if err != nil { return err } diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 632dc9766c0..eb212f6c49d 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -155,12 +155,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* return err } - err = updateWorkingSetFromRoots(ctx, dbData, roots) - if err != nil { - return err - } - - return nil + return updateWorkingSetFromRoots(ctx, dbData, roots) } func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) error { @@ -191,29 +186,15 @@ func doStashPop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName str return err } - err = dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) - if err != nil { - return err - } - - return nil + return dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) } func doStashDrop(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string, idx int) error { - err := dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) - if err != nil { - return err - } - - return nil + return dbData.Ddb.RemoveStashAtIdx(ctx, idx, stashName) } func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName string) error { - err := dbData.Ddb.RemoveAllStashes(ctx, stashName) - if err != nil { - return err - } - return nil + return dbData.Ddb.RemoveAllStashes(ctx, stashName) } func stashedTableSets(ctx context.Context, roots doltdb.Roots) ([]doltdb.TableName, []doltdb.TableName, error) { @@ -359,12 +340,7 @@ func updateWorkingSetFromRoots(ctx *sql.Context, dbData env.DbData[*sql.Context] Description: "updated from dolt environment", } - err = dbData.Ddb.UpdateWorkingSet(ctx, ws.Ref(), ws, h, wsm, nil) - if err != nil { - return err - } - - return nil + return dbData.Ddb.UpdateWorkingSet(ctx, ws.Ref(), ws, h, wsm, nil) } func parseStashIndex(apr *argparser.ArgParseResults) (int, error) { @@ -423,12 +399,7 @@ func updateWorkingRoot(ctx *sql.Context, dbData env.DbData[*sql.Context], newRoo Description: "updated from dolt environment", } - err = dbData.Ddb.UpdateWorkingSet(ctx, wsRef, ws.WithWorkingRoot(newRoot), h, wsm, nil) - if err != nil { - return err - } - - return nil + return dbData.Ddb.UpdateWorkingSet(ctx, wsRef, ws.WithWorkingRoot(newRoot), h, wsm, nil) } // gatherCommitData is a helper function that returns the commit and commit metadata associated with the current head diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index e66b86c8779..587d6ec230b 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -16,11 +16,10 @@ package dtables import ( "fmt" - "io" - "strings" - + "github.com/dolthub/dolt/go/libraries/doltcore/ref" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" + "io" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/libraries/doltcore/schema" @@ -151,17 +150,8 @@ func (itr *StashItr) Next(*sql.Context) (sql.Row, error) { // BranchName and StashReference are of the form refs/heads/name // or refs/stashes/name, so we need to parse them to get names - parsedRef := strings.Split(stash.BranchName, "/") - if len(parsedRef) != 3 { - return nil, fmt.Errorf("Invalid branch name: %s", stash.BranchName) - } - branch := parsedRef[2] - - parsedRef = strings.Split(stash.StashReference, "/") - if len(parsedRef) != 3 { - return nil, fmt.Errorf("Bad reference: %s", stash.StashReference) - } - stashRef := parsedRef[2] + branch := ref.NewBranchRef(stash.BranchName).GetPath() + stashRef := ref.NewStashRef(stash.StashReference).GetPath() return sql.NewRow(stashRef, stash.Name, branch, commitHash.String(), stash.Description), nil } From 98b7a35a4aeb023a0f9cd7b0f0cdc80b500ea3e0 Mon Sep 17 00:00:00 2001 From: NathanGabrielson Date: Tue, 10 Jun 2025 23:48:30 +0000 Subject: [PATCH 13/14] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/libraries/doltcore/sqle/dtables/stashes_table.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index 587d6ec230b..fa942a16ab5 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -16,12 +16,13 @@ package dtables import ( "fmt" - "github.com/dolthub/dolt/go/libraries/doltcore/ref" + "io" + "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/types" - "io" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/libraries/doltcore/ref" "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/index" From a3bc2ba365f5d423f8918aec389279a31de2ef5f Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Tue, 10 Jun 2025 16:53:30 -0700 Subject: [PATCH 14/14] Better stash field name --- go/cmd/dolt/commands/stashcmds/list.go | 2 +- go/libraries/doltcore/doltdb/stash.go | 12 ++++++------ go/libraries/doltcore/sqle/dtables/stashes_table.go | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/list.go b/go/cmd/dolt/commands/stashcmds/list.go index 9bde54702fb..97149c12a93 100644 --- a/go/cmd/dolt/commands/stashcmds/list.go +++ b/go/cmd/dolt/commands/stashcmds/list.go @@ -90,7 +90,7 @@ func listStashes(ctx context.Context, dEnv *env.DoltEnv) error { if err != nil { return err } - cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchName, commitHash.String(), stash.Description)) + cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) } return nil } diff --git a/go/libraries/doltcore/doltdb/stash.go b/go/libraries/doltcore/doltdb/stash.go index af11e77d185..a5be6c3b0d7 100644 --- a/go/libraries/doltcore/doltdb/stash.go +++ b/go/libraries/doltcore/doltdb/stash.go @@ -26,11 +26,11 @@ import ( ) type Stash struct { - Name string - BranchName string - Description string - HeadCommit *Commit - StashReference string + Name string + BranchReference string + Description string + HeadCommit *Commit + StashReference string } const ( @@ -78,7 +78,7 @@ func getStashList(ctx context.Context, ds datas.Dataset, vrw types.ValueReadWrit } s.HeadCommit = headCommit - s.BranchName = meta.BranchName + s.BranchReference = meta.BranchName s.Description = meta.Description s.StashReference = reference diff --git a/go/libraries/doltcore/sqle/dtables/stashes_table.go b/go/libraries/doltcore/sqle/dtables/stashes_table.go index fa942a16ab5..59fcd01fdd0 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -151,7 +151,7 @@ func (itr *StashItr) Next(*sql.Context) (sql.Row, error) { // BranchName and StashReference are of the form refs/heads/name // or refs/stashes/name, so we need to parse them to get names - branch := ref.NewBranchRef(stash.BranchName).GetPath() + branch := ref.NewBranchRef(stash.BranchReference).GetPath() stashRef := ref.NewStashRef(stash.StashReference).GetPath() return sql.NewRow(stashRef, stash.Name, branch, commitHash.String(), stash.Description), nil