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

266 lines
8.1 KiB
Go

// Copyright 2019 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"
"strings"
"github.com/dolthub/go-mysql-server/sql"
"github.com/gocraft/dbr/v2"
"github.com/gocraft/dbr/v2/dialect"
"github.com/dolthub/dolt/go/cmd/dolt/cli"
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
"github.com/dolthub/dolt/go/libraries/doltcore/diff"
"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"
)
const (
SoftResetParam = "soft"
HardResetParam = "hard"
)
var resetDocContent = cli.CommandDocumentationContent{
ShortDesc: "Resets staged or working tables to HEAD or a specified commit",
LongDesc: "{{.EmphasisLeft}}dolt reset <tables>...{{.EmphasisRight}}" +
"\n\n" +
"The default form resets the values for all staged {{.LessThan}}tables{{.GreaterThan}} to their values at {{.EmphasisLeft}}HEAD{{.EmphasisRight}}. " +
"It does not affect the working tree or the current branch." +
"\n\n" +
"This means that {{.EmphasisLeft}}dolt reset <tables>{{.EmphasisRight}} is the opposite of {{.EmphasisLeft}}dolt add <tables>{{.EmphasisRight}}." +
"\n\n" +
"After running {{.EmphasisLeft}}dolt reset <tables>{{.EmphasisRight}} to update the staged tables, you can use {{.EmphasisLeft}}dolt checkout{{.EmphasisRight}} to check the contents out of the staged tables to the working tables." +
"\n\n" +
"{{.EmphasisLeft}}dolt reset [--hard | --soft] <revision>{{.EmphasisRight}}" +
"\n\n" +
"This form resets all tables to values in the specified revision (i.e. commit, tag, working set). " +
"The --soft option resets HEAD to a revision without changing the current working set. " +
" The --hard option resets all three HEADs to a revision, deleting all uncommitted changes in the current working set." +
"\n\n" +
"{{.EmphasisLeft}}dolt reset .{{.EmphasisRight}}" +
"\n\n" +
"This form resets {{.EmphasisLeft}}all{{.EmphasisRight}} staged tables to their values at HEAD. It is the opposite of {{.EmphasisLeft}}dolt add .{{.EmphasisRight}}",
Synopsis: []string{
"{{.LessThan}}tables{{.GreaterThan}}...",
"[--hard | --soft] {{.LessThan}}revision{{.GreaterThan}}",
},
}
type ResetCmd struct{}
// Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
func (cmd ResetCmd) Name() string {
return "reset"
}
// Description returns a description of the command
func (cmd ResetCmd) Description() string {
return "Remove table changes from the list of staged table changes."
}
func (cmd ResetCmd) Docs() *cli.CommandDocumentation {
ap := cli.CreateResetArgParser()
return cli.NewCommandDocumentation(resetDocContent, ap)
}
func (cmd ResetCmd) ArgParser() *argparser.ArgParser {
return cli.CreateResetArgParser()
}
// Exec executes the command
func (cmd ResetCmd) Exec(ctx context.Context, commandStr string, args []string, _ *env.DoltEnv, cliCtx cli.CliContext) int {
ap := cli.CreateResetArgParser()
apr, usage, terminate, status := ParseArgsOrPrintHelp(ap, commandStr, args, resetDocContent)
if terminate {
return status
}
queryist, err := cliCtx.QueryEngine(ctx)
if err != nil {
cli.Println(err.Error())
return 1
}
if apr.ContainsAll(HardResetParam, SoftResetParam) {
verr := errhand.BuildDError("error: --%s and --%s are mutually exclusive options.", HardResetParam, SoftResetParam).Build()
return HandleVErrAndExitCode(verr, usage)
} else if apr.Contains(HardResetParam) && apr.NArg() > 1 {
return handleResetError(fmt.Errorf("--hard supports at most one additional param"), usage)
} else if apr.Contains(SoftResetParam) && apr.NArg() > 1 {
return handleResetError(fmt.Errorf("--soft supports at most one additional param"), usage)
}
// process query through prepared statement to prevent sql injection
query, err := constructInterpolatedDoltResetQuery(apr)
_, _, _, err = queryist.Queryist.Query(queryist.Context, query)
if err != nil {
return handleResetError(err, usage)
}
printNotStaged(queryist.Context, queryist.Queryist)
return 0
}
// constructInterpolatedDoltResetQuery generates the sql query necessary to call the DOLT_RESET() stored procedure.
// Also interpolates this query to prevent sql injection.
func constructInterpolatedDoltResetQuery(apr *argparser.ArgParseResults) (string, error) {
var params []interface{}
var param bool
var buffer bytes.Buffer
var first bool
first = true
buffer.WriteString("CALL DOLT_RESET(")
writeToBuffer := func(s string) {
if !first {
buffer.WriteString(", ")
}
if !param {
buffer.WriteString("'")
}
buffer.WriteString(s)
if !param {
buffer.WriteString("'")
}
first = false
param = false
}
if apr.Contains(HardResetParam) {
writeToBuffer("--hard")
if apr.NArg() == 1 {
param = true
writeToBuffer("?")
params = append(params, apr.Arg(0))
}
} else if apr.Contains(SoftResetParam) {
writeToBuffer("--soft")
if apr.NArg() == 1 {
param = true
writeToBuffer("?")
params = append(params, apr.Arg(0))
}
} else {
for _, input := range apr.Args {
param = true
writeToBuffer("?")
params = append(params, input)
}
}
buffer.WriteString(")")
interpolatedQuery, err := dbr.InterpolateForDialect(buffer.String(), params, dialect.MySQL)
if err != nil {
return "", err
}
return interpolatedQuery, nil
}
var tblDiffTypeToShortLabel = map[diff.TableDiffType]string{
diff.ModifiedTable: "M",
diff.RemovedTable: "D",
diff.AddedTable: "N",
}
type StatRow struct {
tableName string
status string
}
func buildStatRows(rows []sql.Row) []StatRow {
statRows := make([]StatRow, 0, len(rows))
for _, row := range rows {
statRows = append(statRows, StatRow{row[0].(string), row[1].(string)})
}
return statRows
}
func printNotStaged(sqlCtx *sql.Context, queryist cli.Queryist) {
// Printing here is best effort. Fail silently
_, rowIter, _, err := queryist.Query(sqlCtx, "select table_name,status from dolt_status where staged = false")
if err != nil {
return
}
rows, err := sql.RowIterToRows(sqlCtx, rowIter)
if err != nil {
return
}
if rows == nil {
return
}
tranRow := buildStatRows(rows)
removeModified := 0
for _, row := range tranRow {
if row.status != "new table" {
removeModified++
}
}
if removeModified > 0 {
cli.Println("Unstaged changes after reset:")
var lines []string
for _, row := range tranRow {
if row.status == "new table" {
// per Git, unstaged new tables are untracked
continue
} else if row.status == "deleted" {
lines = append(lines, fmt.Sprintf("%s\t%s", tblDiffTypeToShortLabel[diff.RemovedTable], row.tableName))
} else if row.status == "renamed" {
// per Git, unstaged renames are shown as drop + add
lines = append(lines, fmt.Sprintf("%s\t%s", tblDiffTypeToShortLabel[diff.RemovedTable], row.tableName))
} else {
lines = append(lines, fmt.Sprintf("%s\t%s", tblDiffTypeToShortLabel[diff.ModifiedTable], row.tableName))
}
}
cli.Println(strings.Join(lines, "\n"))
}
}
func handleResetError(err error, usage cli.UsagePrinter) int {
if actions.IsTblNotExist(err) {
tbls := actions.GetTablesForError(err)
// In case the ref does not exist.
bdr := errhand.BuildDError("Invalid Ref or Table:")
if len(tbls) > 1 {
bdr = errhand.BuildDError("Invalid Table(s):")
}
for _, tbl := range tbls {
bdr.AddDetails("\t%s", tbl.Name)
}
return HandleVErrAndExitCode(bdr.Build(), usage)
}
var verr errhand.VerboseError = nil
if err != nil {
verr = errhand.BuildDError("error: Failed to reset changes.").AddCause(err).Build()
}
return HandleVErrAndExitCode(verr, usage)
}