From 94730adc71f0c86029d48a6858e49f99bdc0def8 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Tue, 17 Jun 2025 17:34:05 -0700 Subject: [PATCH 01/11] Rough draft for stash migration --- go/cmd/dolt/commands/stashcmds/stash.go | 229 +++++++++++++++++- go/cmd/dolt/doltcmd/doltcmd.go | 2 +- .../doltcore/sqle/dprocedures/dolt_stash.go | 2 +- .../bats/helper/local-remote.bash | 1 - integration-tests/bats/stash.bats | 1 + 5 files changed, 225 insertions(+), 10 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index f1f260ccdc4..92f240775ec 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -15,9 +15,15 @@ package stashcmds import ( + "bytes" "context" "errors" "fmt" + "github.com/dolthub/dolt/go/cmd/dolt/errhand" + "github.com/dolthub/dolt/go/store/hash" + "github.com/dolthub/go-mysql-server/sql" + "strconv" + "strings" "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" @@ -83,25 +89,217 @@ func (cmd StashCmd) EventType() eventsapi.ClientEventType { return eventsapi.ClientEventType_STASH } +// generateStashSql returns the query that will call the `DOLT_ADD` stored proceudre. +func generateStashSql(apr *argparser.ArgParseResults, subcommand string) string { + var buffer bytes.Buffer + first := true + buffer.WriteString("CALL DOLT_STASH(") + + write := func(s string) { + if !first { + buffer.WriteString(", ") + } + buffer.WriteString("'") + buffer.WriteString(s) + buffer.WriteString("'") + first = false + } + + write(subcommand) + write(doltdb.DoltCliRef) // Cli always uses "dolt-cli" stash reference + if len(apr.Args) == 2 { + // Add stash identifier (i.e. "stash@{0}") + write(apr.Arg(1)) + } + + if apr.Contains(cli.AllFlag) { + write("-a") + } + if apr.Contains(cli.IncludeUntrackedFlag) { + write("-u") + } + + buffer.WriteString(")") + return buffer.String() +} + // Exec executes the command func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - ap := cmd.ArgParser() - help, _ := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, stashDocs, ap)) - apr := cli.ParseArgsOrDie(ap, args, help) + ap := cli.CreateStashArgParser() + apr, _, terminate, status := commands.ParseArgsOrPrintHelp(ap, commandStr, args, stashDocs) + if terminate { + return status + } + if len(apr.Args) > 2 { + cli.PrintErrln(fmt.Errorf("dolt stash takes 2 arguments, received %d", len(apr.Args))) + return 1 + } + roots, err := dEnv.Roots(ctx) + if err != nil { + cli.PrintErrln(fmt.Errorf("couldn't get working root, cause: %s", err.Error())) + return 1 + } + + subcommand := "push" + if len(apr.Args) > 0 { + subcommand = strings.ToLower(apr.Arg(0)) + } - if !dEnv.DoltDB(ctx).Format().UsesFlatbuffers() { - cli.PrintErrln(ErrStashNotSupportedForOldFormat.Error()) + var rowIter sql.RowIter + var curBranchName string + var commit *doltdb.Commit + var commitMeta *datas.CommitMeta + var commitHash hash.Hash + var stashes []*doltdb.Stash + idx, err := parseStashIndex(apr) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) return 1 } + switch subcommand { + case "push": + hasChanges, err := hasLocalChanges(ctx, dEnv, roots, apr) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + if !hasChanges { + cli.Println("No local changes to save") + return 0 + } + curBranchName, commit, commitMeta, err = pushPrintData(ctx, dEnv) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + case "pop", "drop": + commitHash, err = dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, doltdb.DoltCliRef) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + case "list": + stashes, err = dEnv.DoltDB(ctx).GetCommandLineStashes(ctx) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + } - err := stashChanges(ctx, dEnv, apr) + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) if err != nil { - commands.PrintStagingError(err) + cli.PrintErrln(errhand.VerboseErrorFromError(err)) return 1 } + if closeFunc != nil { + defer closeFunc() + } + + if subcommand != "list" { + _, rowIter, _, err = queryist.Query(sqlCtx, generateStashSql(apr, subcommand)) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + } + + switch subcommand { + case "push": + err = printStashPush(curBranchName, commit, commitMeta) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + case "drop": + err = printStashDrop(idx, commitHash) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + case "pop": + ret := commands.StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) + if ret != 0 { + cli.Println("The stash entry is kept in case you need it again.") + return 1 + } + err = printStashDrop(idx, commitHash) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + case "list": + err = printStashList(stashes) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + } + + if subcommand != "list" { + _, err = sql.RowIterToRows(sqlCtx, rowIter) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + } + return 0 } +func printStashDrop(idx int, stashHash hash.Hash) error { + cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String())) + return nil +} + +func printStashList(stashes []*doltdb.Stash) error { + for _, stash := range stashes { + commitHash, err := stash.HeadCommit.HashOf() + if err != nil { + return err + } + cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) + } + return nil +} + +// gatherPrintData is a helper function that returns the current branch and the commit meta for the most recent commit +func pushPrintData(ctx context.Context, dEnv *env.DoltEnv) (string, *doltdb.Commit, *datas.CommitMeta, error) { + curHeadRef, err := dEnv.RepoStateReader().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 := dEnv.DoltDB(ctx).Resolve(ctx, commitSpec, curHeadRef) + if err != nil { + return "", nil, nil, err + } + commit, ok := optCmt.ToCommit() + if !ok { + return "", nil, nil, err + } + + commitMeta, err := commit.GetCommitMeta(ctx) + if err != nil { + return "", nil, nil, err + } + + return curBranchName, commit, commitMeta, nil +} + +func printStashPush(curBranchName string, commit *doltdb.Commit, commitMeta *datas.CommitMeta) error { + commitHash, err := commit.HashOf() + if err != nil { + return err + } + + cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", curBranchName, commitHash.String(), commitMeta.Description)) + return nil +} + func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { headRoot, err := dEnv.HeadRoot(ctx) if err != nil { @@ -304,3 +502,20 @@ func stashedTableSets(ctx context.Context, roots doltdb.Roots) ([]doltdb.TableNa return allTbls, addedTblsInStaged, nil } + +func parseStashIndex(apr *argparser.ArgParseResults) (int, error) { + idx := 0 + + if apr.NArg() > 1 { + stashID := apr.Arg(1) + 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 +} diff --git a/go/cmd/dolt/doltcmd/doltcmd.go b/go/cmd/dolt/doltcmd/doltcmd.go index 22814d21903..a00f0fcfa33 100644 --- a/go/cmd/dolt/doltcmd/doltcmd.go +++ b/go/cmd/dolt/doltcmd/doltcmd.go @@ -83,7 +83,7 @@ var doltSubCommands = []cli.Command{ dumpDocsCommand, dumpZshCommand, docscmds.Commands, - stashcmds.StashCommands, + stashcmds.StashCmd{}, &commands.Assist{}, commands.ProfileCmd{}, commands.QueryDiff{}, diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index eb212f6c49d..59833eadcf8 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -500,7 +500,7 @@ func handleMerge(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName st 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) + "Please commit your changes or stash them before you merge.\nAborting\n\"The stash entry is kept in case you need it again.\n", idx, tblNames) return nil, nil, nil, status } diff --git a/integration-tests/bats/helper/local-remote.bash b/integration-tests/bats/helper/local-remote.bash index 268b80915a3..0a27df843c9 100644 --- a/integration-tests/bats/helper/local-remote.bash +++ b/integration-tests/bats/helper/local-remote.bash @@ -128,7 +128,6 @@ SKIP_SERVER_TESTS=$(cat <<-EOM ~doltpy.bats~ ~sql-batch.bats~ ~send-metrics.bats~ -~stash.bats~ ~commit.bats~ ~sql-commit.bats~ ~reset.bats~ diff --git a/integration-tests/bats/stash.bats b/integration-tests/bats/stash.bats index b87cd788394..0c6a54ed24c 100644 --- a/integration-tests/bats/stash.bats +++ b/integration-tests/bats/stash.bats @@ -314,6 +314,7 @@ teardown() { dolt sql -q "INSERT INTO test VALUES (1, 'b')" run dolt stash pop + echo "$output" [ "$status" -eq 1 ] [[ "$output" =~ "error: Your local changes to the following tables would be overwritten by applying stash" ]] || false [[ "$output" =~ "The stash entry is kept in case you need it again." ]] || false From 3258d0fff570d29e46c4b0453a0832b02573ffef Mon Sep 17 00:00:00 2001 From: NathanGabrielson Date: Wed, 18 Jun 2025 00:44:48 +0000 Subject: [PATCH 02/11] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/cmd/dolt/commands/stashcmds/stash.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 92f240775ec..9e26364cf1f 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -19,14 +19,14 @@ import ( "context" "errors" "fmt" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - "github.com/dolthub/dolt/go/store/hash" - "github.com/dolthub/go-mysql-server/sql" "strconv" "strings" + "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" + "github.com/dolthub/dolt/go/cmd/dolt/errhand" eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" "github.com/dolthub/dolt/go/libraries/doltcore/diff" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" @@ -34,6 +34,7 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/env/actions" "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/store/datas" + "github.com/dolthub/dolt/go/store/hash" ) var ErrStashNotSupportedForOldFormat = errors.New("stash is not supported for old storage format") From 50a3be6ea2cecf0d4369ea2880220cb421070175 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Wed, 18 Jun 2025 13:57:52 -0700 Subject: [PATCH 03/11] Query system table, code is now less headache inducing --- go/cmd/dolt/commands/stashcmds/list.go | 16 +- go/cmd/dolt/commands/stashcmds/stash.go | 278 ++++++++++++++---------- integration-tests/bats/stash.bats | 1 + 3 files changed, 166 insertions(+), 129 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/list.go b/go/cmd/dolt/commands/stashcmds/list.go index 97149c12a93..308d7d9b446 100644 --- a/go/cmd/dolt/commands/stashcmds/list.go +++ b/go/cmd/dolt/commands/stashcmds/list.go @@ -14,19 +14,7 @@ package stashcmds -import ( - "context" - "fmt" - - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/cmd/dolt/commands" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" - "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/utils/argparser" -) - -var stashListDocs = cli.CommandDocumentationContent{ +/*var stashListDocs = cli.CommandDocumentationContent{ ShortDesc: "List the stash entries that you currently have.", LongDesc: `Each stash entry is listed with its name (e.g. stash@{0} is the latest entry, stash@{1} is the one before, etc.), the name of the branch that was current when the entry was made, and a short description of the commit the entry was based on. `, @@ -93,4 +81,4 @@ func listStashes(ctx context.Context, dEnv *env.DoltEnv) error { cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) } return nil -} +}*/ diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 9e26364cf1f..7cddac4f4c4 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -19,14 +19,14 @@ import ( "context" "errors" "fmt" + "github.com/dolthub/dolt/go/cmd/dolt/errhand" + "github.com/dolthub/dolt/go/libraries/doltcore/ref" + "github.com/dolthub/go-mysql-server/sql" "strconv" "strings" - "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" "github.com/dolthub/dolt/go/libraries/doltcore/diff" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" @@ -34,7 +34,6 @@ import ( "github.com/dolthub/dolt/go/libraries/doltcore/env/actions" "github.com/dolthub/dolt/go/libraries/utils/argparser" "github.com/dolthub/dolt/go/store/datas" - "github.com/dolthub/dolt/go/store/hash" ) var ErrStashNotSupportedForOldFormat = errors.New("stash is not supported for old storage format") @@ -42,7 +41,6 @@ var ErrStashNotSupportedForOldFormat = errors.New("stash is not supported for ol var StashCommands = cli.NewSubCommandHandlerWithUnspecified("stash", "Stash the changes in a dirty working directory away.", false, StashCmd{}, []cli.Command{ StashClearCmd{}, StashDropCmd{}, - StashListCmd{}, StashPopCmd{}, }) @@ -90,7 +88,7 @@ func (cmd StashCmd) EventType() eventsapi.ClientEventType { return eventsapi.ClientEventType_STASH } -// generateStashSql returns the query that will call the `DOLT_ADD` stored proceudre. +// generateStashSql returns the query that will call the `DOLT_STASH` stored proceudre. func generateStashSql(apr *argparser.ArgParseResults, subcommand string) string { var buffer bytes.Buffer first := true @@ -122,6 +120,51 @@ func generateStashSql(apr *argparser.ArgParseResults, subcommand string) string buffer.WriteString(")") return buffer.String() + +} + +func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, dEnv *env.DoltEnv) ([]*doltdb.Stash, error) { + qry := fmt.Sprintf("select stash_id, branch, hash, commit_message from dolt_stashes where name = '%s'", doltdb.DoltCliRef) + rows, err := commands.GetRowsForSql(queryist, sqlCtx, qry) + if err != nil { + return nil, err + } + + var stashes []*doltdb.Stash + for _, s := range rows { + id, ok := s[0].(string) + if !ok { + return nil, fmt.Errorf("invalid stash id") + } + + branch, ok := s[1].(string) + if !ok { + return nil, fmt.Errorf("invalid stash branch") + } + fullBranch := ref.NewBranchRef(branch).String() + + stashHash, ok := s[2].(string) + if !ok { + return nil, fmt.Errorf("invalid stash hash") + } + maybeCommit, err := actions.MaybeGetCommit(ctx, dEnv, stashHash) + if err != nil { + return nil, err + } + + msg, ok := s[3].(string) + if !ok { + return nil, fmt.Errorf("invalid stash message") + } + stashes = append(stashes, &doltdb.Stash{ + Name: id, + BranchReference: fullBranch, + Description: msg, + HeadCommit: maybeCommit, + }) + } + + return stashes, nil } // Exec executes the command @@ -135,134 +178,32 @@ func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, cli.PrintErrln(fmt.Errorf("dolt stash takes 2 arguments, received %d", len(apr.Args))) return 1 } - roots, err := dEnv.Roots(ctx) - if err != nil { - cli.PrintErrln(fmt.Errorf("couldn't get working root, cause: %s", err.Error())) - return 1 - } subcommand := "push" if len(apr.Args) > 0 { subcommand = strings.ToLower(apr.Arg(0)) } - var rowIter sql.RowIter - var curBranchName string - var commit *doltdb.Commit - var commitMeta *datas.CommitMeta - var commitHash hash.Hash - var stashes []*doltdb.Stash - idx, err := parseStashIndex(apr) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } + var err error switch subcommand { case "push": - hasChanges, err := hasLocalChanges(ctx, dEnv, roots, apr) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - if !hasChanges { - cli.Println("No local changes to save") - return 0 - } - curBranchName, commit, commitMeta, err = pushPrintData(ctx, dEnv) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } + err = stashPush(ctx, cliCtx, dEnv, apr, subcommand) case "pop", "drop": - commitHash, err = dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, doltdb.DoltCliRef) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } + err = stashRemove(ctx, cliCtx, dEnv, apr, subcommand) case "list": - stashes, err = dEnv.DoltDB(ctx).GetCommandLineStashes(ctx) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } + err = listStashes(ctx, cliCtx, dEnv) + case "clear": + err = stashClear(ctx, cliCtx, apr, subcommand) } - queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) if err != nil { cli.PrintErrln(errhand.VerboseErrorFromError(err)) return 1 } - if closeFunc != nil { - defer closeFunc() - } - - if subcommand != "list" { - _, rowIter, _, err = queryist.Query(sqlCtx, generateStashSql(apr, subcommand)) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - } - - switch subcommand { - case "push": - err = printStashPush(curBranchName, commit, commitMeta) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - case "drop": - err = printStashDrop(idx, commitHash) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - case "pop": - ret := commands.StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) - if ret != 0 { - cli.Println("The stash entry is kept in case you need it again.") - return 1 - } - err = printStashDrop(idx, commitHash) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - case "list": - err = printStashList(stashes) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - } - - if subcommand != "list" { - _, err = sql.RowIterToRows(sqlCtx, rowIter) - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - } return 0 } -func printStashDrop(idx int, stashHash hash.Hash) error { - cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String())) - return nil -} - -func printStashList(stashes []*doltdb.Stash) error { - for _, stash := range stashes { - commitHash, err := stash.HeadCommit.HashOf() - if err != nil { - return err - } - cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) - } - return nil -} - // gatherPrintData is a helper function that returns the current branch and the commit meta for the most recent commit func pushPrintData(ctx context.Context, dEnv *env.DoltEnv) (string, *doltdb.Commit, *datas.CommitMeta, error) { curHeadRef, err := dEnv.RepoStateReader().CWBHeadRef(ctx) @@ -291,16 +232,109 @@ func pushPrintData(ctx context.Context, dEnv *env.DoltEnv) (string, *doltdb.Comm return curBranchName, commit, commitMeta, nil } -func printStashPush(curBranchName string, commit *doltdb.Commit, commitMeta *datas.CommitMeta) error { +func stashPush(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, apr *argparser.ArgParseResults, subcommand string) error { + var rowIter sql.RowIter + + roots, err := dEnv.Roots(ctx) + if err != nil { + return err + } + + hasChanges, err := hasLocalChanges(ctx, dEnv, roots, apr) + if err != nil { + return err + } + if !hasChanges { + cli.Println("No local changes to save") + return nil + } + curBranchName, commit, commitMeta, err := pushPrintData(ctx, dEnv) + if err != nil { + return err + } + commitHash, err := commit.HashOf() if err != nil { return err } + rowIter, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", curBranchName, commitHash.String(), commitMeta.Description)) + _, err = sql.RowIterToRows(sqlCtx, rowIter) + return err +} + +func stashRemove(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, apr *argparser.ArgParseResults, subcommand string) error { + idx, err := parseStashIndex(apr) + if err != nil { + return err + } + commitHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, doltdb.DoltCliRef) + if err != nil { + return err + } + + rowIter, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + + if subcommand == "pop" { + ret := commands.StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) + if ret != 0 { + cli.Println("The stash entry is kept in case you need it again.") + return err + } + } + + cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, commitHash.String())) + _, err = sql.RowIterToRows(sqlCtx, rowIter) + return err +} + +func listStashes(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv) error { + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + stashes, err := getStashesSQL(ctx, sqlCtx, queryist, dEnv) + + for _, stash := range stashes { + commitHash, err := stash.HeadCommit.HashOf() + if err != nil { + return err + } + cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) + } + return nil } +func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { + rowIter, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + _, err = sql.RowIterToRows(sqlCtx, rowIter) + return err +} + func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { headRoot, err := dEnv.HeadRoot(ctx) if err != nil { @@ -371,6 +405,20 @@ func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, return true, nil } +func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, *sql.Context, func(), error) { + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) + if err != nil { + return nil, nil, nil, err + } + + _, rowIter, _, err := queryist.Query(sqlCtx, generateStashSql(apr, subcommand)) + if err != nil { + return nil, nil, nil, err + } + + return rowIter, sqlCtx, closeFunc, nil +} + func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgParseResults) error { roots, err := dEnv.Roots(ctx) if err != nil { diff --git a/integration-tests/bats/stash.bats b/integration-tests/bats/stash.bats index 0c6a54ed24c..53c24f6205e 100644 --- a/integration-tests/bats/stash.bats +++ b/integration-tests/bats/stash.bats @@ -40,6 +40,7 @@ teardown() { [[ "$output" =~ "stash@{0}" ]] || false run dolt stash pop + echo "$output" [ "$status" -eq 0 ] [[ "$output" =~ "Dropped refs/stash@{0}" ]] || false From 0df5282be4fb7f1ba0d096ef21e625a26d1d115c Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Wed, 18 Jun 2025 14:06:17 -0700 Subject: [PATCH 04/11] Couple things I forgot to change back --- go/cmd/dolt/commands/stashcmds/list.go | 14 ++++++++++++-- go/cmd/dolt/commands/stashcmds/stash.go | 10 ++-------- integration-tests/bats/stash.bats | 2 -- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/list.go b/go/cmd/dolt/commands/stashcmds/list.go index 308d7d9b446..d07d058c492 100644 --- a/go/cmd/dolt/commands/stashcmds/list.go +++ b/go/cmd/dolt/commands/stashcmds/list.go @@ -14,7 +14,17 @@ package stashcmds -/*var stashListDocs = cli.CommandDocumentationContent{ +import ( + "fmt" + "github.com/dolthub/dolt/go/cmd/dolt/cli" + "github.com/dolthub/dolt/go/cmd/dolt/commands" + "github.com/dolthub/dolt/go/cmd/dolt/errhand" + eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" + "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/utils/argparser" +) + +var stashListDocs = cli.CommandDocumentationContent{ ShortDesc: "List the stash entries that you currently have.", LongDesc: `Each stash entry is listed with its name (e.g. stash@{0} is the latest entry, stash@{1} is the one before, etc.), the name of the branch that was current when the entry was made, and a short description of the commit the entry was based on. `, @@ -81,4 +91,4 @@ func listStashes(ctx context.Context, dEnv *env.DoltEnv) error { cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) } return nil -}*/ +} diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 7cddac4f4c4..7f83d9c51fe 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -38,12 +38,6 @@ import ( var ErrStashNotSupportedForOldFormat = errors.New("stash is not supported for old storage format") -var StashCommands = cli.NewSubCommandHandlerWithUnspecified("stash", "Stash the changes in a dirty working directory away.", false, StashCmd{}, []cli.Command{ - StashClearCmd{}, - StashDropCmd{}, - StashPopCmd{}, -}) - 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. @@ -191,7 +185,7 @@ func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, case "pop", "drop": err = stashRemove(ctx, cliCtx, dEnv, apr, subcommand) case "list": - err = listStashes(ctx, cliCtx, dEnv) + err = stashList(ctx, cliCtx, dEnv) case "clear": err = stashClear(ctx, cliCtx, apr, subcommand) } @@ -302,7 +296,7 @@ func stashRemove(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, return err } -func listStashes(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv) error { +func stashList(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv) error { queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) if err != nil { return err diff --git a/integration-tests/bats/stash.bats b/integration-tests/bats/stash.bats index 53c24f6205e..b87cd788394 100644 --- a/integration-tests/bats/stash.bats +++ b/integration-tests/bats/stash.bats @@ -40,7 +40,6 @@ teardown() { [[ "$output" =~ "stash@{0}" ]] || false run dolt stash pop - echo "$output" [ "$status" -eq 0 ] [[ "$output" =~ "Dropped refs/stash@{0}" ]] || false @@ -315,7 +314,6 @@ teardown() { dolt sql -q "INSERT INTO test VALUES (1, 'b')" run dolt stash pop - echo "$output" [ "$status" -eq 1 ] [[ "$output" =~ "error: Your local changes to the following tables would be overwritten by applying stash" ]] || false [[ "$output" =~ "The stash entry is kept in case you need it again." ]] || false From 531b7c61c87b4673dc3c0f2b7e5bc7603be1432a Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Wed, 18 Jun 2025 15:04:06 -0700 Subject: [PATCH 05/11] Moving stuff around, small fixes --- go/cmd/dolt/commands/stashcmds/list.go | 1 + go/cmd/dolt/commands/stashcmds/stash.go | 220 ++++++++++++------------ 2 files changed, 110 insertions(+), 111 deletions(-) diff --git a/go/cmd/dolt/commands/stashcmds/list.go b/go/cmd/dolt/commands/stashcmds/list.go index d07d058c492..ff64b573b89 100644 --- a/go/cmd/dolt/commands/stashcmds/list.go +++ b/go/cmd/dolt/commands/stashcmds/list.go @@ -15,6 +15,7 @@ package stashcmds import ( + "context" "fmt" "github.com/dolthub/dolt/go/cmd/dolt/cli" "github.com/dolthub/dolt/go/cmd/dolt/commands" diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go index 7f83d9c51fe..b57548529c1 100644 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ b/go/cmd/dolt/commands/stashcmds/stash.go @@ -82,85 +82,6 @@ func (cmd StashCmd) EventType() eventsapi.ClientEventType { return eventsapi.ClientEventType_STASH } -// generateStashSql returns the query that will call the `DOLT_STASH` stored proceudre. -func generateStashSql(apr *argparser.ArgParseResults, subcommand string) string { - var buffer bytes.Buffer - first := true - buffer.WriteString("CALL DOLT_STASH(") - - write := func(s string) { - if !first { - buffer.WriteString(", ") - } - buffer.WriteString("'") - buffer.WriteString(s) - buffer.WriteString("'") - first = false - } - - write(subcommand) - write(doltdb.DoltCliRef) // Cli always uses "dolt-cli" stash reference - if len(apr.Args) == 2 { - // Add stash identifier (i.e. "stash@{0}") - write(apr.Arg(1)) - } - - if apr.Contains(cli.AllFlag) { - write("-a") - } - if apr.Contains(cli.IncludeUntrackedFlag) { - write("-u") - } - - buffer.WriteString(")") - return buffer.String() - -} - -func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, dEnv *env.DoltEnv) ([]*doltdb.Stash, error) { - qry := fmt.Sprintf("select stash_id, branch, hash, commit_message from dolt_stashes where name = '%s'", doltdb.DoltCliRef) - rows, err := commands.GetRowsForSql(queryist, sqlCtx, qry) - if err != nil { - return nil, err - } - - var stashes []*doltdb.Stash - for _, s := range rows { - id, ok := s[0].(string) - if !ok { - return nil, fmt.Errorf("invalid stash id") - } - - branch, ok := s[1].(string) - if !ok { - return nil, fmt.Errorf("invalid stash branch") - } - fullBranch := ref.NewBranchRef(branch).String() - - stashHash, ok := s[2].(string) - if !ok { - return nil, fmt.Errorf("invalid stash hash") - } - maybeCommit, err := actions.MaybeGetCommit(ctx, dEnv, stashHash) - if err != nil { - return nil, err - } - - msg, ok := s[3].(string) - if !ok { - return nil, fmt.Errorf("invalid stash message") - } - stashes = append(stashes, &doltdb.Stash{ - Name: id, - BranchReference: fullBranch, - Description: msg, - HeadCommit: maybeCommit, - }) - } - - return stashes, nil -} - // Exec executes the command func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { ap := cli.CreateStashArgParser() @@ -198,37 +119,7 @@ func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, return 0 } -// gatherPrintData is a helper function that returns the current branch and the commit meta for the most recent commit -func pushPrintData(ctx context.Context, dEnv *env.DoltEnv) (string, *doltdb.Commit, *datas.CommitMeta, error) { - curHeadRef, err := dEnv.RepoStateReader().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 := dEnv.DoltDB(ctx).Resolve(ctx, commitSpec, curHeadRef) - if err != nil { - return "", nil, nil, err - } - commit, ok := optCmt.ToCommit() - if !ok { - return "", nil, nil, err - } - - commitMeta, err := commit.GetCommitMeta(ctx) - if err != nil { - return "", nil, nil, err - } - - return curBranchName, commit, commitMeta, nil -} - func stashPush(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, apr *argparser.ArgParseResults, subcommand string) error { - var rowIter sql.RowIter - roots, err := dEnv.Roots(ctx) if err != nil { return err @@ -242,7 +133,7 @@ func stashPush(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, ap cli.Println("No local changes to save") return nil } - curBranchName, commit, commitMeta, err := pushPrintData(ctx, dEnv) + curBranchName, commit, commitMeta, err := getStashPushData(ctx, dEnv) if err != nil { return err } @@ -304,8 +195,8 @@ func stashList(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv) er if closeFunc != nil { defer closeFunc() } - stashes, err := getStashesSQL(ctx, sqlCtx, queryist, dEnv) + stashes, err := getStashesSQL(ctx, sqlCtx, queryist, dEnv) for _, stash := range stashes { commitHash, err := stash.HeadCommit.HashOf() if err != nil { @@ -329,6 +220,85 @@ func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPa return err } +func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, dEnv *env.DoltEnv) ([]*doltdb.Stash, error) { + qry := fmt.Sprintf("select stash_id, branch, hash, commit_message from dolt_stashes where name = '%s'", doltdb.DoltCliRef) + rows, err := commands.GetRowsForSql(queryist, sqlCtx, qry) + if err != nil { + return nil, err + } + + var stashes []*doltdb.Stash + for _, s := range rows { + id, ok := s[0].(string) + if !ok { + return nil, fmt.Errorf("invalid stash id") + } + + branch, ok := s[1].(string) + if !ok { + return nil, fmt.Errorf("invalid stash branch") + } + fullBranch := ref.NewBranchRef(branch).String() + + stashHash, ok := s[2].(string) + if !ok { + return nil, fmt.Errorf("invalid stash hash") + } + maybeCommit, err := actions.MaybeGetCommit(ctx, dEnv, stashHash) + if err != nil { + return nil, err + } + + msg, ok := s[3].(string) + if !ok { + return nil, fmt.Errorf("invalid stash message") + } + stashes = append(stashes, &doltdb.Stash{ + Name: id, + BranchReference: fullBranch, + HeadCommit: maybeCommit, + Description: msg, + }) + } + + return stashes, nil +} + +// generateStashSql returns the query that will call the `DOLT_STASH` stored proceudre. +func generateStashSql(apr *argparser.ArgParseResults, subcommand string) string { + var buffer bytes.Buffer + first := true + buffer.WriteString("CALL DOLT_STASH(") + + write := func(s string) { + if !first { + buffer.WriteString(", ") + } + buffer.WriteString("'") + buffer.WriteString(s) + buffer.WriteString("'") + first = false + } + + write(subcommand) + write(doltdb.DoltCliRef) + + if len(apr.Args) == 2 { + // Add stash identifier (i.e. "stash@{0}") + write(apr.Arg(1)) + } + + if apr.Contains(cli.AllFlag) { + write("-a") + } + if apr.Contains(cli.IncludeUntrackedFlag) { + write("-u") + } + + buffer.WriteString(")") + return buffer.String() +} + func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { headRoot, err := dEnv.HeadRoot(ctx) if err != nil { @@ -399,6 +369,34 @@ func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, return true, nil } +// getStashPushData is a helper function that returns the current branch, the commit itself, and the commit meta for the most recent commit +func getStashPushData(ctx context.Context, dEnv *env.DoltEnv) (string, *doltdb.Commit, *datas.CommitMeta, error) { + curHeadRef, err := dEnv.RepoStateReader().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 := dEnv.DoltDB(ctx).Resolve(ctx, commitSpec, curHeadRef) + if err != nil { + return "", nil, nil, err + } + commit, ok := optCmt.ToCommit() + if !ok { + return "", nil, nil, err + } + + commitMeta, err := commit.GetCommitMeta(ctx) + if err != nil { + return "", nil, nil, err + } + + return curBranchName, commit, commitMeta, nil +} + func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, *sql.Context, func(), error) { queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) if err != nil { From aefb1785515d3d683fc20289612e091a19f4fcb4 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Fri, 20 Jun 2025 14:17:21 -0700 Subject: [PATCH 06/11] No more dEnv, get rid of bad sql interpolation --- go/cmd/dolt/commands/stash.go | 378 ++++++++++++ go/cmd/dolt/commands/stashcmds/clear.go | 87 --- go/cmd/dolt/commands/stashcmds/drop.go | 112 ---- go/cmd/dolt/commands/stashcmds/list.go | 95 --- go/cmd/dolt/commands/stashcmds/pop.go | 215 ------- go/cmd/dolt/commands/stashcmds/stash.go | 562 ------------------ go/cmd/dolt/doltcmd/doltcmd.go | 3 +- .../doltcore/sqle/dprocedures/dolt_stash.go | 4 + integration-tests/bats/stash.bats | 8 +- 9 files changed, 387 insertions(+), 1077 deletions(-) create mode 100644 go/cmd/dolt/commands/stash.go delete mode 100644 go/cmd/dolt/commands/stashcmds/clear.go delete mode 100644 go/cmd/dolt/commands/stashcmds/drop.go delete mode 100644 go/cmd/dolt/commands/stashcmds/list.go delete mode 100644 go/cmd/dolt/commands/stashcmds/pop.go delete mode 100644 go/cmd/dolt/commands/stashcmds/stash.go diff --git a/go/cmd/dolt/commands/stash.go b/go/cmd/dolt/commands/stash.go new file mode 100644 index 00000000000..43b67a7b208 --- /dev/null +++ b/go/cmd/dolt/commands/stash.go @@ -0,0 +1,378 @@ +// Copyright 2023 Dolthub, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package commands + +import ( + "bytes" + "context" + "fmt" + "github.com/dolthub/dolt/go/cmd/dolt/errhand" + "github.com/dolthub/dolt/go/libraries/doltcore/ref" + "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" + "github.com/dolthub/dolt/go/store/hash" + "github.com/dolthub/go-mysql-server/sql" + "github.com/gocraft/dbr/v2" + "github.com/gocraft/dbr/v2/dialect" + "strconv" + "strings" + + "github.com/dolthub/dolt/go/cmd/dolt/cli" + eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" + "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" + "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/utils/argparser" +) + +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. + +The command saves your local modifications away and reverts the working directory to match the HEAD commit. The stash entries that are saved away can be listed with 'dolt stash list'. +`, + Synopsis: []string{ + "", // this is for `dolt stash` itself. + "list", + "pop {{.LessThan}}stash{{.GreaterThan}}", + "clear", + "drop {{.LessThan}}stash{{.GreaterThan}}", + }, +} + +type StashCmd struct{} + +// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command +func (cmd StashCmd) Name() string { + return "stash" +} + +// Description returns a description of the command +func (cmd StashCmd) Description() string { + return "Stash the changes in a dirty working directory away." +} + +func (cmd StashCmd) Docs() *cli.CommandDocumentation { + ap := cmd.ArgParser() + return cli.NewCommandDocumentation(stashDocs, ap) +} + +func (cmd StashCmd) ArgParser() *argparser.ArgParser { + ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0) + ap.SupportsFlag(cli.IncludeUntrackedFlag, "u", "Untracked tables are also stashed.") + ap.SupportsFlag(cli.AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") + return ap +} + +// EventType returns the type of the event to log +func (cmd StashCmd) EventType() eventsapi.ClientEventType { + return eventsapi.ClientEventType_STASH +} + +// Exec executes the command +func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, _ *env.DoltEnv, cliCtx cli.CliContext) int { + ap := cli.CreateStashArgParser() + + apr, _, terminate, status := ParseArgsOrPrintHelp(ap, commandStr, args, stashDocs) + if terminate { + return status + } + if len(apr.Args) > 2 { + cli.PrintErrln(fmt.Errorf("dolt stash takes 2 arguments, received %d", len(apr.Args))) + return 1 + } + + subcommand := "push" + if len(apr.Args) > 0 { + subcommand = strings.ToLower(apr.Arg(0)) + } + + var err error + switch subcommand { + case "push": + err = stashPush(ctx, cliCtx, apr, subcommand) + case "pop", "drop": + err = stashRemove(ctx, cliCtx, apr, subcommand) + case "list": + err = stashList(ctx, cliCtx) + case "clear": + err = stashClear(ctx, cliCtx, apr, subcommand) + default: + err = fmt.Errorf("unknown stash subcommand %s", subcommand) + } + + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + return 0 +} + +func stashPush(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { + rowIter, queryist, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + + stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 1) + if err != nil { + return err + } + stash := stashes[0] + stashHash, err := stash.HeadCommit.HashOf() + if err != nil { + return err + } + hashStr := stashHash.String() + + cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", stash.BranchReference, hashStr, stash.Description)) + _, err = sql.RowIterToRows(sqlCtx, rowIter) + return err +} + +func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { + idx, err := parseStashIndex(apr) + if err != nil { + return err + } + + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + + stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 0) + if err != nil { + return err + } + if len(stashes) == 0 { + return fmt.Errorf("No stash entries found.") + } + if len(stashes)-1 < idx { + return fmt.Errorf("stash index stash@{%d} does not exist", idx) + } + commit := stashes[idx].HeadCommit + stashHash, err := commit.HashOf() + if err != nil { + return err + } + + qry, params := generateStashSql(apr, subcommand) + interpolatedQuery, err := dbr.InterpolateForDialect(qry, params, dialect.MySQL) + if err != nil { + return err + } + _, rowIter, _, err := queryist.Query(sqlCtx, interpolatedQuery) + if err != nil { + return err + } + + if subcommand == "pop" { + var dEnv *env.DoltEnv + ret := StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) + if ret != 0 { + cli.Println("The stash entry is kept in case you need it again.") + return err + } + } + + cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String())) + _, err = sql.RowIterToRows(sqlCtx, rowIter) + return err +} + +func stashList(ctx context.Context, cliCtx cli.CliContext) error { + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + + stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 0) + for _, stash := range stashes { + commitHash, err := stash.HeadCommit.HashOf() + if err != nil { + return err + } + cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) + } + + return nil +} + +func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { + rowIter, _, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) + if err != nil { + return err + } + if closeFunc != nil { + defer closeFunc() + } + _, err = sql.RowIterToRows(sqlCtx, rowIter) + return err +} + +// getStashesSQL queries the dolt_stashes system table to return the requested number of stashes. A limit of 0 will get all stashes +func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, limit int) ([]*doltdb.Stash, error) { + sess := dsess.DSessFromSess(sqlCtx.Session) + dbName := sqlCtx.GetCurrentDatabase() + doltDb, ok := sess.GetDbData(sqlCtx, dbName) + if !ok { + return nil, fmt.Errorf("No doltdb found for %s", dbName) + } + limitStr := fmt.Sprintf("limit %d", limit) + if limit == 0 { + limitStr = "" + } + + qry := fmt.Sprintf("select stash_id, branch, hash, commit_message from dolt_stashes where name = '%s' order by stash_id ASC %s;", doltdb.DoltCliRef, limitStr) + rows, err := GetRowsForSql(queryist, sqlCtx, qry) + if err != nil { + return nil, err + } + + var stashes []*doltdb.Stash + for _, s := range rows { + id, ok := s[0].(string) + if !ok { + return nil, fmt.Errorf("Invalid stash id") + } + + branch, ok := s[1].(string) + if !ok { + return nil, fmt.Errorf("invalid stash branch") + } + fullBranch := ref.NewBranchRef(branch).String() + + stashHash, ok := s[2].(string) + if !ok { + return nil, fmt.Errorf("invalid stash hash") + } + fullHash, ok := hash.MaybeParse(stashHash) + if !ok { + return nil, fmt.Errorf("invalid stash hash") + } + + var commit *doltdb.Commit + optCommit, err := doltDb.Ddb.ReadCommit(ctx, fullHash) + if err == nil { + commit, ok = optCommit.ToCommit() + } + + msg, ok := s[3].(string) + if !ok { + return nil, fmt.Errorf("invalid stash message") + } + + stashes = append(stashes, &doltdb.Stash{ + Name: id, + BranchReference: fullBranch, + Description: msg, + HeadCommit: commit, + }) + } + + return stashes, nil +} + +// generateStashSql returns the query that will call the `DOLT_STASH` stored proceudre. +func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string, []interface{}) { + var buffer bytes.Buffer + var params []interface{} + var param bool + first := true + buffer.WriteString("CALL DOLT_STASH(") + + write := func(s string) { + if !first { + buffer.WriteString(", ") + } + if !param { + buffer.WriteString("'") + } + buffer.WriteString(s) + if !param { + buffer.WriteString("'") + } + first = false + param = false + } + + param = true + write("?") + param = true + write("?") + params = append(params, subcommand) + params = append(params, doltdb.DoltCliRef) + + if len(apr.Args) == 2 { + param = true + params = append(params, apr.Arg(1)) + write("?") + } + + if apr.Contains(cli.AllFlag) { + write("-a") + } + if apr.Contains(cli.IncludeUntrackedFlag) { + write("-u") + } + + buffer.WriteString(")") + return buffer.String(), params +} + +func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, cli.Queryist, *sql.Context, func(), error) { + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) + if err != nil { + return nil, nil, nil, nil, err + } + + qry, params := generateStashSql(apr, subcommand) + interpolatedQuery, err := dbr.InterpolateForDialect(qry, params, dialect.MySQL) + if err != nil { + return nil, nil, nil, nil, err + } + + _, rowIter, _, err := queryist.Query(sqlCtx, interpolatedQuery) + if err != nil { + return nil, nil, nil, nil, err + } + + return rowIter, queryist, sqlCtx, closeFunc, nil +} + +func parseStashIndex(apr *argparser.ArgParseResults) (int, error) { + idx := 0 + + if apr.NArg() > 1 { + stashID := apr.Arg(1) + 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 +} diff --git a/go/cmd/dolt/commands/stashcmds/clear.go b/go/cmd/dolt/commands/stashcmds/clear.go deleted file mode 100644 index e11805f4b1e..00000000000 --- a/go/cmd/dolt/commands/stashcmds/clear.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2023 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 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" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" - "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/utils/argparser" -) - -var stashClearDocs = cli.CommandDocumentationContent{ - ShortDesc: "Remove all the stash entries.", - LongDesc: `Removes all the stash entries from the current stash list. This command cannot be reverted and stash entries may not be recoverable. - -This command does not apply the stash on current working directory, use 'dolt stash pop' to apply a stash on current working directory.`, - Synopsis: []string{ - "", - }, -} - -type StashClearCmd struct{} - -// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command -func (cmd StashClearCmd) Name() string { - return "clear" -} - -// Description returns a description of the command -func (cmd StashClearCmd) Description() string { - return "Remove all the stash entries." -} - -func (cmd StashClearCmd) Docs() *cli.CommandDocumentation { - ap := cmd.ArgParser() - return cli.NewCommandDocumentation(stashClearDocs, ap) -} - -func (cmd StashClearCmd) ArgParser() *argparser.ArgParser { - ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0) - return ap -} - -// EventType returns the type of the event to log -func (cmd StashClearCmd) EventType() eventsapi.ClientEventType { - return eventsapi.ClientEventType_STASH_CLEAR -} - -// Exec executes the command -func (cmd StashClearCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - if !dEnv.DoltDB(ctx).Format().UsesFlatbuffers() { - cli.PrintErrln(ErrStashNotSupportedForOldFormat.Error()) - return 1 - } - ap := cmd.ArgParser() - help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, stashClearDocs, ap)) - apr := cli.ParseArgsOrDie(ap, args, help) - - if apr.NArg() != 0 { - usage() - return 1 - } - - err := dEnv.DoltDB(ctx).RemoveAllStashes(ctx, doltdb.DoltCliRef) - if err != nil { - return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) - } - return 0 -} diff --git a/go/cmd/dolt/commands/stashcmds/drop.go b/go/cmd/dolt/commands/stashcmds/drop.go deleted file mode 100644 index 98f00621225..00000000000 --- a/go/cmd/dolt/commands/stashcmds/drop.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2023 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 stashcmds - -import ( - "context" - "fmt" - "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" - eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" - "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/utils/argparser" -) - -var stashDropDocs = cli.CommandDocumentationContent{ - ShortDesc: "Remove a single stash entry.", - LongDesc: `Removes a single stash entry at given index from the list of stash entries (e.g. 'dolt stash drop stash@{1}' will drop the stash entry at index 1 in the stash list). - -This command does not apply the stash on current working directory, use 'dolt stash pop' to apply a stash on current working directory.`, - Synopsis: []string{ - "{{.LessThan}}stash{{.GreaterThan}}", - }, -} - -type StashDropCmd struct{} - -// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command -func (cmd StashDropCmd) Name() string { - return "drop" -} - -// Description returns a description of the command -func (cmd StashDropCmd) Description() string { - return "Remove a single stash entry." -} - -func (cmd StashDropCmd) Docs() *cli.CommandDocumentation { - ap := cmd.ArgParser() - return cli.NewCommandDocumentation(stashDropDocs, ap) -} - -func (cmd StashDropCmd) ArgParser() *argparser.ArgParser { - ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 1) - return ap -} - -// EventType returns the type of the event to log -func (cmd StashDropCmd) EventType() eventsapi.ClientEventType { - return eventsapi.ClientEventType_STASH_DROP -} - -// Exec executes the command -func (cmd StashDropCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - if !dEnv.DoltDB(ctx).Format().UsesFlatbuffers() { - cli.PrintErrln(ErrStashNotSupportedForOldFormat.Error()) - return 1 - } - ap := cmd.ArgParser() - help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, stashDropDocs, ap)) - apr := cli.ParseArgsOrDie(ap, args, help) - - var idx = 0 - var err error - if apr.NArg() == 1 { - stashName := apr.Args[0] - stashName = strings.TrimSuffix(strings.TrimPrefix(stashName, "stash@{"), "}") - idx, err = strconv.Atoi(stashName) - if err != nil { - cli.Printf("error: %s is not a valid reference", stashName) - return 1 - } - } - - err = dropStashAtIdx(ctx, dEnv, idx) - if err != nil { - return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) - } - return 0 -} - -func dropStashAtIdx(ctx context.Context, dEnv *env.DoltEnv, idx int) error { - stashHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, doltdb.DoltCliRef) - if err != nil { - return err - } - - err = dEnv.DoltDB(ctx).RemoveStashAtIdx(ctx, idx, doltdb.DoltCliRef) - if err != nil { - return err - } - - cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String())) - return nil -} diff --git a/go/cmd/dolt/commands/stashcmds/list.go b/go/cmd/dolt/commands/stashcmds/list.go deleted file mode 100644 index ff64b573b89..00000000000 --- a/go/cmd/dolt/commands/stashcmds/list.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2023 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 stashcmds - -import ( - "context" - "fmt" - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/cmd/dolt/commands" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" - "github.com/dolthub/dolt/go/libraries/doltcore/env" - "github.com/dolthub/dolt/go/libraries/utils/argparser" -) - -var stashListDocs = cli.CommandDocumentationContent{ - ShortDesc: "List the stash entries that you currently have.", - LongDesc: `Each stash entry is listed with its name (e.g. stash@{0} is the latest entry, stash@{1} is the one before, etc.), the name of the branch that was current when the entry was made, and a short description of the commit the entry was based on. -`, - Synopsis: []string{ - "", - }, -} - -type StashListCmd struct{} - -// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command -func (cmd StashListCmd) Name() string { - return "list" -} - -// Description returns a description of the command -func (cmd StashListCmd) Description() string { - return "List the stash entries that you currently have." -} - -func (cmd StashListCmd) Docs() *cli.CommandDocumentation { - ap := cmd.ArgParser() - return cli.NewCommandDocumentation(stashListDocs, ap) -} - -func (cmd StashListCmd) ArgParser() *argparser.ArgParser { - ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0) - return ap -} - -// EventType returns the type of the event to log -func (cmd StashListCmd) EventType() eventsapi.ClientEventType { - return eventsapi.ClientEventType_STASH_LIST -} - -// Exec executes the command -func (cmd StashListCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - if !dEnv.DoltDB(ctx).Format().UsesFlatbuffers() { - cli.PrintErrln(ErrStashNotSupportedForOldFormat.Error()) - return 1 - } - ap := cmd.ArgParser() - help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, stashListDocs, ap)) - cli.ParseArgsOrDie(ap, args, help) - - err := listStashes(ctx, dEnv) - if err != nil { - return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) - } - return 0 -} - -func listStashes(ctx context.Context, dEnv *env.DoltEnv) error { - stashes, err := dEnv.DoltDB(ctx).GetCommandLineStashes(ctx) - if err != nil { - return err - } - - for _, stash := range stashes { - commitHash, err := stash.HeadCommit.HashOf() - if err != nil { - return err - } - cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) - } - return nil -} diff --git a/go/cmd/dolt/commands/stashcmds/pop.go b/go/cmd/dolt/commands/stashcmds/pop.go deleted file mode 100644 index 18e2b0caa67..00000000000 --- a/go/cmd/dolt/commands/stashcmds/pop.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2023 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 stashcmds - -import ( - "context" - "strconv" - "strings" - - "github.com/dolthub/go-mysql-server/sql" - - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/cmd/dolt/commands" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" - "github.com/dolthub/dolt/go/libraries/doltcore/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/table/editor" - "github.com/dolthub/dolt/go/libraries/utils/argparser" -) - -var stashPopDocs = cli.CommandDocumentationContent{ - ShortDesc: "Remove a single stash from the stash list and apply it on top of the current working set.", - LongDesc: `Apply a single stash at given index and drop that stash entry from the stash list (e.g. 'dolt stash pop stash@{1}' will apply and drop the stash entry at index 1 in the stash list). - -Applying the stash entry can fail with conflicts; in this case, the stash entry is not removed from the stash list. You need to resolve the conflicts by hand and call dolt stash drop manually afterwards. -`, - Synopsis: []string{ - "{{.LessThan}}stash{{.GreaterThan}}", - }, -} - -type StashPopCmd struct{} - -// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command -func (cmd StashPopCmd) Name() string { - return "pop" -} - -// Description returns a description of the command -func (cmd StashPopCmd) Description() string { - return "Remove a single stash from the stash list and apply it on top of the current working set." -} - -func (cmd StashPopCmd) Docs() *cli.CommandDocumentation { - ap := cmd.ArgParser() - return cli.NewCommandDocumentation(stashPopDocs, ap) -} - -func (cmd StashPopCmd) ArgParser() *argparser.ArgParser { - ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 1) - return ap -} - -// EventType returns the type of the event to log -func (cmd StashPopCmd) EventType() eventsapi.ClientEventType { - return eventsapi.ClientEventType_STASH_POP -} - -// Exec executes the command -func (cmd StashPopCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - if !dEnv.DoltDB(ctx).Format().UsesFlatbuffers() { - cli.PrintErrln(ErrStashNotSupportedForOldFormat.Error()) - return 1 - } - ap := cmd.ArgParser() - help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, stashPopDocs, ap)) - apr := cli.ParseArgsOrDie(ap, args, help) - - _, sqlCtx, closer, err := cliCtx.QueryEngine(ctx) - if err != nil { - cli.PrintErrln(err.Error()) - return 1 - } - defer closer() - - var idx = 0 - if apr.NArg() == 1 { - stashName := apr.Args[0] - stashName = strings.TrimSuffix(strings.TrimPrefix(stashName, "stash@{"), "}") - idx, err = strconv.Atoi(stashName) - if err != nil { - cli.Printf("error: %s is not a valid reference", stashName) - return 1 - } - } - - workingRoot, err := dEnv.WorkingRoot(sqlCtx) - if err != nil { - return handleStashPopErr(usage, err) - } - - success, err := applyStashAtIdx(sqlCtx, dEnv, workingRoot, idx) - if err != nil { - return handleStashPopErr(usage, err) - } - - ret := commands.StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) - if ret != 0 || !success { - cli.Println("The stash entry is kept in case you need it again.") - return 1 - } - - cli.Println() - err = dropStashAtIdx(sqlCtx, dEnv, idx) - if err != nil { - return handleStashPopErr(usage, err) - } - - return 0 -} - -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, doltdb.DoltCliRef) - if err != nil { - return false, err - } - - hch, err := headCommit.HashOf() - if err != nil { - return false, err - } - headCommitSpec, err := doltdb.NewCommitSpec(hch.String()) - if err != nil { - return false, err - } - headRef, err := dEnv.RepoStateReader().CWBHeadRef(ctx) - if err != nil { - return false, err - } - optCmt, err := dEnv.DoltDB(ctx).Resolve(ctx, headCommitSpec, headRef) - if err != nil { - return false, 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 false, doltdb.ErrGhostCommitEncountered - } - - parentRoot, err := parentCommit.GetRootValue(ctx) - if err != nil { - return false, err - } - - tmpDir, err := dEnv.TempTableFilesDir() - if err != nil { - return false, err - } - - opts := editor.Options{Deaf: dEnv.BulkDbEaFactory(ctx), Tempdir: tmpDir} - result, err := merge.MergeRoots(ctx, curWorkingRoot, stashRoot, parentRoot, stashRoot, parentCommit, opts, merge.MergeOpts{IsCherryPick: false}) - if err != nil { - return false, 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), "', '") - cli.Printf("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 false, nil - } - - err = dEnv.UpdateWorkingRoot(ctx, result.Root) - if err != nil { - return false, err - } - - roots, err := dEnv.Roots(ctx) - if err != nil { - return false, 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 false, err - } - - err = dEnv.UpdateRoots(ctx, roots) - if err != nil { - return false, err - } - - return true, nil -} - -func handleStashPopErr(usage cli.UsagePrinter, err error) int { - cli.Println("The stash entry is kept in case you need it again.") - return commands.HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage) -} diff --git a/go/cmd/dolt/commands/stashcmds/stash.go b/go/cmd/dolt/commands/stashcmds/stash.go deleted file mode 100644 index b57548529c1..00000000000 --- a/go/cmd/dolt/commands/stashcmds/stash.go +++ /dev/null @@ -1,562 +0,0 @@ -// Copyright 2023 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 stashcmds - -import ( - "bytes" - "context" - "errors" - "fmt" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - "github.com/dolthub/dolt/go/libraries/doltcore/ref" - "github.com/dolthub/go-mysql-server/sql" - "strconv" - "strings" - - "github.com/dolthub/dolt/go/cmd/dolt/cli" - "github.com/dolthub/dolt/go/cmd/dolt/commands" - eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" - "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/utils/argparser" - "github.com/dolthub/dolt/go/store/datas" -) - -var ErrStashNotSupportedForOldFormat = errors.New("stash is not supported for old storage format") - -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. - -The command saves your local modifications away and reverts the working directory to match the HEAD commit. The stash entries that are saved away can be listed with 'dolt stash list'. -`, - Synopsis: []string{ - "", // this is for `dolt stash` itself. - "list", - "pop {{.LessThan}}stash{{.GreaterThan}}", - "clear", - "drop {{.LessThan}}stash{{.GreaterThan}}", - }, -} - -type StashCmd struct{} - -// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command -func (cmd StashCmd) Name() string { - return "stash" -} - -// Description returns a description of the command -func (cmd StashCmd) Description() string { - return "Stash the changes in a dirty working directory away." -} - -func (cmd StashCmd) Docs() *cli.CommandDocumentation { - ap := cmd.ArgParser() - return cli.NewCommandDocumentation(stashDocs, ap) -} - -func (cmd StashCmd) ArgParser() *argparser.ArgParser { - ap := argparser.NewArgParserWithMaxArgs(cmd.Name(), 0) - ap.SupportsFlag(cli.IncludeUntrackedFlag, "u", "Untracked tables are also stashed.") - ap.SupportsFlag(cli.AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") - return ap -} - -// EventType returns the type of the event to log -func (cmd StashCmd) EventType() eventsapi.ClientEventType { - return eventsapi.ClientEventType_STASH -} - -// Exec executes the command -func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int { - ap := cli.CreateStashArgParser() - apr, _, terminate, status := commands.ParseArgsOrPrintHelp(ap, commandStr, args, stashDocs) - if terminate { - return status - } - if len(apr.Args) > 2 { - cli.PrintErrln(fmt.Errorf("dolt stash takes 2 arguments, received %d", len(apr.Args))) - return 1 - } - - subcommand := "push" - if len(apr.Args) > 0 { - subcommand = strings.ToLower(apr.Arg(0)) - } - - var err error - switch subcommand { - case "push": - err = stashPush(ctx, cliCtx, dEnv, apr, subcommand) - case "pop", "drop": - err = stashRemove(ctx, cliCtx, dEnv, apr, subcommand) - case "list": - err = stashList(ctx, cliCtx, dEnv) - case "clear": - err = stashClear(ctx, cliCtx, apr, subcommand) - } - - if err != nil { - cli.PrintErrln(errhand.VerboseErrorFromError(err)) - return 1 - } - - return 0 -} - -func stashPush(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, apr *argparser.ArgParseResults, subcommand string) error { - roots, err := dEnv.Roots(ctx) - if err != nil { - return err - } - - hasChanges, err := hasLocalChanges(ctx, dEnv, roots, apr) - if err != nil { - return err - } - if !hasChanges { - cli.Println("No local changes to save") - return nil - } - curBranchName, commit, commitMeta, err := getStashPushData(ctx, dEnv) - if err != nil { - return err - } - - commitHash, err := commit.HashOf() - if err != nil { - return err - } - - rowIter, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) - if err != nil { - return err - } - if closeFunc != nil { - defer closeFunc() - } - - cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", curBranchName, commitHash.String(), commitMeta.Description)) - _, err = sql.RowIterToRows(sqlCtx, rowIter) - return err -} - -func stashRemove(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv, apr *argparser.ArgParseResults, subcommand string) error { - idx, err := parseStashIndex(apr) - if err != nil { - return err - } - commitHash, err := dEnv.DoltDB(ctx).GetStashHashAtIdx(ctx, idx, doltdb.DoltCliRef) - if err != nil { - return err - } - - rowIter, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) - if err != nil { - return err - } - if closeFunc != nil { - defer closeFunc() - } - - if subcommand == "pop" { - ret := commands.StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) - if ret != 0 { - cli.Println("The stash entry is kept in case you need it again.") - return err - } - } - - cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, commitHash.String())) - _, err = sql.RowIterToRows(sqlCtx, rowIter) - return err -} - -func stashList(ctx context.Context, cliCtx cli.CliContext, dEnv *env.DoltEnv) error { - queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) - if err != nil { - return err - } - if closeFunc != nil { - defer closeFunc() - } - - stashes, err := getStashesSQL(ctx, sqlCtx, queryist, dEnv) - for _, stash := range stashes { - commitHash, err := stash.HeadCommit.HashOf() - if err != nil { - return err - } - cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) - } - - return nil -} - -func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { - rowIter, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) - if err != nil { - return err - } - if closeFunc != nil { - defer closeFunc() - } - _, err = sql.RowIterToRows(sqlCtx, rowIter) - return err -} - -func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, dEnv *env.DoltEnv) ([]*doltdb.Stash, error) { - qry := fmt.Sprintf("select stash_id, branch, hash, commit_message from dolt_stashes where name = '%s'", doltdb.DoltCliRef) - rows, err := commands.GetRowsForSql(queryist, sqlCtx, qry) - if err != nil { - return nil, err - } - - var stashes []*doltdb.Stash - for _, s := range rows { - id, ok := s[0].(string) - if !ok { - return nil, fmt.Errorf("invalid stash id") - } - - branch, ok := s[1].(string) - if !ok { - return nil, fmt.Errorf("invalid stash branch") - } - fullBranch := ref.NewBranchRef(branch).String() - - stashHash, ok := s[2].(string) - if !ok { - return nil, fmt.Errorf("invalid stash hash") - } - maybeCommit, err := actions.MaybeGetCommit(ctx, dEnv, stashHash) - if err != nil { - return nil, err - } - - msg, ok := s[3].(string) - if !ok { - return nil, fmt.Errorf("invalid stash message") - } - stashes = append(stashes, &doltdb.Stash{ - Name: id, - BranchReference: fullBranch, - HeadCommit: maybeCommit, - Description: msg, - }) - } - - return stashes, nil -} - -// generateStashSql returns the query that will call the `DOLT_STASH` stored proceudre. -func generateStashSql(apr *argparser.ArgParseResults, subcommand string) string { - var buffer bytes.Buffer - first := true - buffer.WriteString("CALL DOLT_STASH(") - - write := func(s string) { - if !first { - buffer.WriteString(", ") - } - buffer.WriteString("'") - buffer.WriteString(s) - buffer.WriteString("'") - first = false - } - - write(subcommand) - write(doltdb.DoltCliRef) - - if len(apr.Args) == 2 { - // Add stash identifier (i.e. "stash@{0}") - write(apr.Arg(1)) - } - - if apr.Contains(cli.AllFlag) { - write("-a") - } - if apr.Contains(cli.IncludeUntrackedFlag) { - write("-u") - } - - buffer.WriteString(")") - return buffer.String() -} - -func hasLocalChanges(ctx context.Context, dEnv *env.DoltEnv, roots doltdb.Roots, apr *argparser.ArgParseResults) (bool, error) { - headRoot, err := dEnv.HeadRoot(ctx) - if err != nil { - return false, err - } - workingRoot, err := dEnv.WorkingRoot(ctx) - if err != nil { - return false, err - } - stagedRoot, err := dEnv.StagedRoot(ctx) - if err != nil { - return false, err - } - 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(cli.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 -} - -// getStashPushData is a helper function that returns the current branch, the commit itself, and the commit meta for the most recent commit -func getStashPushData(ctx context.Context, dEnv *env.DoltEnv) (string, *doltdb.Commit, *datas.CommitMeta, error) { - curHeadRef, err := dEnv.RepoStateReader().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 := dEnv.DoltDB(ctx).Resolve(ctx, commitSpec, curHeadRef) - if err != nil { - return "", nil, nil, err - } - commit, ok := optCmt.ToCommit() - if !ok { - return "", nil, nil, err - } - - commitMeta, err := commit.GetCommitMeta(ctx) - if err != nil { - return "", nil, nil, err - } - - return curBranchName, commit, commitMeta, nil -} - -func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, *sql.Context, func(), error) { - queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) - if err != nil { - return nil, nil, nil, err - } - - _, rowIter, _, err := queryist.Query(sqlCtx, generateStashSql(apr, subcommand)) - if err != nil { - return nil, nil, nil, err - } - - return rowIter, sqlCtx, closeFunc, nil -} - -func stashChanges(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgParseResults) error { - roots, err := dEnv.Roots(ctx) - if err != nil { - return fmt.Errorf("couldn't get working root, cause: %s", err.Error()) - } - - hasChanges, err := hasLocalChanges(ctx, dEnv, roots, apr) - if err != nil { - return err - } - if !hasChanges { - cli.Println("No local changes to save") - return nil - } - - 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 - } - - // 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(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(cli.AllFlag)) - if err != nil { - return err - } - } - - curHeadRef, err := dEnv.RepoStateReader().CWBHeadRef(ctx) - if err != nil { - return err - } - curBranchName := curHeadRef.String() - commitSpec, err := doltdb.NewCommitSpec(curBranchName) - if err != nil { - return err - } - optCmt, err := dEnv.DoltDB(ctx).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 = dEnv.DoltDB(ctx).AddStash(ctx, commit, roots.Staged, datas.NewStashMeta(curBranchName, commitMeta.Description, doltdb.FlattenTableNames(addedTblsToStage)), doltdb.DoltCliRef) - if err != nil { - return err - } - - // setting STAGED to current HEAD RootValue resets staged set of changed, so - // these changes are now in working set of changes, which needs to be checked out - roots.Staged = roots.Head - roots, err = actions.MoveTablesFromHeadToWorking(ctx, roots, allTblsToBeStashed) - if err != nil { - return err - } - - err = dEnv.UpdateRoots(ctx, roots) - if err != nil { - return err - } - - commitHash, err := commit.HashOf() - if err != nil { - return err - } - cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", curBranchName, commitHash.String(), commitMeta.Description)) - return nil -} - -// workingSetContainsOnlyUntrackedTables returns true if all changes in working set are untracked files/added tables. -// Untracked files are part of working set changes, but should not be stashed unless staged or --include-untracked flag is used. -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 -} - -// stashedTableSets returns array of table names for all tables that are being stashed and added tables in staged. -// These table names are determined from all tables in the staged set of changes as they are being stashed only. -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 parseStashIndex(apr *argparser.ArgParseResults) (int, error) { - idx := 0 - - if apr.NArg() > 1 { - stashID := apr.Arg(1) - 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 -} diff --git a/go/cmd/dolt/doltcmd/doltcmd.go b/go/cmd/dolt/doltcmd/doltcmd.go index a00f0fcfa33..1bf9808df6c 100644 --- a/go/cmd/dolt/doltcmd/doltcmd.go +++ b/go/cmd/dolt/doltcmd/doltcmd.go @@ -26,7 +26,6 @@ import ( "github.com/dolthub/dolt/go/cmd/dolt/commands/indexcmds" "github.com/dolthub/dolt/go/cmd/dolt/commands/schcmds" "github.com/dolthub/dolt/go/cmd/dolt/commands/sqlserver" - "github.com/dolthub/dolt/go/cmd/dolt/commands/stashcmds" "github.com/dolthub/dolt/go/cmd/dolt/commands/tblcmds" "github.com/dolthub/dolt/go/cmd/dolt/doltversion" ) @@ -83,7 +82,7 @@ var doltSubCommands = []cli.Command{ dumpDocsCommand, dumpZshCommand, docscmds.Commands, - stashcmds.StashCmd{}, + commands.StashCmd{}, &commands.Assist{}, commands.ProfileCmd{}, commands.QueryDiff{}, diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 59833eadcf8..dff8d2cfc55 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -197,6 +197,8 @@ func doStashClear(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName s return dbData.Ddb.RemoveAllStashes(ctx, stashName) } +// stashedTableSets returns array of table names for all tables that are being stashed and added tables in staged. +// These table names are determined from all tables in the staged set of changes as they are being stashed only. func stashedTableSets(ctx context.Context, roots doltdb.Roots) ([]doltdb.TableName, []doltdb.TableName, error) { var addedTblsInStaged []doltdb.TableName var allTbls []doltdb.TableName @@ -288,6 +290,8 @@ func hasLocalChanges(ctx *sql.Context, dSess *dsess.DoltSession, roots doltdb.Ro return true, nil } +// workingSetContainsOnlyUntrackedTables returns true if all changes in working set are untracked files/added tables. +// Untracked files are part of working set changes, but should not be stashed unless staged or --include-untracked flag is used. func workingSetContainsOnlyUntrackedTables(ctx context.Context, roots doltdb.Roots) (bool, error) { _, unstaged, err := diff.GetStagedUnstagedTableDeltas(ctx, roots) if err != nil { diff --git a/integration-tests/bats/stash.bats b/integration-tests/bats/stash.bats index b87cd788394..b08f9693c45 100644 --- a/integration-tests/bats/stash.bats +++ b/integration-tests/bats/stash.bats @@ -16,8 +16,8 @@ teardown() { @test "stash: stashing on clean working set" { run dolt stash - [ "$status" -eq 0 ] - [[ "$output" =~ "No local changes to save" ]] || false + [ "$status" -eq 1 ] + [[ "$output" =~ "no local changes to save" ]] || false } @test "stash: simple stashing and popping stash" { @@ -369,8 +369,8 @@ teardown() { [[ "$output" =~ "Untracked tables:" ]] || false run dolt stash - [ "$status" -eq 0 ] - [[ "$output" =~ "No local changes to save" ]] || false + [ "$status" -eq 1 ] + [[ "$output" =~ "no local changes to save" ]] || false run dolt ls [ "$status" -eq 0 ] From 4cd3a04fc71176775e7f91d9ba042bd600111ce2 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Fri, 20 Jun 2025 16:57:57 -0700 Subject: [PATCH 07/11] Small fixes --- go/cmd/dolt/commands/stash.go | 85 +++---------------- go/libraries/doltcore/doltdb/stash.go | 8 +- .../doltcore/sqle/dtables/stashes_table.go | 6 +- integration-tests/bats/stash.bats | 24 ++++++ 4 files changed, 45 insertions(+), 78 deletions(-) diff --git a/go/cmd/dolt/commands/stash.go b/go/cmd/dolt/commands/stash.go index 43b67a7b208..0224e74dbd3 100644 --- a/go/cmd/dolt/commands/stash.go +++ b/go/cmd/dolt/commands/stash.go @@ -20,8 +20,6 @@ import ( "fmt" "github.com/dolthub/dolt/go/cmd/dolt/errhand" "github.com/dolthub/dolt/go/libraries/doltcore/ref" - "github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess" - "github.com/dolthub/dolt/go/store/hash" "github.com/dolthub/go-mysql-server/sql" "github.com/gocraft/dbr/v2" "github.com/gocraft/dbr/v2/dialect" @@ -68,10 +66,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(cli.AllFlag, "a", "All tables are stashed, including untracked and ignored tables.") - return ap + return cli.CreateStashArgParser() } // EventType returns the type of the event to log @@ -132,13 +127,7 @@ func stashPush(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPar return err } stash := stashes[0] - stashHash, err := stash.HeadCommit.HashOf() - if err != nil { - return err - } - hashStr := stashHash.String() - - cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", stash.BranchReference, hashStr, stash.Description)) + cli.Println(fmt.Sprintf("Saved working directory and index state WIP on %s: %s %s", stash.BranchReference, stash.CommitHash, stash.Description)) _, err = sql.RowIterToRows(sqlCtx, rowIter) return err } @@ -167,11 +156,6 @@ func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgP if len(stashes)-1 < idx { return fmt.Errorf("stash index stash@{%d} does not exist", idx) } - commit := stashes[idx].HeadCommit - stashHash, err := commit.HashOf() - if err != nil { - return err - } qry, params := generateStashSql(apr, subcommand) interpolatedQuery, err := dbr.InterpolateForDialect(qry, params, dialect.MySQL) @@ -184,15 +168,14 @@ func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgP } if subcommand == "pop" { - var dEnv *env.DoltEnv - ret := StatusCmd{}.Exec(sqlCtx, "status", []string{}, dEnv, cliCtx) + ret := StatusCmd{}.Exec(sqlCtx, "status", []string{}, nil, cliCtx) if ret != 0 { cli.Println("The stash entry is kept in case you need it again.") return err } } - cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashHash.String())) + cli.Println(fmt.Sprintf("Dropped refs/stash@{%v} (%s)", idx, stashes[idx].CommitHash)) _, err = sql.RowIterToRows(sqlCtx, rowIter) return err } @@ -207,12 +190,11 @@ func stashList(ctx context.Context, cliCtx cli.CliContext) error { } stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 0) + if err != nil { + return err + } for _, stash := range stashes { - commitHash, err := stash.HeadCommit.HashOf() - if err != nil { - return err - } - cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, commitHash.String(), stash.Description)) + cli.Println(fmt.Sprintf("%s: WIP on %s: %s %s", stash.Name, stash.BranchReference, stash.CommitHash, stash.Description)) } return nil @@ -232,12 +214,6 @@ func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPa // getStashesSQL queries the dolt_stashes system table to return the requested number of stashes. A limit of 0 will get all stashes func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, limit int) ([]*doltdb.Stash, error) { - sess := dsess.DSessFromSess(sqlCtx.Session) - dbName := sqlCtx.GetCurrentDatabase() - doltDb, ok := sess.GetDbData(sqlCtx, dbName) - if !ok { - return nil, fmt.Errorf("No doltdb found for %s", dbName) - } limitStr := fmt.Sprintf("limit %d", limit) if limit == 0 { limitStr = "" @@ -266,16 +242,6 @@ func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryi if !ok { return nil, fmt.Errorf("invalid stash hash") } - fullHash, ok := hash.MaybeParse(stashHash) - if !ok { - return nil, fmt.Errorf("invalid stash hash") - } - - var commit *doltdb.Commit - optCommit, err := doltDb.Ddb.ReadCommit(ctx, fullHash) - if err == nil { - commit, ok = optCommit.ToCommit() - } msg, ok := s[3].(string) if !ok { @@ -286,54 +252,31 @@ func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryi Name: id, BranchReference: fullBranch, Description: msg, - HeadCommit: commit, + CommitHash: stashHash, }) } return stashes, nil } -// generateStashSql returns the query that will call the `DOLT_STASH` stored proceudre. +// generateStashSql returns the query that will call the `DOLT_STASH` stored procedure. func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string, []interface{}) { var buffer bytes.Buffer var params []interface{} - var param bool - first := true - buffer.WriteString("CALL DOLT_STASH(") - - write := func(s string) { - if !first { - buffer.WriteString(", ") - } - if !param { - buffer.WriteString("'") - } - buffer.WriteString(s) - if !param { - buffer.WriteString("'") - } - first = false - param = false - } - - param = true - write("?") - param = true - write("?") + buffer.WriteString("CALL DOLT_STASH(?, ?") params = append(params, subcommand) params = append(params, doltdb.DoltCliRef) if len(apr.Args) == 2 { - param = true params = append(params, apr.Arg(1)) - write("?") + buffer.WriteString(", ?") } if apr.Contains(cli.AllFlag) { - write("-a") + buffer.WriteString(", '-a'") } if apr.Contains(cli.IncludeUntrackedFlag) { - write("-u") + buffer.WriteString(", '-u'") } buffer.WriteString(")") diff --git a/go/libraries/doltcore/doltdb/stash.go b/go/libraries/doltcore/doltdb/stash.go index a5be6c3b0d7..2c4a58466c0 100644 --- a/go/libraries/doltcore/doltdb/stash.go +++ b/go/libraries/doltcore/doltdb/stash.go @@ -29,7 +29,7 @@ type Stash struct { Name string BranchReference string Description string - HeadCommit *Commit + CommitHash string StashReference string } @@ -76,8 +76,12 @@ func getStashList(ctx context.Context, ds datas.Dataset, vrw types.ValueReadWrit if err != nil { return nil, err } + headCommitHash, err := headCommit.HashOf() + if err != nil { + return nil, err + } - s.HeadCommit = headCommit + s.CommitHash = headCommitHash.String() 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 59fcd01fdd0..c7175854c10 100644 --- a/go/libraries/doltcore/sqle/dtables/stashes_table.go +++ b/go/libraries/doltcore/sqle/dtables/stashes_table.go @@ -144,17 +144,13 @@ func (itr *StashItr) Next(*sql.Context) (sql.Row, error) { }() stash := itr.stashes[itr.idx] - commitHash, err := stash.HeadCommit.HashOf() - if err != nil { - return nil, err - } // 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.BranchReference).GetPath() stashRef := ref.NewStashRef(stash.StashReference).GetPath() - return sql.NewRow(stashRef, stash.Name, branch, commitHash.String(), stash.Description), nil + return sql.NewRow(stashRef, stash.Name, branch, stash.CommitHash, stash.Description), nil } // Close closes the iterator. diff --git a/integration-tests/bats/stash.bats b/integration-tests/bats/stash.bats index b08f9693c45..55514838c8b 100644 --- a/integration-tests/bats/stash.bats +++ b/integration-tests/bats/stash.bats @@ -159,6 +159,9 @@ teardown() { } @test "stash: popping neither latest nor oldest stash" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt sql -q "INSERT INTO test VALUES (1, 'a')" run dolt stash [ "$status" -eq 0 ] @@ -211,6 +214,9 @@ teardown() { } @test "stash: stashing multiple entries on different branches" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt sql -q "INSERT INTO test VALUES (1, 'a')" run dolt stash [ "$status" -eq 0 ] @@ -227,6 +233,9 @@ teardown() { } @test "stash: popping stash on different branch" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt sql -q "INSERT INTO test VALUES (1, 'a')" run dolt stash [ "$status" -eq 0 ] @@ -538,6 +547,9 @@ teardown() { } @test "stash: popping stash with deleted table that is deleted already on current head" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt branch branch1 dolt checkout -b branch2 dolt sql -q "DROP TABLE test;" @@ -566,6 +578,9 @@ teardown() { } @test "stash: popping stash with deleted table that the same table exists on current head" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt branch branch1 dolt branch branch2 @@ -598,6 +613,9 @@ teardown() { } @test "stash: popping stash with deleted table that different table with same name on current head gives conflict" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt branch branch1 dolt branch branch2 @@ -627,6 +645,9 @@ teardown() { } @test "stash: popping stash with added table with PK on current head with the exact same table is added already" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt branch branch1 dolt checkout -b branch2 dolt sql -q "CREATE TABLE new_test(id INT PRIMARY KEY); INSERT INTO new_test VALUES (1);" @@ -656,6 +677,9 @@ teardown() { } @test "stash: popping stash with added keyless table on current head with the exact same table is added already" { + if [ "$SQL_ENGINE" = "remote-engine" ]; then + skip "needs checkout which is unsupported for remote-engine" + fi dolt branch branch1 dolt checkout -b branch2 dolt sql -q "CREATE TABLE new_test(id INT); INSERT INTO new_test VALUES (1);" From 63a24513fceb6c22892187f0acb709fdbe9f4fed Mon Sep 17 00:00:00 2001 From: NathanGabrielson Date: Sat, 21 Jun 2025 00:07:32 +0000 Subject: [PATCH 08/11] [ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/update.sh --- go/cmd/dolt/commands/stash.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/go/cmd/dolt/commands/stash.go b/go/cmd/dolt/commands/stash.go index 0224e74dbd3..14889b8c117 100644 --- a/go/cmd/dolt/commands/stash.go +++ b/go/cmd/dolt/commands/stash.go @@ -18,18 +18,19 @@ import ( "bytes" "context" "fmt" - "github.com/dolthub/dolt/go/cmd/dolt/errhand" - "github.com/dolthub/dolt/go/libraries/doltcore/ref" + "strconv" + "strings" + "github.com/dolthub/go-mysql-server/sql" "github.com/gocraft/dbr/v2" "github.com/gocraft/dbr/v2/dialect" - "strconv" - "strings" "github.com/dolthub/dolt/go/cmd/dolt/cli" + "github.com/dolthub/dolt/go/cmd/dolt/errhand" eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/libraries/doltcore/env" + "github.com/dolthub/dolt/go/libraries/doltcore/ref" "github.com/dolthub/dolt/go/libraries/utils/argparser" ) From 6b9a55cc317e88ab51382812fe5f19f323d2d156 Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Mon, 23 Jun 2025 09:07:47 -0700 Subject: [PATCH 09/11] Fixing tests & code cleanup --- go/cmd/dolt/commands/stash.go | 19 +++++++++---------- .../doltcore/sqle/dprocedures/dolt_stash.go | 2 +- .../sqle/enginetest/dolt_queries_help.go | 1 + .../sqle/enginetest/dolt_queries_stash.go | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go/cmd/dolt/commands/stash.go b/go/cmd/dolt/commands/stash.go index 14889b8c117..dbf9ede2077 100644 --- a/go/cmd/dolt/commands/stash.go +++ b/go/cmd/dolt/commands/stash.go @@ -123,7 +123,7 @@ func stashPush(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPar defer closeFunc() } - stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 1) + stashes, err := getStashesSQL(sqlCtx, queryist, 1) if err != nil { return err } @@ -147,7 +147,7 @@ func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgP defer closeFunc() } - stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 0) + stashes, err := getStashesSQL(sqlCtx, queryist, 0) if err != nil { return err } @@ -158,8 +158,7 @@ func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgP return fmt.Errorf("stash index stash@{%d} does not exist", idx) } - qry, params := generateStashSql(apr, subcommand) - interpolatedQuery, err := dbr.InterpolateForDialect(qry, params, dialect.MySQL) + interpolatedQuery, err := generateStashSql(apr, subcommand) if err != nil { return err } @@ -190,7 +189,7 @@ func stashList(ctx context.Context, cliCtx cli.CliContext) error { defer closeFunc() } - stashes, err := getStashesSQL(ctx, sqlCtx, queryist, 0) + stashes, err := getStashesSQL(sqlCtx, queryist, 0) if err != nil { return err } @@ -214,7 +213,7 @@ func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPa } // getStashesSQL queries the dolt_stashes system table to return the requested number of stashes. A limit of 0 will get all stashes -func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryist, limit int) ([]*doltdb.Stash, error) { +func getStashesSQL(sqlCtx *sql.Context, queryist cli.Queryist, limit int) ([]*doltdb.Stash, error) { limitStr := fmt.Sprintf("limit %d", limit) if limit == 0 { limitStr = "" @@ -261,7 +260,7 @@ func getStashesSQL(ctx context.Context, sqlCtx *sql.Context, queryist cli.Queryi } // generateStashSql returns the query that will call the `DOLT_STASH` stored procedure. -func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string, []interface{}) { +func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string, error) { var buffer bytes.Buffer var params []interface{} buffer.WriteString("CALL DOLT_STASH(?, ?") @@ -281,7 +280,8 @@ func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string } buffer.WriteString(")") - return buffer.String(), params + interpolatedQuery, err := dbr.InterpolateForDialect(buffer.String(), params, dialect.MySQL) + return interpolatedQuery, err } func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, cli.Queryist, *sql.Context, func(), error) { @@ -290,8 +290,7 @@ func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPa return nil, nil, nil, nil, err } - qry, params := generateStashSql(apr, subcommand) - interpolatedQuery, err := dbr.InterpolateForDialect(qry, params, dialect.MySQL) + interpolatedQuery, err := generateStashSql(apr, subcommand) if err != nil { return nil, nil, nil, nil, err } diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index dff8d2cfc55..4d1ed02d3e4 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -504,7 +504,7 @@ func handleMerge(ctx *sql.Context, dbData env.DbData[*sql.Context], stashName st 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\"The stash entry is kept in case you need it again.\n", idx, tblNames) + "Please commit your changes or stash them before you merge.\nAborting\nThe stash entry is kept in case you need it again.\n", idx, tblNames) return nil, nil, nil, status } diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_help.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_help.go index 6862ec1a5dc..1030b7a68dc 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_help.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_help.go @@ -57,6 +57,7 @@ var DoltHelpScripts = []queries.ScriptTest{ {"dolt_backup"}, {"dolt_tag"}, {"dolt_gc"}, + {"dolt_stash"}, {"dolt_rebase"}, }, }, diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go index cb984efdc32..3d0da8c7928 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go @@ -292,7 +292,7 @@ var DoltStashTests = []queries.ScriptTest{ { 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", + "Please commit your changes or stash them before you merge.\nAborting\nThe stash entry is kept in case you need it again.\n", }, { Query: "SELECT * FROM DOLT_STASHES;", From 1f08c69c90c787682f43ee1f7ff0a3d3501a827b Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Mon, 23 Jun 2025 10:50:27 -0700 Subject: [PATCH 10/11] stop skipping tests --- go/cmd/dolt/commands/stash.go | 3 + .../doltcore/sqle/dprocedures/dolt_stash.go | 2 +- integration-tests/bats/stash.bats | 161 +++++++----------- 3 files changed, 68 insertions(+), 98 deletions(-) diff --git a/go/cmd/dolt/commands/stash.go b/go/cmd/dolt/commands/stash.go index dbf9ede2077..884c9377835 100644 --- a/go/cmd/dolt/commands/stash.go +++ b/go/cmd/dolt/commands/stash.go @@ -109,6 +109,9 @@ func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, if err != nil { cli.PrintErrln(errhand.VerboseErrorFromError(err)) + if err.Error() == "No local changes to save" { + return 0 + } return 1 } return 0 diff --git a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go index 4d1ed02d3e4..a72ff6310c9 100644 --- a/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go +++ b/go/libraries/doltcore/sqle/dprocedures/dolt_stash.go @@ -113,7 +113,7 @@ func doStashPush(ctx *sql.Context, dSess *dsess.DoltSession, dbData env.DbData[* 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) diff --git a/integration-tests/bats/stash.bats b/integration-tests/bats/stash.bats index 55514838c8b..eaa016eb6ab 100644 --- a/integration-tests/bats/stash.bats +++ b/integration-tests/bats/stash.bats @@ -16,8 +16,8 @@ teardown() { @test "stash: stashing on clean working set" { run dolt stash - [ "$status" -eq 1 ] - [[ "$output" =~ "no local changes to save" ]] || false + [ "$status" -eq 0 ] + [[ "$output" =~ "No local changes to save" ]] || false } @test "stash: simple stashing and popping stash" { @@ -159,9 +159,6 @@ teardown() { } @test "stash: popping neither latest nor oldest stash" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi dolt sql -q "INSERT INTO test VALUES (1, 'a')" run dolt stash [ "$status" -eq 0 ] @@ -199,31 +196,28 @@ teardown() { [ "$status" -eq 0 ] [[ "$output" =~ "3,c" ]] || false - dolt checkout test - run dolt stash list + dolt branch test + run dolt --branch test stash list [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 2 ] - run dolt stash pop stash@{1} + run dolt --branch test stash pop stash@{1} [ "$status" -eq 0 ] [[ "$output" =~ "Dropped refs/stash@{1}" ]] || false - run dolt sql -q "SELECT * FROM test" -r csv + run dolt --branch test sql -q "SELECT * FROM test" -r csv [ "$status" -eq 0 ] [[ "$output" =~ "1,a" ]] || false } @test "stash: stashing multiple entries on different branches" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi dolt sql -q "INSERT INTO test VALUES (1, 'a')" run dolt stash [ "$status" -eq 0 ] - dolt checkout -b newbranch - dolt sql -q "INSERT INTO test VALUES (1, 'b')" - run dolt stash + dolt branch newbranch + dolt --branch newbranch sql -q "INSERT INTO test VALUES (1, 'b')" + run dolt --branch newbranch stash [ "$status" -eq 0 ] run dolt stash list @@ -233,9 +227,6 @@ teardown() { } @test "stash: popping stash on different branch" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi dolt sql -q "INSERT INTO test VALUES (1, 'a')" run dolt stash [ "$status" -eq 0 ] @@ -244,11 +235,11 @@ teardown() { [ "$status" -eq 0 ] [[ "$output" =~ "stash@{0}: WIP on refs/heads/main:" ]] || false - dolt checkout -b newbranch - run dolt stash pop + dolt branch newbranch + run dolt --branch newbranch stash pop [ "$status" -eq 0 ] - run dolt sql -q "SELECT * FROM test" -r csv + run dolt --branch newbranch sql -q "SELECT * FROM test" -r csv [[ "$output" =~ "1,a" ]] || false } @@ -378,8 +369,8 @@ teardown() { [[ "$output" =~ "Untracked tables:" ]] || false run dolt stash - [ "$status" -eq 1 ] - [[ "$output" =~ "no local changes to save" ]] || false + [ "$status" -eq 0 ] + [[ "$output" =~ "No local changes to save" ]] || false run dolt ls [ "$status" -eq 0 ] @@ -547,65 +538,55 @@ teardown() { } @test "stash: popping stash with deleted table that is deleted already on current head" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi - dolt branch branch1 - dolt checkout -b branch2 - dolt sql -q "DROP TABLE test;" - dolt commit -am "table 'test' is dropped" - - dolt checkout branch1 - run dolt ls + dolt branch br1 + dolt branch br2 + dolt --branch br2 sql -q "DROP TABLE test;" + dolt --branch br2 commit -am "table 'test' is dropped" + + run dolt --branch br1 ls [ "$status" -eq 0 ] [[ "$output" =~ "test" ]] || false - dolt sql -q "DROP TABLE test;" - run dolt stash + dolt --branch br1 sql -q "DROP TABLE test;" + run dolt --branch br1 stash [ "$status" -eq 0 ] [[ "$output" =~ "Saved working directory and index state" ]] || false - dolt checkout branch2 - run dolt stash list + run dolt --branch br2 stash list [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 1 ] [[ "$output" =~ "stash@{0}" ]] || false - run dolt stash pop + run dolt --branch br2 stash pop [ "$status" -eq 0 ] [[ "$output" =~ "nothing to commit, working tree clean" ]] || false [[ "$output" =~ "Dropped refs/stash@{0}" ]] || false } @test "stash: popping stash with deleted table that the same table exists on current head" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi - dolt branch branch1 - dolt branch branch2 + dolt branch br1 + dolt branch br2 - dolt checkout branch1 - run dolt ls + run dolt --branch br1 ls [ "$status" -eq 0 ] [[ "$output" =~ "test" ]] || false - dolt sql -q "DROP TABLE test;" - run dolt stash + dolt --branch br1 sql -q "DROP TABLE test;" + run dolt --branch br1 stash [ "$status" -eq 0 ] [[ "$output" =~ "Saved working directory and index state" ]] || false - dolt checkout branch2 - run dolt ls + run dolt --branch br2 ls [ "$status" -eq 0 ] [[ "$output" =~ "test" ]] || false - run dolt stash list + run dolt --branch br2 stash list [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 1 ] [[ "$output" =~ "stash@{0}" ]] || false # if the table is the same, it's dropped - run dolt stash pop + run dolt --branch br2 stash pop [ "$status" -eq 0 ] [[ "$output" =~ "Changes not staged for commit:" ]] || false [[ "$output" =~ "deleted: test" ]] || false @@ -613,97 +594,83 @@ teardown() { } @test "stash: popping stash with deleted table that different table with same name on current head gives conflict" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi - dolt branch branch1 - dolt branch branch2 + dolt branch br1 + dolt branch br2 - dolt checkout branch1 - run dolt ls + run dolt --branch br1 ls [ "$status" -eq 0 ] [[ "$output" =~ "test" ]] || false - dolt sql -q "DROP TABLE test;" - run dolt stash + dolt --branch br1 sql -q "DROP TABLE test;" + run dolt --branch br1 stash [ "$status" -eq 0 ] [[ "$output" =~ "Saved working directory and index state" ]] || false - dolt checkout branch2 - dolt sql -q "DROP TABLE test;" - dolt sql -q "CREATE TABLE test (id BIGINT PRIMARY KEY);" + dolt --branch br2 sql -q "DROP TABLE test;" + dolt --branch br2 sql -q "CREATE TABLE test (id BIGINT PRIMARY KEY);" - run dolt stash list + run dolt --branch br2 stash list [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 1 ] [[ "$output" =~ "stash@{0}" ]] || false # if the table is different with the same name, it gives conflict - run dolt stash pop + run dolt --branch br2 stash pop [ "$status" -eq 1 ] [[ "$output" =~ "table was modified in one branch and deleted in the other" ]] || false } @test "stash: popping stash with added table with PK on current head with the exact same table is added already" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi - dolt branch branch1 - dolt checkout -b branch2 - dolt sql -q "CREATE TABLE new_test(id INT PRIMARY KEY); INSERT INTO new_test VALUES (1);" - dolt commit -Am "new table 'new_test' is created" - - dolt checkout branch1 - run dolt ls + dolt branch br1 + dolt branch br2 + + dolt --branch br2 sql -q "CREATE TABLE new_test(id INT PRIMARY KEY); INSERT INTO new_test VALUES (1);" + dolt --branch br2 commit -Am "new table 'new_test' is created" + + run dolt --branch br1 ls [ "$status" -eq 0 ] [[ "$output" =~ "test" ]] || false - dolt sql -q "CREATE TABLE new_test(id INT PRIMARY KEY); INSERT INTO new_test VALUES (1);" - dolt add . - run dolt stash + dolt --branch br1 sql -q "CREATE TABLE new_test(id INT PRIMARY KEY); INSERT INTO new_test VALUES (1);" + dolt --branch br1 add . + run dolt --branch br1 stash [ "$status" -eq 0 ] [[ "$output" =~ "Saved working directory and index state" ]] || false - dolt checkout branch2 - run dolt stash list + run dolt --branch br2 stash list [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 1 ] [[ "$output" =~ "stash@{0}" ]] || false - run dolt stash pop + run dolt --branch br2 stash pop [ "$status" -eq 0 ] [[ "$output" =~ "nothing to commit, working tree clean" ]] || false [[ "$output" =~ "Dropped refs/stash@{0}" ]] || false } @test "stash: popping stash with added keyless table on current head with the exact same table is added already" { - if [ "$SQL_ENGINE" = "remote-engine" ]; then - skip "needs checkout which is unsupported for remote-engine" - fi - dolt branch branch1 - dolt checkout -b branch2 - dolt sql -q "CREATE TABLE new_test(id INT); INSERT INTO new_test VALUES (1);" - dolt commit -Am "new table 'new_test' is created" - - dolt checkout branch1 - run dolt ls + dolt branch br1 + dolt branch br2 + dolt --branch br2 sql -q "CREATE TABLE new_test(id INT); INSERT INTO new_test VALUES (1);" + dolt --branch br2 commit -Am "new table 'new_test' is created" + + run dolt --branch br1 ls [ "$status" -eq 0 ] [[ "$output" =~ "test" ]] || false - dolt sql -q "CREATE TABLE new_test(id INT); INSERT INTO new_test VALUES (1);" - dolt add . - run dolt stash + dolt --branch br1 sql -q "CREATE TABLE new_test(id INT); INSERT INTO new_test VALUES (1);" + dolt --branch br1 add . + run dolt --branch br1 stash [ "$status" -eq 0 ] [[ "$output" =~ "Saved working directory and index state" ]] || false - dolt checkout branch2 - run dolt stash list + run dolt --branch br2 stash list [ "$status" -eq 0 ] [ "${#lines[@]}" -eq 1 ] [[ "$output" =~ "stash@{0}" ]] || false skip # stash of the exact copy of keyless table causes merge conflict, where it should not - run dolt stash pop + run dolt --branch br2 stash pop [ "$status" -eq 0 ] [[ "$output" =~ "nothing to commit, working tree clean" ]] || false [[ "$output" =~ "Dropped refs/stash@{0}" ]] || false From 18dc62e7a48bf5662d5730568eb426041278f65e Mon Sep 17 00:00:00 2001 From: Nathan Gabrielson Date: Mon, 23 Jun 2025 16:20:11 -0700 Subject: [PATCH 11/11] Fix engine test, stash.go cleanup --- go/cmd/dolt/commands/stash.go | 119 +++++++++--------- .../sqle/enginetest/dolt_queries_stash.go | 4 +- 2 files changed, 59 insertions(+), 64 deletions(-) diff --git a/go/cmd/dolt/commands/stash.go b/go/cmd/dolt/commands/stash.go index 884c9377835..4293f041b7a 100644 --- a/go/cmd/dolt/commands/stash.go +++ b/go/cmd/dolt/commands/stash.go @@ -34,11 +34,19 @@ import ( "github.com/dolthub/dolt/go/libraries/utils/argparser" ) +const ( + PushCmdRef = "push" + PopCmdRef = "pop" + DropCmdRef = "drop" + ClearCmdRef = "clear" + ListCmdRef = "list" +) + 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. + ShortDesc: "Stash the changes in a dirty workspace away.", + LongDesc: `Use dolt stash when you want to record the current state of the workspace and the index, but want to go back to a clean workspace. -The command saves your local modifications away and reverts the working directory to match the HEAD commit. The stash entries that are saved away can be listed with 'dolt stash list'. +The command saves your local modifications away and reverts the workspace to match the HEAD commit. The stash entries that are saved away can be listed with 'dolt stash list'. `, Synopsis: []string{ "", // this is for `dolt stash` itself. @@ -58,7 +66,7 @@ func (cmd StashCmd) Name() string { // Description returns a description of the command func (cmd StashCmd) Description() string { - return "Stash the changes in a dirty working directory away." + return "Stash the changes in a dirty workspace away." } func (cmd StashCmd) Docs() *cli.CommandDocumentation { @@ -93,23 +101,40 @@ func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, subcommand = strings.ToLower(apr.Arg(0)) } - var err error + queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + if closeFunc != nil { + defer closeFunc() + } + + idx := 0 + if apr.NArg() > 1 { + idx, err = parseStashIndex(apr.Arg(1)) + if err != nil { + cli.PrintErrln(errhand.VerboseErrorFromError(err)) + return 1 + } + } + switch subcommand { - case "push": - err = stashPush(ctx, cliCtx, apr, subcommand) - case "pop", "drop": - err = stashRemove(ctx, cliCtx, apr, subcommand) - case "list": + case PushCmdRef: + err = stashPush(queryist, sqlCtx, apr, subcommand) + case PopCmdRef, DropCmdRef: + err = stashRemove(queryist, sqlCtx, cliCtx, apr, subcommand, idx) + case ListCmdRef: err = stashList(ctx, cliCtx) - case "clear": - err = stashClear(ctx, cliCtx, apr, subcommand) + case ClearCmdRef: + err = stashClear(queryist, sqlCtx, apr, subcommand) default: err = fmt.Errorf("unknown stash subcommand %s", subcommand) } if err != nil { cli.PrintErrln(errhand.VerboseErrorFromError(err)) - if err.Error() == "No local changes to save" { + if strings.Contains(err.Error(), "No local changes to save") { return 0 } return 1 @@ -117,14 +142,11 @@ func (cmd StashCmd) Exec(ctx context.Context, commandStr string, args []string, return 0 } -func stashPush(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { - rowIter, queryist, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) +func stashPush(queryist cli.Queryist, sqlCtx *sql.Context, apr *argparser.ArgParseResults, subcommand string) error { + rowIter, err := stashQuery(queryist, sqlCtx, apr, subcommand) if err != nil { return err } - if closeFunc != nil { - defer closeFunc() - } stashes, err := getStashesSQL(sqlCtx, queryist, 1) if err != nil { @@ -136,20 +158,7 @@ func stashPush(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgPar return err } -func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { - idx, err := parseStashIndex(apr) - if err != nil { - return err - } - - queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) - if err != nil { - return err - } - if closeFunc != nil { - defer closeFunc() - } - +func stashRemove(queryist cli.Queryist, sqlCtx *sql.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string, idx int) error { stashes, err := getStashesSQL(sqlCtx, queryist, 0) if err != nil { return err @@ -170,8 +179,8 @@ func stashRemove(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgP return err } - if subcommand == "pop" { - ret := StatusCmd{}.Exec(sqlCtx, "status", []string{}, nil, cliCtx) + if subcommand == PopCmdRef { + ret := StatusCmd{}.Exec(sqlCtx, StatusCmd{}.Name(), []string{}, nil, cliCtx) if ret != 0 { cli.Println("The stash entry is kept in case you need it again.") return err @@ -203,14 +212,11 @@ func stashList(ctx context.Context, cliCtx cli.CliContext) error { return nil } -func stashClear(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) error { - rowIter, _, sqlCtx, closeFunc, err := stashQuery(ctx, cliCtx, apr, subcommand) +func stashClear(queryist cli.Queryist, sqlCtx *sql.Context, apr *argparser.ArgParseResults, subcommand string) error { + rowIter, err := stashQuery(queryist, sqlCtx, apr, subcommand) if err != nil { return err } - if closeFunc != nil { - defer closeFunc() - } _, err = sql.RowIterToRows(sqlCtx, rowIter) return err } @@ -276,10 +282,10 @@ func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string } if apr.Contains(cli.AllFlag) { - buffer.WriteString(", '-a'") + buffer.WriteString(", '--all'") } if apr.Contains(cli.IncludeUntrackedFlag) { - buffer.WriteString(", '-u'") + buffer.WriteString(", '--include-untracked'") } buffer.WriteString(")") @@ -287,37 +293,26 @@ func generateStashSql(apr *argparser.ArgParseResults, subcommand string) (string return interpolatedQuery, err } -func stashQuery(ctx context.Context, cliCtx cli.CliContext, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, cli.Queryist, *sql.Context, func(), error) { - queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx) - if err != nil { - return nil, nil, nil, nil, err - } - +func stashQuery(queryist cli.Queryist, sqlCtx *sql.Context, apr *argparser.ArgParseResults, subcommand string) (sql.RowIter, error) { interpolatedQuery, err := generateStashSql(apr, subcommand) if err != nil { - return nil, nil, nil, nil, err + return nil, err } _, rowIter, _, err := queryist.Query(sqlCtx, interpolatedQuery) if err != nil { - return nil, nil, nil, nil, err + return nil, err } - return rowIter, queryist, sqlCtx, closeFunc, nil + return rowIter, nil } -func parseStashIndex(apr *argparser.ArgParseResults) (int, error) { - idx := 0 - - if apr.NArg() > 1 { - stashID := apr.Arg(1) - 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) - } +func parseStashIndex(stashID string) (int, error) { + 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 diff --git a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go index 3d0da8c7928..b0e748a4031 100644 --- a/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go +++ b/go/libraries/doltcore/sqle/enginetest/dolt_queries_stash.go @@ -26,7 +26,7 @@ var DoltStashTests = []queries.ScriptTest{ Assertions: []queries.ScriptTestAssertion{ { Query: "CALL DOLT_STASH('push', 'myStash');", - ExpectedErrStr: "no local changes to save", + ExpectedErrStr: "No local changes to save", }, { Query: "CREATE TABLE test (i int)", @@ -34,7 +34,7 @@ var DoltStashTests = []queries.ScriptTest{ }, { Query: "CALL DOLT_STASH('push', 'myStash');", - ExpectedErrStr: "no local changes to save", + ExpectedErrStr: "No local changes to save", }, { Query: "CALL DOLT_STASH('pop', 'myStash');",