diff --git a/go/cmd/dolt/commands/reset.go b/go/cmd/dolt/commands/reset.go index f20776ec18..ca253ae146 100644 --- a/go/cmd/dolt/commands/reset.go +++ b/go/cmd/dolt/commands/reset.go @@ -101,7 +101,22 @@ func (cmd ResetCmd) Exec(ctx context.Context, commandStr string, args []string, err = actions.ResetHard(ctx, dEnv, arg, workingRoot, stagedRoot, headRoot) } else { - stagedRoot, err = actions.ResetSoft(ctx, dEnv.DbData(), apr, stagedRoot, headRoot) + // Check whether the input argument is a ref. + if apr.NArg() == 1 { + argToCheck := apr.Arg(0) + + ok := actions.ValidateIsRef(ctx, argToCheck, dEnv.DoltDB, dEnv.RepoStateReader()) + + // This is a valid ref + if ok { + err = actions.ResetSoftToRef(ctx, dEnv.DbData(), apr.Arg(0)) + return handleResetError(err, usage) + } + } + + tables := apr.Args() + + stagedRoot, err = actions.ResetSoft(ctx, dEnv.DbData(), tables, stagedRoot, headRoot) if err != nil { return handleResetError(err, usage) @@ -171,7 +186,12 @@ func printNotStaged(ctx context.Context, dEnv *env.DoltEnv, staged *doltdb.RootV func handleResetError(err error, usage cli.UsagePrinter) int { if actions.IsTblNotExist(err) { tbls := actions.GetTablesForError(err) - bdr := errhand.BuildDError("Invalid Table(s):") + + // 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" + tbl) diff --git a/go/libraries/doltcore/doltdb/commit_spec.go b/go/libraries/doltcore/doltdb/commit_spec.go index 0efcb5a0e2..a4c511d258 100644 --- a/go/libraries/doltcore/doltdb/commit_spec.go +++ b/go/libraries/doltcore/doltdb/commit_spec.go @@ -87,7 +87,7 @@ type CommitSpec struct { // * remotes/origin/master~~ // * refs/heads/my-feature-branch^2~ // -// Constructing a |CommitSpec| does not mean the sepcified branch or commit +// Constructing a |CommitSpec| does not mean the specified branch or commit // exists. This carries a description of how to find the specified commit. See // |doltdb.Resolve| for resolving a |CommitSpec| to a |Commit|. func NewCommitSpec(cSpecStr string) (*CommitSpec, error) { diff --git a/go/libraries/doltcore/env/actions/reset.go b/go/libraries/doltcore/env/actions/reset.go index 9e671d3866..e39ef0e599 100644 --- a/go/libraries/doltcore/env/actions/reset.go +++ b/go/libraries/doltcore/env/actions/reset.go @@ -175,8 +175,8 @@ func ResetSoftTables(ctx context.Context, dbData env.DbData, apr *argparser.ArgP return stagedRoot, nil } -func ResetSoft(ctx context.Context, dbData env.DbData, apr *argparser.ArgParseResults, stagedRoot, headRoot *doltdb.RootValue) (*doltdb.RootValue, error) { - tables, err := getUnionedTables(ctx, apr.Args(), stagedRoot, headRoot) +func ResetSoft(ctx context.Context, dbData env.DbData, tables []string, stagedRoot, headRoot *doltdb.RootValue) (*doltdb.RootValue, error) { + tables, err := getUnionedTables(ctx, tables, stagedRoot, headRoot) if err != nil { return nil, err @@ -211,6 +211,38 @@ func ResetSoft(ctx context.Context, dbData env.DbData, apr *argparser.ArgParseRe return stagedRoot, nil } +// ResetSoftToRef matches the `git reset --soft ` pattern. It resets both staged and head to the previous ref +// and leaves the working unset. The user can then choose to create a commit that contains all changes since the ref. +func ResetSoftToRef(ctx context.Context, dbData env.DbData, cSpecStr string) error { + cs, err := doltdb.NewCommitSpec(cSpecStr) + if err != nil { + return err + } + + newHead, err := dbData.Ddb.Resolve(ctx, cs, dbData.Rsr.CWBHeadRef()) + if err != nil { + return err + } + + foundRoot, err := newHead.GetRootValue() + if err != nil { + return err + } + + // Changed the stage to old the root. Leave the working as is. + _, err = env.UpdateStagedRoot(ctx, dbData.Ddb, dbData.Rsw, foundRoot) + if err != nil { + return err + } + + // Update the head to this commit + if err = dbData.Ddb.SetHeadToCommit(ctx, dbData.Rsr.CWBHeadRef(), newHead); err != nil { + return err + } + + return err +} + func getUnionedTables(ctx context.Context, tables []string, stagedRoot, headRoot *doltdb.RootValue) ([]string, error) { if len(tables) == 0 || (len(tables) == 1 && tables[0] == ".") { var err error @@ -258,3 +290,18 @@ func resetStaged(ctx context.Context, ddb *doltdb.DoltDB, rsw env.RepoStateWrite return updatedRoot, env.UpdateStagedRootWithVErr(ddb, rsw, updatedRoot) } + +// ValidateIsRef validates whether the input parameter is a valid cString +func ValidateIsRef(ctx context.Context, cSpecStr string, ddb *doltdb.DoltDB, rsr env.RepoStateReader) bool { + cs, err := doltdb.NewCommitSpec(cSpecStr) + if err != nil { + return false + } + + _, err = ddb.Resolve(ctx, cs, rsr.CWBHeadRef()) + if err != nil { + return false + } + + return true +} diff --git a/go/libraries/doltcore/sqle/json/noms_json_value_test.go b/go/libraries/doltcore/sqle/json/noms_json_value_test.go index 2c1d6570b3..8c218c1865 100644 --- a/go/libraries/doltcore/sqle/json/noms_json_value_test.go +++ b/go/libraries/doltcore/sqle/json/noms_json_value_test.go @@ -205,6 +205,8 @@ func TestJSONStructuralSharing(t *testing.T) { err = db.Flush(ctx) require.NoError(t, err) + err = db.(datas.GarbageCollector).GC(ctx) + require.NoError(t, err) after := ts.Len() // flush creates a single chunk diff --git a/integration-tests/bats/docs.bats b/integration-tests/bats/docs.bats index 17d225eb18..ac2c594cfa 100644 --- a/integration-tests/bats/docs.bats +++ b/integration-tests/bats/docs.bats @@ -328,7 +328,7 @@ SQL dolt add . run dolt reset LICENSE.md invalid [ "$status" -eq 1 ] - [[ "$output" =~ "Invalid Table(s)" ]] || false + [[ "$output" =~ "Invalid Ref or Table" ]] || false [[ "$output" =~ "invalid" ]] || false run dolt status [ "$status" -eq 0 ] diff --git a/integration-tests/bats/status.bats b/integration-tests/bats/status.bats index df0ce6b1f3..20cc0d14d3 100644 --- a/integration-tests/bats/status.bats +++ b/integration-tests/bats/status.bats @@ -204,4 +204,201 @@ SQL @test "status: dolt reset --hard with more than one additional arg throws an error " { run dolt reset --hard HEAD HEAD2 [ "$status" -eq 1 ] +} + +@test "status: dolt reset hard with ~ works" { + dolt sql -q "CREATE TABLE test (pk int PRIMARY KEY);" + dolt commit -am "cm1" + + dolt sql -q "INSERT INTO test values (1);" + dolt commit -am "cm2" + + dolt sql -q "INSERT INTO test VALUES (2);" + dolt commit -am "cm3" + + # Do a hard reset back one commit and confirm the appropriate values. + run dolt reset --hard HEAD~1 + [ "$status" -eq 0 ] + + run dolt sql -q "SELECT sum(pk) FROM test;" + [ "$status" -eq 0 ] + [[ "$output" =~ "1" ]] || false + + # Since this is a hard reset double check the status + run dolt status + [ "$status" -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false + + # Run again with ~2 this time + dolt sql -q "INSERT INTO test VALUES (2);" + dolt commit -am "cm3" + + # Do a hard reset back two commits and confirm the appropriate values. + run dolt reset --hard HEAD~2 + [ "$status" -eq 0 ] + + run dolt sql -q "SELECT sum(pk) FROM test;" + [ "$status" -eq 0 ] + [[ "$output" =~ "NULL" ]] || false + + # Since this is a hard reset double check the status + run dolt status + [ "$status" -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false +} + +@test "status: dolt reset soft with ~ works" { + dolt sql -q "CREATE TABLE test (pk int PRIMARY KEY);" + dolt commit -am "cm1" + + dolt sql -q "INSERT INTO test values (1);" + dolt commit -am "cm2" + + # Make a dirty change + dolt sql -q "INSERT INTO test values (2)" + run dolt reset HEAD~ + [ "$status" -eq 0 ] + + # Verify that the changes are still there + run dolt sql -q "SELECT sum(pk) FROM test;" + [ "$status" -eq 0 ] + [[ "$output" =~ "3" ]] || false + + # Now verify that commit log has changes + run dolt sql -q "SELECT count(*) from dolt_log" + [[ "$output" =~ "2" ]] || false + + run dolt reset HEAD~1 + [ "$status" -eq 0 ] + + # Verify that the changes are still there + run dolt sql -q "SELECT sum(pk) FROM test;" + [ "$status" -eq 0 ] + [[ "$output" =~ "3" ]] || false + + run dolt status + [[ "$output" =~ "Untracked files:" ]] || false + [[ "$output" =~ " (use \"dolt add \" to include in what will be committed)" ]] || false + [[ "$output" =~ " new table: test" ]] || false + + # Now verify that commit log has changes + run dolt sql -q "SELECT count(*) from dolt_log" + [[ "$output" =~ "1" ]] || false +} + +@test "status: dolt reset works with commit hash ref" { + dolt sql -q "CREATE TABLE tb1 (pk int PRIMARY KEY);" + dolt sql -q "INSERT INTO tb1 values (1);" + dolt commit -am "cm1" + + cm1=$(get_head_commit) + + dolt sql -q "CREATE TABLE tb2 (pk int PRIMARY KEY);" + dolt sql -q "INSERT INTO tb2 values (11);" + dolt commit -am "cm2" + + cm2=$(get_head_commit) + + dolt sql -q "CREATE TABLE tb3 (pk int PRIMARY KEY);" + dolt sql -q "INSERT INTO tb3 values (11);" + dolt commit -am "cm3" + + cm3=$(get_head_commit) + + # Try a soft reset to commit 3. Nothing should change + dolt reset $cm3 + run dolt status + [ "$status" -eq 0 ] + [[ "$output" =~ "On branch master" ]] || false + [[ "$output" =~ "nothing to commit, working tree clean" ]] || false + + # Do a soft reset to commit 2. + dolt reset $cm2 + run dolt status + [ "$status" -eq 0 ] + [[ "$output" =~ "Untracked files:" ]] || false + [[ "$output" =~ " (use \"dolt add \" to include in what will be committed)" ]] || false + [[ "$output" =~ " new table: tb3" ]] || false + ! [[ "$output" =~ " new table: tb2" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM tb3" + [[ "$output" =~ "1" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM dolt_log" + [[ "$output" =~ "3" ]] || false # includes init commit + + dolt commit -am "commit 3" + + # Do a soft reset to commit 1 + dolt reset $cm1 + run dolt status + [ "$status" -eq 0 ] + [[ "$output" =~ "Untracked files:" ]] || false + [[ "$output" =~ " (use \"dolt add \" to include in what will be committed)" ]] || false + [[ "$output" =~ " new table: tb3" ]] || false + [[ "$output" =~ " new table: tb2" ]] || false + ! [[ "$output" =~ " new table: tb1" ]] || false + + run dolt sql -q "SELECT COUNT(*) FROM dolt_log" + [[ "$output" =~ "2" ]] || false # includes init commit +} + +@test "status: dolt reset works with branch ref" { + dolt sql -q "CREATE TABLE tbl(pk int);" + dolt sql -q "INSERT into tbl VALUES (1)" + dolt commit -am "cm1" + + # create a new branch and make a change + dolt checkout -b test + dolt sql -q "INSERT INTO tbl VALUES (2),(3)" + dolt sql -q "CREATE TABLE tbl2(pk int);" + dolt commit -am "test cm1" + + # go back to master and merge + dolt checkout master + dolt merge test + dolt sql -q "INSERT INTO tbl VALUES (4)" + dolt commit -am "cm2" + + # execute the reset + dolt reset test + run dolt status + + [ "$status" -eq 0 ] + [[ "$output" =~ "Changes not staged for commit:" ]] || false + [[ "$output" =~ " (use \"dolt add \" to update what will be committed)" ]] || false + [[ "$output" =~ " (use \"dolt checkout
\" to discard changes in working directory)" ]] || false + [[ "$output" =~ " modified: tbl" ]] || false + ! [[ "$output" =~ " new table: tb2" ]] || false +} + +@test "status: dolt reset ref properly manages staged changes as well" { + dolt sql -q "CREATE TABLE tbl(pk int);" + dolt sql -q "INSERT into tbl VALUES (1)" + dolt commit -am "cm1" + + dolt sql -q "INSERT INTO tbl VALUES (2)" + dolt add . + + dolt reset HEAD + run dolt status + + [ "$status" -eq 0 ] + [[ "$output" =~ "Changes not staged for commit:" ]] || false + [[ "$output" =~ " (use \"dolt add
\" to update what will be committed)" ]] || false + [[ "$output" =~ " (use \"dolt checkout
\" to discard changes in working directory)" ]] || false + [[ "$output" =~ " modified: tbl" ]] || false +} + +@test "status: dolt reset throws errors for unknown ref/table" { + run dolt reset test + [ "$status" -eq 1 ] + [[ "$output" =~ "Invalid Ref or Table" ]] || false + [[ "$output" =~ "test" ]] || false +} + +get_head_commit() { + dolt log -n 1 | grep -m 1 commit | cut -c 8- } \ No newline at end of file