mirror of
https://github.com/dolthub/dolt.git
synced 2026-01-25 18:49:36 -06:00
Merge pull request #7373 from dolthub/steph/rebase
add `dolt rebase` command
This commit is contained in:
@@ -359,7 +359,7 @@ func getCommitMessageFromEditor(sqlCtx *sql.Context, queryist cli.Queryist, sugg
|
||||
editorStr := cliCtx.Config().GetStringOrDefault(config.DoltEditor, backupEd)
|
||||
|
||||
cli.ExecuteWithStdioRestored(func() {
|
||||
commitMsg, cErr := editor.OpenCommitEditor(editorStr, initialMsg)
|
||||
commitMsg, cErr := editor.OpenTempEditor(editorStr, initialMsg)
|
||||
if cErr != nil {
|
||||
err = cErr
|
||||
}
|
||||
|
||||
341
go/cmd/dolt/commands/rebase.go
Normal file
341
go/cmd/dolt/commands/rebase.go
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2024 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"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"
|
||||
eventsapi "github.com/dolthub/dolt/go/gen/proto/dolt/services/eventsapi/v1alpha1"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/dconfig"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/rebase"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dprocedures"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/argparser"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/config"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/editor"
|
||||
)
|
||||
|
||||
var rebaseDocs = cli.CommandDocumentationContent{
|
||||
ShortDesc: "Reapplies commits on top of another base tip",
|
||||
LongDesc: `Rewrites commit history for the current branch by replaying commits, allowing the commits to be reordered,
|
||||
squashed, or dropped. The commits included in the rebase plan are the commits reachable by the current branch, but NOT
|
||||
reachable from the branch specified as the argument when starting a rebase (also known as the upstream branch). This is
|
||||
the same as Git and Dolt's "two dot log" syntax, or |upstreamBranch|..|currentBranch|.
|
||||
|
||||
Rebasing is useful to clean and organize your commit history, especially before merging a feature branch back to a shared
|
||||
branch. For example, you can drop commits that contain debugging or test changes, or squash or fixup small commits into a
|
||||
single commit, or reorder commits so that related changes are adjacent in the new commit history.
|
||||
`,
|
||||
Synopsis: []string{
|
||||
`(-i | --interactive) {{.LessThan}}upstream{{.GreaterThan}}`,
|
||||
`(--continue | --abort)`,
|
||||
},
|
||||
}
|
||||
|
||||
type RebaseCmd struct{}
|
||||
|
||||
var _ cli.Command = RebaseCmd{}
|
||||
|
||||
// Name returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
|
||||
func (cmd RebaseCmd) Name() string {
|
||||
return "rebase"
|
||||
}
|
||||
|
||||
// Description returns a description of the command
|
||||
func (cmd RebaseCmd) Description() string {
|
||||
return rebaseDocs.ShortDesc
|
||||
}
|
||||
|
||||
// EventType returns the type of the event to log
|
||||
func (cmd RebaseCmd) EventType() eventsapi.ClientEventType {
|
||||
return eventsapi.ClientEventType_REBASE
|
||||
}
|
||||
|
||||
func (cmd RebaseCmd) Docs() *cli.CommandDocumentation {
|
||||
ap := cmd.ArgParser()
|
||||
return cli.NewCommandDocumentation(rebaseDocs, ap)
|
||||
}
|
||||
|
||||
func (cmd RebaseCmd) ArgParser() *argparser.ArgParser {
|
||||
return cli.CreateRebaseArgParser()
|
||||
}
|
||||
|
||||
// Exec executes the command
|
||||
func (cmd RebaseCmd) Exec(ctx context.Context, commandStr string, args []string, dEnv *env.DoltEnv, cliCtx cli.CliContext) int {
|
||||
ap := cmd.ArgParser()
|
||||
help, usage := cli.HelpAndUsagePrinters(cli.CommandDocsForCommandString(commandStr, rebaseDocs, ap))
|
||||
apr := cli.ParseArgsOrDie(ap, args, help)
|
||||
|
||||
queryist, sqlCtx, closeFunc, err := cliCtx.QueryEngine(ctx)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
if closeFunc != nil {
|
||||
defer closeFunc()
|
||||
}
|
||||
|
||||
branchName, err := getActiveBranchName(sqlCtx, queryist)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
|
||||
query, err := constructInterpolatedDoltRebaseQuery(apr)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
|
||||
rows, err := GetRowsForSql(queryist, sqlCtx, query)
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
|
||||
status, err := getInt64ColAsInt64(rows[0][0])
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
if status == 1 {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(errors.New("error: "+rows[0][1].(string))), usage)
|
||||
}
|
||||
|
||||
message := rows[0][1].(string)
|
||||
if strings.Contains(message, dprocedures.SuccessfulRebaseMessage) {
|
||||
cli.Println(dprocedures.SuccessfulRebaseMessage + branchName)
|
||||
} else if strings.Contains(message, dprocedures.RebaseAbortedMessage) {
|
||||
cli.Println(dprocedures.RebaseAbortedMessage)
|
||||
} else {
|
||||
rebasePlan, err := getRebasePlan(cliCtx, sqlCtx, queryist, apr.Arg(0), branchName)
|
||||
if err != nil {
|
||||
// attempt to abort the rebase
|
||||
_, _, _ = queryist.Query(sqlCtx, "CALL DOLT_REBASE('--abort');")
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
|
||||
// if all uncommented lines are deleted in the editor, abort the rebase
|
||||
if rebasePlan == nil || rebasePlan.Steps == nil || len(rebasePlan.Steps) == 0 {
|
||||
rows, err := GetRowsForSql(queryist, sqlCtx, "CALL DOLT_REBASE('--abort');")
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
status, err := getInt64ColAsInt64(rows[0][0])
|
||||
if err != nil {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
if status == 1 {
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(errors.New("error: "+rows[0][1].(string))), usage)
|
||||
}
|
||||
|
||||
cli.Println(dprocedures.RebaseAbortedMessage)
|
||||
} else {
|
||||
err = insertRebasePlanIntoDoltRebaseTable(rebasePlan, sqlCtx, queryist)
|
||||
if err != nil {
|
||||
// attempt to abort the rebase
|
||||
_, _, _ = queryist.Query(sqlCtx, "CALL DOLT_REBASE('--abort');")
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
|
||||
rows, err := GetRowsForSql(queryist, sqlCtx, "CALL DOLT_REBASE('--continue');")
|
||||
if err != nil {
|
||||
// attempt to abort the rebase
|
||||
_, _, _ = queryist.Query(sqlCtx, "CALL DOLT_REBASE('--abort');")
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
status, err := getInt64ColAsInt64(rows[0][0])
|
||||
if err != nil {
|
||||
// attempt to abort the rebase
|
||||
_, _, _ = queryist.Query(sqlCtx, "CALL DOLT_REBASE('--abort');")
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(err), usage)
|
||||
}
|
||||
if status == 1 {
|
||||
// attempt to abort the rebase
|
||||
_, _, _ = queryist.Query(sqlCtx, "CALL DOLT_REBASE('--abort');")
|
||||
return HandleVErrAndExitCode(errhand.VerboseErrorFromError(errors.New("error: "+rows[0][1].(string))), usage)
|
||||
}
|
||||
|
||||
cli.Println(dprocedures.SuccessfulRebaseMessage + branchName)
|
||||
}
|
||||
}
|
||||
|
||||
return HandleVErrAndExitCode(nil, usage)
|
||||
}
|
||||
|
||||
// constructInterpolatedDoltRebaseQuery generates the sql query necessary to call the DOLT_REBASE() function.
|
||||
// Also interpolates this query to prevent sql injection.
|
||||
func constructInterpolatedDoltRebaseQuery(apr *argparser.ArgParseResults) (string, error) {
|
||||
var params []interface{}
|
||||
var args []string
|
||||
|
||||
if apr.NArg() == 1 {
|
||||
params = append(params, apr.Arg(0))
|
||||
args = append(args, "?")
|
||||
}
|
||||
if apr.Contains(cli.InteractiveFlag) {
|
||||
args = append(args, "'--interactive'")
|
||||
}
|
||||
if apr.Contains(cli.ContinueFlag) {
|
||||
args = append(args, "'--continue'")
|
||||
}
|
||||
if apr.Contains(cli.AbortParam) {
|
||||
args = append(args, "'--abort'")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf("CALL DOLT_REBASE(%s);", strings.Join(args, ", "))
|
||||
return dbr.InterpolateForDialect(query, params, dialect.MySQL)
|
||||
}
|
||||
|
||||
// getRebasePlan opens an editor for users to edit the rebase plan and returns the parsed rebase plan from the editor.
|
||||
func getRebasePlan(cliCtx cli.CliContext, sqlCtx *sql.Context, queryist cli.Queryist, rebaseBranch, currentBranch string) (*rebase.RebasePlan, error) {
|
||||
if cli.ExecuteWithStdioRestored == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !checkIsTerminal() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
initialRebaseMsg, err := buildInitialRebaseMsg(sqlCtx, queryist, rebaseBranch, currentBranch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backupEd := "vim"
|
||||
// try getting default editor on the user system
|
||||
if ed, edSet := os.LookupEnv(dconfig.EnvEditor); edSet {
|
||||
backupEd = ed
|
||||
}
|
||||
// try getting Dolt config core.editor
|
||||
editorStr := cliCtx.Config().GetStringOrDefault(config.DoltEditor, backupEd)
|
||||
|
||||
var rebaseMsg string
|
||||
cli.ExecuteWithStdioRestored(func() {
|
||||
rebaseMsg, err = editor.OpenTempEditor(editorStr, initialRebaseMsg)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return parseRebaseMessage(rebaseMsg)
|
||||
}
|
||||
|
||||
// buildInitialRebaseMsg builds the initial message to display to the user when they open the rebase plan editor,
|
||||
// including the formatted rebase plan.
|
||||
func buildInitialRebaseMsg(sqlCtx *sql.Context, queryist cli.Queryist, rebaseBranch, currentBranch string) (string, error) {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
rows, err := GetRowsForSql(queryist, sqlCtx, "SELECT action, commit_hash, commit_message FROM dolt_rebase ORDER BY rebase_order")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// rebase plan
|
||||
for _, row := range rows {
|
||||
action, found := getRebaseAction(row[0])
|
||||
if !found {
|
||||
return "", errors.New("invalid rebase action")
|
||||
}
|
||||
commitHash := row[1].(string)
|
||||
commitMessage := row[2].(string)
|
||||
buffer.WriteString(fmt.Sprintf("%s %s %s\n", action, commitHash, commitMessage))
|
||||
}
|
||||
buffer.WriteString("\n")
|
||||
|
||||
// help text
|
||||
rebaseBranchHash, err := getHashOf(queryist, sqlCtx, rebaseBranch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
currentBranchHash, err := getHashOf(queryist, sqlCtx, currentBranch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
numSteps := len(rows)
|
||||
buffer.WriteString(fmt.Sprintf("# Rebase %s..%s onto %s (%d commands)\n#\n", rebaseBranchHash, currentBranchHash, rebaseBranchHash, numSteps))
|
||||
|
||||
buffer.WriteString("# Commands:\n")
|
||||
buffer.WriteString("# p, pick <commit> = use commit\n")
|
||||
buffer.WriteString("# d, drop <commit> = remove commit\n")
|
||||
buffer.WriteString("# r, reword <commit> = use commit, but edit the commit message\n")
|
||||
buffer.WriteString("# s, squash <commit> = use commit, but meld into previous commit\n")
|
||||
buffer.WriteString("# f, fixup <commit> = like \"squash\", but discard this commit's message\n")
|
||||
buffer.WriteString("# These lines can be re-ordered; they are executed from top to bottom.\n")
|
||||
buffer.WriteString("#\n")
|
||||
buffer.WriteString("# If you remove a line here THAT COMMIT WILL BE LOST.\n")
|
||||
buffer.WriteString("#\n")
|
||||
buffer.WriteString("# However, if you remove everything, the rebase will be aborted.\n")
|
||||
buffer.WriteString("#\n")
|
||||
|
||||
return buffer.String(), nil
|
||||
}
|
||||
|
||||
// getRebaseAction returns the rebase action for the given row. This conversion is necessary because a local client
|
||||
// returns an int representing the enum whereas a remote client properly returns the label.
|
||||
// TODO: Remove this once the local client returns the label.
|
||||
func getRebaseAction(col interface{}) (string, bool) {
|
||||
action, ok := col.(string)
|
||||
if ok {
|
||||
return action, true
|
||||
} else {
|
||||
return dprocedures.RebaseActionEnumType.At(int(col.(uint16)))
|
||||
}
|
||||
}
|
||||
|
||||
// parseRebaseMessage parses the rebase message from the editor and adds all uncommented out lines as steps in the rebase plan.
|
||||
func parseRebaseMessage(rebaseMsg string) (*rebase.RebasePlan, error) {
|
||||
plan := &rebase.RebasePlan{}
|
||||
splitMsg := strings.Split(rebaseMsg, "\n")
|
||||
for i, line := range splitMsg {
|
||||
if !strings.HasPrefix(line, "#") && strings.TrimSpace(line) != "" {
|
||||
rebaseStepParts := strings.SplitN(line, " ", 3)
|
||||
if len(rebaseStepParts) != 3 {
|
||||
return nil, fmt.Errorf("invalid line %d: %s", i, line)
|
||||
}
|
||||
plan.Steps = append(plan.Steps, rebase.RebasePlanStep{
|
||||
Action: rebaseStepParts[0],
|
||||
CommitHash: rebaseStepParts[1],
|
||||
CommitMsg: rebaseStepParts[2],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// insertRebasePlanIntoDoltRebaseTable inserts the rebase plan into the dolt_rebase table by re-building the dolt_rebase
|
||||
// table from scratch.
|
||||
func insertRebasePlanIntoDoltRebaseTable(plan *rebase.RebasePlan, sqlCtx *sql.Context, queryist cli.Queryist) error {
|
||||
_, err := GetRowsForSql(queryist, sqlCtx, "TRUNCATE TABLE dolt_rebase")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, step := range plan.Steps {
|
||||
_, err := GetRowsForSql(queryist, sqlCtx, fmt.Sprintf("INSERT INTO dolt_rebase VALUES (%d, '%s', '%s', '%s')", i+1, step.Action, step.CommitHash, step.CommitMsg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -125,6 +125,7 @@ var doltSubCommands = []cli.Command{
|
||||
commands.ProfileCmd{},
|
||||
commands.QueryDiff{},
|
||||
commands.ReflogCmd{},
|
||||
commands.RebaseCmd{},
|
||||
}
|
||||
|
||||
var commandsWithoutCliCtx = []cli.Command{
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.22.0
|
||||
// protoc v4.26.0
|
||||
// source: dolt/services/eventsapi/v1alpha1/client_event.proto
|
||||
|
||||
package eventsapi
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.22.0
|
||||
// - protoc v4.26.0
|
||||
// source: dolt/services/eventsapi/v1alpha1/client_event.proto
|
||||
|
||||
package eventsapi
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.22.0
|
||||
// protoc v4.26.0
|
||||
// source: dolt/services/eventsapi/v1alpha1/event_constants.proto
|
||||
|
||||
package eventsapi
|
||||
@@ -157,6 +157,7 @@ const (
|
||||
ClientEventType_PROFILE ClientEventType = 62
|
||||
ClientEventType_REFLOG ClientEventType = 63
|
||||
ClientEventType_SQL_SERVER_HEARTBEAT ClientEventType = 64
|
||||
ClientEventType_REBASE ClientEventType = 65
|
||||
)
|
||||
|
||||
// Enum value maps for ClientEventType.
|
||||
@@ -227,6 +228,7 @@ var (
|
||||
62: "PROFILE",
|
||||
63: "REFLOG",
|
||||
64: "SQL_SERVER_HEARTBEAT",
|
||||
65: "REBASE",
|
||||
}
|
||||
ClientEventType_value = map[string]int32{
|
||||
"TYPE_UNSPECIFIED": 0,
|
||||
@@ -294,6 +296,7 @@ var (
|
||||
"PROFILE": 62,
|
||||
"REFLOG": 63,
|
||||
"SQL_SERVER_HEARTBEAT": 64,
|
||||
"REBASE": 65,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -484,7 +487,7 @@ var file_dolt_services_eventsapi_v1alpha1_event_constants_proto_rawDesc = []byte
|
||||
0x52, 0x4d, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
|
||||
0x12, 0x09, 0x0a, 0x05, 0x4c, 0x49, 0x4e, 0x55, 0x58, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x57,
|
||||
0x49, 0x4e, 0x44, 0x4f, 0x57, 0x53, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x41, 0x52, 0x57,
|
||||
0x49, 0x4e, 0x10, 0x03, 0x2a, 0xa3, 0x08, 0x0a, 0x0f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45,
|
||||
0x49, 0x4e, 0x10, 0x03, 0x2a, 0xaf, 0x08, 0x0a, 0x0f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x45,
|
||||
0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x59, 0x50, 0x45,
|
||||
0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x08,
|
||||
0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x41, 0x54,
|
||||
@@ -550,28 +553,29 @@ var file_dolt_services_eventsapi_v1alpha1_event_constants_proto_rawDesc = []byte
|
||||
0x04, 0x53, 0x48, 0x4f, 0x57, 0x10, 0x3d, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x4f, 0x46, 0x49,
|
||||
0x4c, 0x45, 0x10, 0x3e, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x46, 0x4c, 0x4f, 0x47, 0x10, 0x3f,
|
||||
0x12, 0x18, 0x0a, 0x14, 0x53, 0x51, 0x4c, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x45, 0x52, 0x5f, 0x48,
|
||||
0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x40, 0x2a, 0x6a, 0x0a, 0x08, 0x4d, 0x65,
|
||||
0x74, 0x72, 0x69, 0x63, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43,
|
||||
0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14,
|
||||
0x0a, 0x10, 0x42, 0x59, 0x54, 0x45, 0x53, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44,
|
||||
0x45, 0x44, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44,
|
||||
0x5f, 0x4d, 0x53, 0x5f, 0x45, 0x4c, 0x41, 0x50, 0x53, 0x45, 0x44, 0x10, 0x02, 0x12, 0x17, 0x0a,
|
||||
0x13, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x41, 0x50, 0x49, 0x5f, 0x52, 0x50, 0x43, 0x5f, 0x45,
|
||||
0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x2a, 0x45, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62,
|
||||
0x75, 0x74, 0x65, 0x49, 0x44, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, 0x55,
|
||||
0x54, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
|
||||
0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x55, 0x52, 0x4c, 0x5f, 0x53,
|
||||
0x43, 0x48, 0x45, 0x4d, 0x45, 0x10, 0x02, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x2a, 0x3f, 0x0a,
|
||||
0x05, 0x41, 0x70, 0x70, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x50, 0x50, 0x5f, 0x49, 0x44,
|
||||
0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c,
|
||||
0x0a, 0x08, 0x41, 0x50, 0x50, 0x5f, 0x44, 0x4f, 0x4c, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c,
|
||||
0x41, 0x50, 0x50, 0x5f, 0x44, 0x4f, 0x4c, 0x54, 0x47, 0x52, 0x45, 0x53, 0x10, 0x02, 0x42, 0x51,
|
||||
0x5a, 0x4f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x6c,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x65, 0x6e,
|
||||
0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x73, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x61, 0x70, 0x69, 0x2f, 0x76,
|
||||
0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x61, 0x70,
|
||||
0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x45, 0x41, 0x52, 0x54, 0x42, 0x45, 0x41, 0x54, 0x10, 0x40, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45,
|
||||
0x42, 0x41, 0x53, 0x45, 0x10, 0x41, 0x2a, 0x6a, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
|
||||
0x49, 0x44, 0x12, 0x16, 0x0a, 0x12, 0x4d, 0x45, 0x54, 0x52, 0x49, 0x43, 0x5f, 0x55, 0x4e, 0x53,
|
||||
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x42, 0x59,
|
||||
0x54, 0x45, 0x53, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x45, 0x44, 0x10, 0x01,
|
||||
0x12, 0x17, 0x0a, 0x13, 0x44, 0x4f, 0x57, 0x4e, 0x4c, 0x4f, 0x41, 0x44, 0x5f, 0x4d, 0x53, 0x5f,
|
||||
0x45, 0x4c, 0x41, 0x50, 0x53, 0x45, 0x44, 0x10, 0x02, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x4d,
|
||||
0x4f, 0x54, 0x45, 0x41, 0x50, 0x49, 0x5f, 0x52, 0x50, 0x43, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52,
|
||||
0x10, 0x03, 0x2a, 0x45, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x49,
|
||||
0x44, 0x12, 0x19, 0x0a, 0x15, 0x41, 0x54, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x45, 0x5f, 0x55,
|
||||
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11,
|
||||
0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x55, 0x52, 0x4c, 0x5f, 0x53, 0x43, 0x48, 0x45, 0x4d,
|
||||
0x45, 0x10, 0x02, 0x22, 0x04, 0x08, 0x01, 0x10, 0x01, 0x2a, 0x3f, 0x0a, 0x05, 0x41, 0x70, 0x70,
|
||||
0x49, 0x44, 0x12, 0x16, 0x0a, 0x12, 0x41, 0x50, 0x50, 0x5f, 0x49, 0x44, 0x5f, 0x55, 0x4e, 0x53,
|
||||
0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x50,
|
||||
0x50, 0x5f, 0x44, 0x4f, 0x4c, 0x54, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x41, 0x50, 0x50, 0x5f,
|
||||
0x44, 0x4f, 0x4c, 0x54, 0x47, 0x52, 0x45, 0x53, 0x10, 0x02, 0x42, 0x51, 0x5a, 0x4f, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x68, 0x75, 0x62,
|
||||
0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x2f, 0x67, 0x6f, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x2f, 0x64, 0x6f, 0x6c, 0x74, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73,
|
||||
0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70,
|
||||
0x68, 0x61, 0x31, 0x3b, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.22.0
|
||||
// protoc v4.26.0
|
||||
// source: dolt/services/remotesapi/v1alpha1/chunkstore.proto
|
||||
|
||||
package remotesapi
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.22.0
|
||||
// - protoc v4.26.0
|
||||
// source: dolt/services/remotesapi/v1alpha1/chunkstore.proto
|
||||
|
||||
package remotesapi
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.22.0
|
||||
// protoc v4.26.0
|
||||
// source: dolt/services/remotesapi/v1alpha1/credentials.proto
|
||||
|
||||
package remotesapi
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.22.0
|
||||
// - protoc v4.26.0
|
||||
// source: dolt/services/remotesapi/v1alpha1/credentials.proto
|
||||
|
||||
package remotesapi
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v4.22.0
|
||||
// protoc v4.26.0
|
||||
// source: dolt/services/replicationapi/v1alpha1/replication.proto
|
||||
|
||||
package replicationapi
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v4.22.0
|
||||
// - protoc v4.26.0
|
||||
// source: dolt/services/replicationapi/v1alpha1/replication.proto
|
||||
|
||||
package replicationapi
|
||||
|
||||
@@ -92,6 +92,12 @@ var ErrRebaseConflictWithAbortError = goerrors.NewKind(
|
||||
"merge conflict detected while rebasing commit %s. " +
|
||||
"attempted to abort rebase operation, but encountered error: %w")
|
||||
|
||||
// SuccessfulRebaseMessage is used when a rebase finishes successfully. The branch that was rebased should be appended
|
||||
// to the end of the message.
|
||||
var SuccessfulRebaseMessage = "Successfully rebased and updated refs/heads/"
|
||||
|
||||
var RebaseAbortedMessage = "Interactive rebase aborted"
|
||||
|
||||
func doltRebase(ctx *sql.Context, args ...string) (sql.RowIter, error) {
|
||||
res, message, err := doDoltRebase(ctx, args)
|
||||
if err != nil {
|
||||
@@ -116,15 +122,15 @@ func doDoltRebase(ctx *sql.Context, args []string) (int, string, error) {
|
||||
if err != nil {
|
||||
return 1, "", err
|
||||
} else {
|
||||
return 0, "interactive rebase aborted", nil
|
||||
return 0, RebaseAbortedMessage, nil
|
||||
}
|
||||
|
||||
case apr.Contains(cli.ContinueFlag):
|
||||
err := continueRebase(ctx)
|
||||
rebaseBranch, err := continueRebase(ctx)
|
||||
if err != nil {
|
||||
return 1, "", err
|
||||
} else {
|
||||
return 0, "interactive rebase completed", nil
|
||||
return 0, SuccessfulRebaseMessage + rebaseBranch, nil
|
||||
}
|
||||
|
||||
default:
|
||||
@@ -371,7 +377,7 @@ func abortRebase(ctx *sql.Context) error {
|
||||
return doltSession.SwitchWorkingSet(ctx, ctx.GetCurrentDatabase(), wsRef)
|
||||
}
|
||||
|
||||
func continueRebase(ctx *sql.Context) error {
|
||||
func continueRebase(ctx *sql.Context) (string, error) {
|
||||
// TODO: Eventually, when we allow interactive-rebases to be stopped and started (e.g. with the break action,
|
||||
// or for conflict resolution), we'll need to track what step we're at in the rebase plan.
|
||||
|
||||
@@ -379,46 +385,46 @@ func continueRebase(ctx *sql.Context) error {
|
||||
doltSession := dsess.DSessFromSess(ctx.Session)
|
||||
workingSet, err := doltSession.WorkingSet(ctx, ctx.GetCurrentDatabase())
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
if !workingSet.RebaseActive() {
|
||||
return fmt.Errorf("no rebase in progress")
|
||||
return "", fmt.Errorf("no rebase in progress")
|
||||
}
|
||||
|
||||
db, err := doltSession.Provider().Database(ctx, ctx.GetCurrentDatabase())
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
rdb, ok := db.(rebase.RebasePlanDatabase)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected a dsess.RebasePlanDatabase implementation, but received a %T", db)
|
||||
return "", fmt.Errorf("expected a dsess.RebasePlanDatabase implementation, but received a %T", db)
|
||||
}
|
||||
rebasePlan, err := rdb.LoadRebasePlan(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = rebase.ValidateRebasePlan(ctx, rebasePlan)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, step := range rebasePlan.Steps {
|
||||
err = processRebasePlanStep(ctx, &step)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Update the branch being rebased to point to the same commit as our temporary working branch
|
||||
rebaseBranchWorkingSet, err := doltSession.WorkingSet(ctx, ctx.GetCurrentDatabase())
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
dbData, ok := doltSession.GetDbData(ctx, ctx.GetCurrentDatabase())
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get db data for database %s", ctx.GetCurrentDatabase())
|
||||
return "", fmt.Errorf("unable to get db data for database %s", ctx.GetCurrentDatabase())
|
||||
}
|
||||
|
||||
rebaseBranch := rebaseBranchWorkingSet.RebaseState().Branch()
|
||||
@@ -427,7 +433,7 @@ func continueRebase(ctx *sql.Context) error {
|
||||
// Check that the branch being rebased hasn't been updated since the rebase started
|
||||
err = validateRebaseBranchHasntChanged(ctx, rebaseBranch, rebaseBranchWorkingSet.RebaseState())
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// TODO: copyABranch (and the underlying call to doltdb.NewBranchAtCommit) has a race condition
|
||||
@@ -438,25 +444,25 @@ func continueRebase(ctx *sql.Context) error {
|
||||
// database.CommitWithWorkingSet, since it updates a branch head and working set atomically.
|
||||
err = copyABranch(ctx, dbData, rebaseWorkingBranch, rebaseBranch, true, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Checkout the branch being rebased
|
||||
previousBranchWorkingSetRef, err := ref.WorkingSetRefForHead(ref.NewBranchRef(rebaseBranchWorkingSet.RebaseState().Branch()))
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
err = doltSession.SwitchWorkingSet(ctx, ctx.GetCurrentDatabase(), previousBranchWorkingSetRef)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// delete the temporary working branch
|
||||
dbData, ok = doltSession.GetDbData(ctx, ctx.GetCurrentDatabase())
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to lookup dbdata")
|
||||
return "", fmt.Errorf("unable to lookup dbdata")
|
||||
}
|
||||
return actions.DeleteBranch(ctx, dbData, rebaseWorkingBranch, actions.DeleteOptions{
|
||||
return rebaseBranch, actions.DeleteBranch(ctx, dbData, rebaseWorkingBranch, actions.DeleteOptions{
|
||||
Force: true,
|
||||
}, doltSession.Provider(), nil)
|
||||
}
|
||||
|
||||
@@ -321,7 +321,7 @@ var DoltRebaseScriptTests = []queries.ScriptTest{
|
||||
},
|
||||
{
|
||||
Query: "call dolt_rebase('--abort');",
|
||||
Expected: []sql.Row{{0, "interactive rebase aborted"}},
|
||||
Expected: []sql.Row{{0, "Interactive rebase aborted"}},
|
||||
},
|
||||
{
|
||||
Query: "select active_branch();",
|
||||
@@ -412,7 +412,7 @@ var DoltRebaseScriptTests = []queries.ScriptTest{
|
||||
},
|
||||
{
|
||||
Query: "call dolt_rebase('--continue');",
|
||||
Expected: []sql.Row{{0, "interactive rebase completed"}},
|
||||
Expected: []sql.Row{{0, "Successfully rebased and updated refs/heads/branch1"}},
|
||||
},
|
||||
{
|
||||
// When rebase completes, rebase status should be cleared
|
||||
@@ -627,7 +627,7 @@ var DoltRebaseScriptTests = []queries.ScriptTest{
|
||||
},
|
||||
{
|
||||
Query: "call dolt_rebase('--continue');",
|
||||
Expected: []sql.Row{{0, "interactive rebase completed"}},
|
||||
Expected: []sql.Row{{0, "Successfully rebased and updated refs/heads/branch1"}},
|
||||
},
|
||||
{
|
||||
Query: "select message from dolt_log;",
|
||||
@@ -694,7 +694,7 @@ var DoltRebaseScriptTests = []queries.ScriptTest{
|
||||
},
|
||||
{
|
||||
Query: "call dolt_rebase('--continue');",
|
||||
Expected: []sql.Row{{0, "interactive rebase completed"}},
|
||||
Expected: []sql.Row{{0, "Successfully rebased and updated refs/heads/branch1"}},
|
||||
},
|
||||
{
|
||||
Query: "select message from dolt_log;",
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// OpenCommitEditor allows user to write/edit commit message in temporary file
|
||||
func OpenCommitEditor(ed string, initialContents string) (string, error) {
|
||||
// OpenTempEditor allows user to write/edit message in temporary file
|
||||
func OpenTempEditor(ed string, initialContents string) (string, error) {
|
||||
filename := filepath.Join(os.TempDir(), uuid.New().String())
|
||||
err := os.WriteFile(filename, []byte(initialContents), os.ModePerm)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestOpenCommitEditor(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
val, err := OpenCommitEditor(test.editorStr, test.initialContents)
|
||||
val, err := OpenTempEditor(test.editorStr, test.initialContents)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
|
||||
@@ -136,6 +136,7 @@ SKIP_SERVER_TESTS=$(cat <<-EOM
|
||||
~cli-hosted.bats~
|
||||
~profile.bats~
|
||||
~ls.bats~
|
||||
~rebase.bats~
|
||||
EOM
|
||||
)
|
||||
|
||||
|
||||
382
integration-tests/bats/rebase.bats
Executable file
382
integration-tests/bats/rebase.bats
Executable file
@@ -0,0 +1,382 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
|
||||
setup() {
|
||||
setup_common
|
||||
dolt sql -q "CREATE table t1 (pk int primary key, c int);"
|
||||
dolt add t1
|
||||
dolt commit -m "main commit 1"
|
||||
dolt branch b1
|
||||
dolt sql -q "INSERT INTO t1 VALUES (1,1);"
|
||||
dolt add t1
|
||||
dolt commit -m "main commit 2"
|
||||
|
||||
dolt checkout b1
|
||||
dolt sql -q "CREATE table t2 (pk int primary key);"
|
||||
dolt add t2
|
||||
dolt commit -m "b1 commit 1"
|
||||
|
||||
dolt checkout main
|
||||
}
|
||||
|
||||
teardown() {
|
||||
assert_feature_version
|
||||
teardown_common
|
||||
}
|
||||
|
||||
setupCustomEditorScript() {
|
||||
touch rebaseScript.sh
|
||||
echo "#!/bin/bash" >> rebaseScript.sh
|
||||
if [ $# -eq 1 ]; then
|
||||
echo "mv $1 \$1" >> rebaseScript.sh
|
||||
fi
|
||||
chmod +x rebaseScript.sh
|
||||
export EDITOR=$PWD/rebaseScript.sh
|
||||
export DOLT_TEST_FORCE_OPEN_EDITOR="1"
|
||||
}
|
||||
|
||||
@test "rebase: no rebase in progress errors" {
|
||||
run dolt rebase --abort
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "no rebase in progress" ]] || false
|
||||
|
||||
run dolt rebase --continue
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "no rebase in progress" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: -i flag required" {
|
||||
run dolt rebase b1
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "non-interactive rebases not currently supported" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: bad args" {
|
||||
run dolt rebase -i
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "not enough args" ]] || false
|
||||
|
||||
run dolt rebase -i main b1
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "rebase takes at most one positional argument" ]] || false
|
||||
|
||||
run dolt rebase --abrot
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "error: unknown option \`abrot'" ]] || false
|
||||
|
||||
run dolt rebase -i foo
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "branch not found: foo" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: cannot rebase with dirty working set" {
|
||||
dolt sql -q "INSERT INTO t1 VALUES (2,2);"
|
||||
run dolt rebase -i b1
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "cannot start a rebase with uncommitted changes" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: cannot rebase during active merge" {
|
||||
dolt checkout b1
|
||||
dolt sql -q "INSERT INTO t1 VALUES (1,2);"
|
||||
dolt add t1
|
||||
dolt commit -m "b1 commit 2"
|
||||
|
||||
run dolt merge main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "Automatic merge failed" ]] || false
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "unable to start rebase while a merge is in progress – abort the current merge before proceeding" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: rebase working branch already exists" {
|
||||
dolt checkout b1
|
||||
dolt branch dolt_rebase_b1
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "fatal: A branch named 'dolt_rebase_b1' already exists." ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: verify custom script" {
|
||||
setupCustomEditorScript "rebasePlan.txt"
|
||||
|
||||
dolt checkout b1
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT1=${lines[0]:12:32}
|
||||
|
||||
touch rebasePlan.txt
|
||||
echo "pick $COMMIT1 b1 commit 1" >> rebasePlan.txt
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b1" ]] || false
|
||||
|
||||
run dolt log
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "b1 commit 1" ]] || false
|
||||
[[ "$output" =~ "main commit 2" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: basic rebase" {
|
||||
setupCustomEditorScript
|
||||
|
||||
dolt checkout b1
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b1" ]] || false
|
||||
|
||||
run dolt log
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "main commit 2" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: failed rebase will abort and clean up" {
|
||||
setupCustomEditorScript "invalidRebasePlan.txt"
|
||||
dolt checkout b1
|
||||
|
||||
touch invalidRebasePlan.txt
|
||||
echo "foo" >> invalidRebasePlan.txt
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "invalid line 0: foo" ]] || false
|
||||
|
||||
run dolt branch
|
||||
[ "$status" -eq 0 ]
|
||||
! [[ "$output" =~ "dolt_rebase_b1" ]] || false
|
||||
|
||||
run dolt rebase --continue
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "no rebase in progress" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: invalid rebase plan" {
|
||||
setupCustomEditorScript "invalidRebasePlan.txt"
|
||||
|
||||
dolt checkout b1
|
||||
|
||||
touch invalidRebasePlan.txt
|
||||
echo "foo" >> invalidRebasePlan.txt
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "invalid line 0: foo" ]] || false
|
||||
|
||||
touch invalidRebasePlan.txt
|
||||
echo "pick foo main commit 1" >> invalidRebasePlan.txt
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "invalid commit hash: foo" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: empty rebase plan aborts the rebase" {
|
||||
setupCustomEditorScript "emptyRebasePlan.txt"
|
||||
touch emptyRebasePlan.txt
|
||||
echo "# " >> emptyRebasePlan.txt
|
||||
echo "# commented out lines don't count" >> emptyRebasePlan.txt
|
||||
|
||||
dolt checkout b1
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "rebase aborted" ]] || false
|
||||
|
||||
run dolt log
|
||||
[ "$status" -eq 0 ]
|
||||
! [[ "$output" =~ "main commit 2" ]] || false
|
||||
|
||||
run dolt rebase --continue
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "no rebase in progress" ]] || false
|
||||
|
||||
run dolt branch
|
||||
[ "$status" -eq 0 ]
|
||||
! [[ "$output" =~ "dolt_rebase_b1" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: multi step rebase" {
|
||||
setupCustomEditorScript "multiStepPlan.txt"
|
||||
|
||||
dolt checkout b1
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT1=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t2 values (1);"
|
||||
dolt commit -am "b1 commit 2"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT2=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t2 values (2);"
|
||||
dolt commit -am "b1 commit 3"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT3=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t2 values (3);"
|
||||
dolt commit -am "b1 commit 4"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT4=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t2 values (4);"
|
||||
dolt commit -am "b1 commit 5"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT5=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t2 values (5);"
|
||||
dolt commit -am "b1 commit 6"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT6=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t2 values (6);"
|
||||
dolt commit -am "b1 commit 7"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT7=${lines[0]:12:32}
|
||||
|
||||
touch multiStepPlan.txt
|
||||
echo "pick $COMMIT1 b1 commit 1" >> multiStepPlan.txt
|
||||
echo "squash $COMMIT2 b1 commit 2" >> multiStepPlan.txt
|
||||
echo "squash $COMMIT3 b1 commit 3" >> multiStepPlan.txt
|
||||
echo "drop $COMMIT4 b1 commit 4" >> multiStepPlan.txt
|
||||
echo "reword $COMMIT5 reworded!" >> multiStepPlan.txt
|
||||
echo "fixup $COMMIT6 b1 commit 6" >> multiStepPlan.txt
|
||||
echo "pick $COMMIT7 b1 commit 7" >> multiStepPlan.txt
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b1" ]] || false
|
||||
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "b1 commit 7" ]] || false
|
||||
|
||||
run dolt show head~1
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "reworded!" ]] || false
|
||||
|
||||
run dolt show head~2
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "b1 commit 1" ]] || false
|
||||
[[ "$output" =~ "b1 commit 2" ]] || false
|
||||
[[ "$output" =~ "b1 commit 3" ]] || false
|
||||
|
||||
run dolt show head~3
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "main commit 2" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: non-standard plan changes" {
|
||||
setupCustomEditorScript "nonStandardPlan.txt"
|
||||
|
||||
dolt checkout -b b2
|
||||
dolt sql -q "CREATE table t3 (pk int primary key);"
|
||||
dolt add t3
|
||||
dolt commit -m "b2 commit 1"
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT1=${lines[0]:12:32}
|
||||
|
||||
dolt sql -q "insert into t3 values (1);"
|
||||
dolt commit -am "b2 commit 2"
|
||||
dolt sql -q "insert into t3 values (2);"
|
||||
dolt commit -am "b2 commit 3"
|
||||
|
||||
dolt checkout b1
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
COMMIT2=${lines[0]:12:32}
|
||||
|
||||
touch nonStandardPlan.txt
|
||||
echo "pick $COMMIT1 b2 commit 1" >> nonStandardPlan.txt
|
||||
echo "pick $COMMIT2 b1 commit 1" >> nonStandardPlan.txt
|
||||
|
||||
dolt checkout b2
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b2" ]] || false
|
||||
|
||||
run dolt show head
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "b1 commit 1" ]] || false
|
||||
|
||||
run dolt show head~1
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "b2 commit 1" ]] || false
|
||||
|
||||
run dolt show head~2
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "main commit 2" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: rebase skips merge commits" {
|
||||
setupCustomEditorScript
|
||||
|
||||
dolt checkout b1
|
||||
dolt merge main -m "b1 merge commit"
|
||||
dolt sql -q "insert into t2 values (1);"
|
||||
dolt commit -am "b1 commit 2"
|
||||
|
||||
dolt checkout main
|
||||
dolt sql -q "insert into t1 values (2,2);"
|
||||
dolt commit -am "main commit 3"
|
||||
dolt checkout b1
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b1" ]] || false
|
||||
|
||||
run dolt log
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "b1 commit 2" ]] || false
|
||||
[[ "$output" =~ "b1 commit 1" ]] || false
|
||||
[[ "$output" =~ "main commit 3" ]] || false
|
||||
! [[ "$output" =~ "b1 merge commit" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: rebase with data conflicts aborts" {
|
||||
setupCustomEditorScript
|
||||
|
||||
dolt checkout b1
|
||||
dolt sql -q "INSERT INTO t1 VALUES (1,2);"
|
||||
dolt commit -am "b1 commit 2"
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "merge conflict detected while rebasing commit" ]] || false
|
||||
[[ "$output" =~ "the rebase has been automatically aborted" ]] || false
|
||||
|
||||
run dolt rebase --continue
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "no rebase in progress" ]] || false
|
||||
|
||||
run dolt branch
|
||||
[ "$status" -eq 0 ]
|
||||
! [[ "$output" =~ "dolt_rebase_b1" ]] || false
|
||||
}
|
||||
|
||||
@test "rebase: rebase with schema conflicts aborts" {
|
||||
setupCustomEditorScript
|
||||
|
||||
dolt checkout b1
|
||||
dolt sql -q "ALTER TABLE t1 MODIFY COLUMN c varchar(100);"
|
||||
dolt commit -am "b1 commit 2"
|
||||
|
||||
run dolt rebase -i main
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "merge conflict detected while rebasing commit" ]] || false
|
||||
[[ "$output" =~ "the rebase has been automatically aborted" ]] || false
|
||||
|
||||
run dolt rebase --continue
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "no rebase in progress" ]] || false
|
||||
|
||||
run dolt branch
|
||||
[ "$status" -eq 0 ]
|
||||
! [[ "$output" =~ "dolt_rebase_b1" ]] || false
|
||||
}
|
||||
@@ -1338,3 +1338,51 @@ SQL
|
||||
[ "$status" -eq "0" ]
|
||||
[[ "$output" =~ "5" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-local-remote: verify dolt rebase behavior" {
|
||||
cd altDB
|
||||
|
||||
dolt sql -q "drop table dolt_ignore;"
|
||||
dolt add .
|
||||
|
||||
dolt branch b1
|
||||
dolt commit -m "main commit 2"
|
||||
dolt checkout b1
|
||||
dolt sql -q "create table t2 (pk int primary key)"
|
||||
dolt add .
|
||||
dolt commit -m "b1 commit 1"
|
||||
|
||||
touch rebaseScript.sh
|
||||
echo "#!/bin/bash" >> rebaseScript.sh
|
||||
chmod +x rebaseScript.sh
|
||||
export EDITOR=$PWD/rebaseScript.sh
|
||||
export DOLT_TEST_FORCE_OPEN_EDITOR="1"
|
||||
|
||||
run dolt --verbose-engine-setup rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "starting local mode" ]] || false
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b1" ]] || false
|
||||
|
||||
run dolt log
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "main commit 2" ]] || false
|
||||
[[ "$output" =~ "b1 commit 1" ]] || false
|
||||
|
||||
dolt checkout main
|
||||
dolt sql -q "create table t3 (pk int primary key)"
|
||||
dolt add .
|
||||
dolt commit -m "main commit 3"
|
||||
dolt checkout b1
|
||||
|
||||
start_sql_server altDB
|
||||
run dolt --verbose-engine-setup rebase -i main
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "starting remote mode" ]] || false
|
||||
[[ "$output" =~ "Successfully rebased and updated refs/heads/b1" ]] || false
|
||||
|
||||
run dolt log
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "main commit 3" ]] || false
|
||||
[[ "$output" =~ "main commit 2" ]] || false
|
||||
[[ "$output" =~ "b1 commit 1" ]] || false
|
||||
}
|
||||
|
||||
@@ -94,6 +94,7 @@ enum ClientEventType {
|
||||
PROFILE = 62;
|
||||
REFLOG = 63;
|
||||
SQL_SERVER_HEARTBEAT = 64;
|
||||
REBASE = 65;
|
||||
}
|
||||
|
||||
enum MetricID {
|
||||
|
||||
Reference in New Issue
Block a user