mirror of
https://github.com/dolthub/dolt.git
synced 2026-04-24 03:16:12 -05:00
Merge pull request #3745 from dolthub/zachmu/clone
dolt_clone stored procedure
This commit is contained in:
@@ -95,6 +95,8 @@ const (
|
||||
DeleteFlag = "delete"
|
||||
DeleteForceFlag = "D"
|
||||
OutputOnlyFlag = "output-only"
|
||||
RemoteParam = "remote"
|
||||
BranchParam = "branch"
|
||||
TrackFlag = "track"
|
||||
)
|
||||
|
||||
@@ -147,6 +149,17 @@ func CreateAddArgParser() *argparser.ArgParser {
|
||||
return ap
|
||||
}
|
||||
|
||||
func CreateCloneArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsString(RemoteParam, "", "name", "Name of the remote to be added to the cloned database. The default is 'origin'.")
|
||||
ap.SupportsString(BranchParam, "b", "branch", "The branch to be cloned. If not specified all branches will be cloned.")
|
||||
ap.SupportsString(dbfactory.AWSRegionParam, "", "region", "")
|
||||
ap.SupportsValidatedString(dbfactory.AWSCredsTypeParam, "", "creds-type", "", argparser.ValidatorFromStrList(dbfactory.AWSCredsTypeParam, dbfactory.AWSCredTypes))
|
||||
ap.SupportsString(dbfactory.AWSCredsFileParam, "", "file", "AWS credentials file.")
|
||||
ap.SupportsString(dbfactory.AWSCredsProfile, "", "profile", "AWS profile to use.")
|
||||
return ap
|
||||
}
|
||||
|
||||
func CreateResetArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsFlag(HardResetParam, "", "Resets the working tables and staged tables. Any changes to tracked tables in the working tree since {{.LessThan}}commit{{.GreaterThan}} are discarded.")
|
||||
|
||||
@@ -17,7 +17,6 @@ package commands
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
|
||||
@@ -314,29 +313,29 @@ func restoreBackup(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPar
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
// make .dolt dir whith env.NoRemote to avoid origin upstream
|
||||
dEnv, err = actions.EnvForClone(ctx, srcDb.ValueReadWriter().Format(), env.NoRemote, dir, dEnv.FS, dEnv.Version, env.GetCurrentUserHomeDir)
|
||||
// Create a new Dolt env for the clone; use env.NoRemote to avoid origin upstream
|
||||
clonedEnv, err := actions.EnvForClone(ctx, srcDb.ValueReadWriter().Format(), env.NoRemote, dir, dEnv.FS, dEnv.Version, env.GetCurrentUserHomeDir)
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
// Nil out the old Dolt env so we don't accidentally use the wrong database
|
||||
dEnv = nil
|
||||
|
||||
// still make empty repo state
|
||||
_, err = env.CreateRepoState(dEnv.FS, env.DefaultInitBranch)
|
||||
_, err = env.CreateRepoState(clonedEnv.FS, env.DefaultInitBranch)
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
err = actions.SyncRoots(ctx, srcDb, dEnv.DoltDB, dEnv.TempTableFilesDir(), buildProgStarter(downloadLanguage), stopProgFuncs)
|
||||
err = actions.SyncRoots(ctx, srcDb, clonedEnv.DoltDB, clonedEnv.TempTableFilesDir(), buildProgStarter(downloadLanguage), stopProgFuncs)
|
||||
if err != nil {
|
||||
// If we're cloning into a directory that already exists do not erase it. Otherwise
|
||||
// make best effort to delete the directory we created.
|
||||
if userDirExists {
|
||||
// Set the working dir to the parent of the .dolt folder so we can delete .dolt
|
||||
_ = os.Chdir(dir)
|
||||
_ = dEnv.FS.Delete(dbfactory.DoltDir, true)
|
||||
_ = clonedEnv.FS.Delete(dbfactory.DoltDir, true)
|
||||
} else {
|
||||
_ = os.Chdir("../")
|
||||
_ = dEnv.FS.Delete(dir, true)
|
||||
_ = clonedEnv.FS.Delete(".", true)
|
||||
}
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ func printBranches(ctx context.Context, dEnv *env.DoltEnv, apr *argparser.ArgPar
|
||||
branchSet := set.NewStrSet(apr.Args)
|
||||
|
||||
verbose := apr.Contains(verboseFlag)
|
||||
printRemote := apr.Contains(remoteParam)
|
||||
printRemote := apr.Contains(cli.RemoteParam)
|
||||
printAll := apr.Contains(allFlag)
|
||||
|
||||
branches, err := dEnv.DoltDB.GetHeadRefs(ctx)
|
||||
|
||||
@@ -16,7 +16,6 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
||||
@@ -33,11 +32,6 @@ import (
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
)
|
||||
|
||||
const (
|
||||
remoteParam = "remote"
|
||||
branchParam = "branch"
|
||||
)
|
||||
|
||||
var cloneDocs = cli.CommandDocumentationContent{
|
||||
ShortDesc: "Clone a data repository into a new directory",
|
||||
LongDesc: `Clones a repository into a newly created directory, creates remote-tracking branches for each branch in the cloned repository (visible using {{.LessThan}}dolt branch -a{{.GreaterThan}}), and creates and checks out an initial branch that is forked from the cloned repository's currently active branch.
|
||||
@@ -75,14 +69,7 @@ func (cmd CloneCmd) Docs() *cli.CommandDocumentation {
|
||||
}
|
||||
|
||||
func (cmd CloneCmd) ArgParser() *argparser.ArgParser {
|
||||
ap := argparser.NewArgParser()
|
||||
ap.SupportsString(remoteParam, "", "name", "Name of the remote to be added. Default will be 'origin'.")
|
||||
ap.SupportsString(branchParam, "b", "branch", "The branch to be cloned. If not specified all branches will be cloned.")
|
||||
ap.SupportsString(dbfactory.AWSRegionParam, "", "region", "")
|
||||
ap.SupportsValidatedString(dbfactory.AWSCredsTypeParam, "", "creds-type", "", argparser.ValidatorFromStrList(dbfactory.AWSCredsTypeParam, credTypes))
|
||||
ap.SupportsString(dbfactory.AWSCredsFileParam, "", "file", "AWS credentials file.")
|
||||
ap.SupportsString(dbfactory.AWSCredsProfile, "", "profile", "AWS profile to use.")
|
||||
return ap
|
||||
return cli.CreateCloneArgParser()
|
||||
}
|
||||
|
||||
// EventType returns the type of the event to log
|
||||
@@ -105,8 +92,8 @@ func (cmd CloneCmd) Exec(ctx context.Context, commandStr string, args []string,
|
||||
}
|
||||
|
||||
func clone(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEnv) errhand.VerboseError {
|
||||
remoteName := apr.GetValueOrDefault(remoteParam, "origin")
|
||||
branch := apr.GetValueOrDefault(branchParam, "")
|
||||
remoteName := apr.GetValueOrDefault(cli.RemoteParam, "origin")
|
||||
branch := apr.GetValueOrDefault(cli.BranchParam, "")
|
||||
dir, urlStr, verr := parseArgs(apr)
|
||||
if verr != nil {
|
||||
return verr
|
||||
@@ -132,22 +119,23 @@ func clone(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEn
|
||||
return verr
|
||||
}
|
||||
|
||||
dEnv, err = actions.EnvForClone(ctx, srcDB.ValueReadWriter().Format(), r, dir, dEnv.FS, dEnv.Version, env.GetCurrentUserHomeDir)
|
||||
// Create a new Dolt env for the clone
|
||||
clonedEnv, err := actions.EnvForClone(ctx, srcDB.ValueReadWriter().Format(), r, dir, dEnv.FS, dEnv.Version, env.GetCurrentUserHomeDir)
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
err = actions.CloneRemote(ctx, srcDB, remoteName, branch, dEnv)
|
||||
// Nil out the old Dolt env so we don't accidentally operate on the wrong database
|
||||
dEnv = nil
|
||||
|
||||
err = actions.CloneRemote(ctx, srcDB, remoteName, branch, clonedEnv)
|
||||
if err != nil {
|
||||
// If we're cloning into a directory that already exists do not erase it. Otherwise
|
||||
// make best effort to delete the directory we created.
|
||||
if userDirExists {
|
||||
// Set the working dir to the parent of the .dolt folder so we can delete .dolt
|
||||
_ = os.Chdir(dir)
|
||||
_ = dEnv.FS.Delete(dbfactory.DoltDir, true)
|
||||
clonedEnv.FS.Delete(dbfactory.DoltDir, true)
|
||||
} else {
|
||||
_ = os.Chdir("../")
|
||||
_ = dEnv.FS.Delete(dir, true)
|
||||
clonedEnv.FS.Delete(".", true)
|
||||
}
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
@@ -160,15 +148,15 @@ func clone(ctx context.Context, apr *argparser.ArgParseResults, dEnv *env.DoltEn
|
||||
}
|
||||
}
|
||||
|
||||
err = dEnv.RepoStateWriter().UpdateBranch(dEnv.RepoState.CWBHeadRef().GetPath(), env.BranchConfig{
|
||||
Merge: dEnv.RepoState.Head,
|
||||
err = clonedEnv.RepoStateWriter().UpdateBranch(clonedEnv.RepoState.CWBHeadRef().GetPath(), env.BranchConfig{
|
||||
Merge: clonedEnv.RepoState.Head,
|
||||
Remote: remoteName,
|
||||
})
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
err = dEnv.RepoState.Save(dEnv.FS)
|
||||
err = clonedEnv.RepoState.Save(clonedEnv.FS)
|
||||
if err != nil {
|
||||
return errhand.VerboseErrorFromError(err)
|
||||
}
|
||||
|
||||
@@ -67,8 +67,6 @@ const (
|
||||
removeRemoteShortId = "rm"
|
||||
)
|
||||
|
||||
var credTypes = dbfactory.AWSCredTypes
|
||||
|
||||
type RemoteCmd struct{}
|
||||
|
||||
// Name is returns the name of the Dolt cli command. This is what is used on the command line to invoke the command
|
||||
@@ -93,7 +91,7 @@ func (cmd RemoteCmd) ArgParser() *argparser.ArgParser {
|
||||
ap.ArgListHelp = append(ap.ArgListHelp, [2]string{"profile", "AWS profile to use."})
|
||||
ap.SupportsFlag(verboseFlag, "v", "When printing the list of remotes adds additional details.")
|
||||
ap.SupportsString(dbfactory.AWSRegionParam, "", "region", "")
|
||||
ap.SupportsValidatedString(dbfactory.AWSCredsTypeParam, "", "creds-type", "", argparser.ValidatorFromStrList(dbfactory.AWSCredsTypeParam, credTypes))
|
||||
ap.SupportsValidatedString(dbfactory.AWSCredsTypeParam, "", "creds-type", "", argparser.ValidatorFromStrList(dbfactory.AWSCredsTypeParam, dbfactory.AWSCredTypes))
|
||||
ap.SupportsString(dbfactory.AWSCredsFileParam, "", "file", "AWS credentials file")
|
||||
ap.SupportsString(dbfactory.AWSCredsProfile, "", "profile", "AWS profile to use")
|
||||
return ap
|
||||
|
||||
+3
-7
@@ -18,7 +18,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -56,6 +55,7 @@ var ErrUserNotFound = errors.New("could not determine user name. run dolt config
|
||||
var ErrEmailNotFound = errors.New("could not determine email. run dolt config --global --add user.email")
|
||||
var ErrCloneFailed = errors.New("clone failed")
|
||||
|
||||
// EnvForClone creates a new DoltEnv and configures it with repo state from the specified remote. The returned DoltEnv is ready for content to be cloned into it. The directory used for the new DoltEnv is determined by resolving the specified dir against the specified Filesys.
|
||||
func EnvForClone(ctx context.Context, nbf *types.NomsBinFormat, r env.Remote, dir string, fs filesys.Filesys, version string, homeProvider env.HomeDirProvider) (*env.DoltEnv, error) {
|
||||
exists, _ := fs.Exists(filepath.Join(dir, dbfactory.DoltDir))
|
||||
|
||||
@@ -64,20 +64,17 @@ func EnvForClone(ctx context.Context, nbf *types.NomsBinFormat, r env.Remote, di
|
||||
}
|
||||
|
||||
err := fs.MkDirs(dir)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s; %s", ErrFailedToCreateDirectory, dir, err.Error())
|
||||
}
|
||||
|
||||
err = os.Chdir(dir)
|
||||
|
||||
newFs, err := fs.WithWorkingDir(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s; %s", ErrFailedToAccessDir, dir, err.Error())
|
||||
}
|
||||
|
||||
dEnv := env.Load(ctx, homeProvider, fs, doltdb.LocalDirDoltDB, version)
|
||||
dEnv := env.Load(ctx, homeProvider, newFs, doltdb.LocalDirDoltDB, version)
|
||||
err = dEnv.InitRepoWithNoData(ctx, nbf)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w; %s", ErrFailedToInitRepo, err.Error())
|
||||
}
|
||||
@@ -85,7 +82,6 @@ func EnvForClone(ctx context.Context, nbf *types.NomsBinFormat, r env.Remote, di
|
||||
dEnv.RSLoadErr = nil
|
||||
if !env.IsEmptyRemote(r) {
|
||||
dEnv.RepoState, err = env.CloneRepoState(dEnv.FS, r)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %s; %s", ErrFailedToCreateRepoStateWithRemote, r.Name, err.Error())
|
||||
}
|
||||
|
||||
+2
-2
@@ -98,7 +98,7 @@ type DoltEnv struct {
|
||||
IgnoreLockFile bool
|
||||
}
|
||||
|
||||
// Load loads the DoltEnv for the current directory of the cli
|
||||
// Load loads the DoltEnv for the .dolt directory determined by resolving the specified urlStr with the specified Filesys.
|
||||
func Load(ctx context.Context, hdp HomeDirProvider, fs filesys.Filesys, urlStr, version string) *DoltEnv {
|
||||
config, cfgErr := LoadDoltCliConfig(hdp, fs)
|
||||
repoState, rsErr := LoadRepoState(fs)
|
||||
@@ -361,7 +361,7 @@ func (dEnv *DoltEnv) InitRepoWithNoData(ctx context.Context, nbf *types.NomsBinF
|
||||
return err
|
||||
}
|
||||
|
||||
dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr, filesys.LocalFS)
|
||||
dEnv.DoltDB, err = doltdb.LoadDoltDB(ctx, nbf, dEnv.urlStr, dEnv.FS)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -22,14 +22,17 @@ import (
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
|
||||
"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/ref"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/remotestorage"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dfunctions"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/table/editor"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/earl"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/filesys"
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
)
|
||||
@@ -212,6 +215,87 @@ func (p DoltDatabaseProvider) CreateDatabase(ctx *sql.Context, name string) erro
|
||||
return dsess.AddDB(ctx, dbstate)
|
||||
}
|
||||
|
||||
// CloneDatabaseFromRemote implements DoltDatabaseProvider interface
|
||||
func (p DoltDatabaseProvider) CloneDatabaseFromRemote(ctx *sql.Context, dbName, branch, remoteName, remoteUrl string, remoteParams map[string]string) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
exists, isDir := p.fs.Exists(dbName)
|
||||
if exists && isDir {
|
||||
return sql.ErrDatabaseExists.New(dbName)
|
||||
} else if exists {
|
||||
return fmt.Errorf("cannot create DB, file exists at %s", dbName)
|
||||
}
|
||||
|
||||
var r env.Remote
|
||||
var srcDB *doltdb.DoltDB
|
||||
dialer := p.remoteDialer
|
||||
if dialer == nil {
|
||||
return fmt.Errorf("unable to clone remote database; no remote dialer configured")
|
||||
}
|
||||
r, srcDB, err := createRemote(ctx, remoteName, remoteUrl, remoteParams, dialer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dEnv, err := actions.EnvForClone(ctx, srcDB.ValueReadWriter().Format(), r, dbName, p.fs, "VERSION", env.GetCurrentUserHomeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = actions.CloneRemote(ctx, srcDB, remoteName, branch, dEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dEnv.RepoStateWriter().UpdateBranch(dEnv.RepoState.CWBHeadRef().GetPath(), env.BranchConfig{
|
||||
Merge: dEnv.RepoState.Head,
|
||||
Remote: remoteName,
|
||||
})
|
||||
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
fkChecks, err := ctx.GetSessionVariable(ctx, "foreign_key_checks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := editor.Options{
|
||||
Deaf: dEnv.DbEaFactory(),
|
||||
// TODO: this doesn't seem right, why is this getting set in the constructor to the DB
|
||||
ForeignKeyChecksDisabled: fkChecks.(int8) == 0,
|
||||
}
|
||||
|
||||
db := NewDatabase(dbName, dEnv.DbData(), opts)
|
||||
p.databases[formatDbMapKeyName(db.Name())] = db
|
||||
|
||||
dbstate, err := GetInitialDBState(ctx, db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.AddDB(ctx, dbstate)
|
||||
}
|
||||
|
||||
// TODO: extract a shared library for this functionality
|
||||
func createRemote(ctx *sql.Context, remoteName, remoteUrl string, params map[string]string, dialer dbfactory.GRPCDialProvider) (env.Remote, *doltdb.DoltDB, error) {
|
||||
r := env.NewRemote(remoteName, remoteUrl, params)
|
||||
|
||||
ddb, err := r.GetRemoteDB(ctx, types.Format_Default, dialer)
|
||||
|
||||
if err != nil {
|
||||
bdr := errhand.BuildDError("error: failed to get remote db").AddCause(err)
|
||||
|
||||
if err == remotestorage.ErrInvalidDoltSpecPath {
|
||||
urlObj, _ := earl.Parse(remoteUrl)
|
||||
bdr.AddDetails("'%s' should be in the format 'organization/repo'", urlObj.Path)
|
||||
}
|
||||
|
||||
return env.NoRemote, nil, bdr.Build()
|
||||
}
|
||||
|
||||
return r, ddb, nil
|
||||
}
|
||||
|
||||
func (p DoltDatabaseProvider) DropDatabase(ctx *sql.Context, name string) error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// Copyright 2022 Dolthub, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package dprocedures
|
||||
|
||||
import (
|
||||
"path"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/cli"
|
||||
"github.com/dolthub/dolt/go/cmd/dolt/errhand"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/env"
|
||||
"github.com/dolthub/dolt/go/libraries/doltcore/sqle/dsess"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/argparser"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/config"
|
||||
"github.com/dolthub/dolt/go/libraries/utils/earl"
|
||||
)
|
||||
|
||||
// doltClone is a stored procedure to clone a database from a remote
|
||||
func doltClone(ctx *sql.Context, args ...string) (sql.RowIter, error) {
|
||||
ap := cli.CreateCloneArgParser()
|
||||
apr, err := ap.Parse(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteName := apr.GetValueOrDefault(cli.RemoteParam, "origin")
|
||||
branch := apr.GetValueOrDefault(cli.BranchParam, "")
|
||||
dir, urlStr, err := getDirectoryAndUrlString(apr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess := dsess.DSessFromSess(ctx.Session)
|
||||
scheme, remoteUrl, err := env.GetAbsRemoteUrl(sess.Provider().FileSystem(), emptyConfig(), urlStr)
|
||||
if err != nil {
|
||||
return nil, errhand.BuildDError("error: '%s' is not valid.", urlStr).Build()
|
||||
}
|
||||
|
||||
params, err := remoteParams(apr, scheme, remoteUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = sess.Provider().CloneDatabaseFromRemote(ctx, dir, branch, remoteName, remoteUrl, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rowToIter(int64(0)), nil
|
||||
}
|
||||
|
||||
func emptyConfig() config.ReadableConfig {
|
||||
return &config.MapConfig{}
|
||||
}
|
||||
|
||||
func getDirectoryAndUrlString(apr *argparser.ArgParseResults) (string, string, error) {
|
||||
if apr.NArg() < 1 || apr.NArg() > 2 {
|
||||
return "", "", errhand.BuildDError("error: invalid number of arguments: database URL must be specified and database name is optional").Build()
|
||||
}
|
||||
|
||||
urlStr := apr.Arg(0)
|
||||
_, err := earl.Parse(urlStr)
|
||||
if err != nil {
|
||||
return "", "", errhand.BuildDError("error: invalid remote url: " + urlStr).Build()
|
||||
}
|
||||
|
||||
var dir string
|
||||
if apr.NArg() == 2 {
|
||||
dir = apr.Arg(1)
|
||||
} else {
|
||||
dir = path.Base(urlStr)
|
||||
if dir == "." {
|
||||
dir = path.Dir(urlStr)
|
||||
} else if dir == "/" {
|
||||
return "", "", errhand.BuildDError("Could not infer repo name. Please explicitly define a directory for this url").Build()
|
||||
}
|
||||
}
|
||||
|
||||
return dir, urlStr, nil
|
||||
}
|
||||
@@ -22,6 +22,7 @@ var DoltProcedures = []sql.ExternalStoredProcedureDetails{
|
||||
{Name: "dolt_branch", Schema: int64Schema("status"), Function: doltBranch},
|
||||
{Name: "dolt_checkout", Schema: int64Schema("status"), Function: doltCheckout},
|
||||
{Name: "dolt_clean", Schema: int64Schema("status"), Function: doltClean},
|
||||
{Name: "dolt_clone", Schema: int64Schema("status"), Function: doltClone},
|
||||
{Name: "dolt_commit", Schema: stringSchema("hash"), Function: doltCommit},
|
||||
{Name: "dolt_fetch", Schema: int64Schema("success"), Function: doltFetch},
|
||||
{Name: "dolt_merge", Schema: int64Schema("fast_forward", "conflicts"), Function: doltMerge},
|
||||
|
||||
@@ -46,6 +46,11 @@ type DoltDatabaseProvider interface {
|
||||
// This function replaces env.Remote's GetRemoteDB method during SQL session to access dialer in order
|
||||
// to get remote database associated to the env.Remote object.
|
||||
GetRemoteDB(ctx *sql.Context, srcDB *doltdb.DoltDB, r env.Remote, withCaching bool) (*doltdb.DoltDB, error)
|
||||
// CloneDatabaseFromRemote clones the database from the specified remoteURL as a new database in this provider.
|
||||
// dbName is the name for the new database, branch is an optional parameter indicating which branch to clone
|
||||
// (otherwise all branches are cloned), remoteName is the name for the remote created in the new database, and
|
||||
// remoteUrl is a URL (e.g. "file:///dbs/db1") or an <org>/<database> path indicating a database hosted on DoltHub.
|
||||
CloneDatabaseFromRemote(ctx *sql.Context, dbName, branch, remoteName, remoteUrl string, remoteParams map[string]string) error
|
||||
}
|
||||
|
||||
func EmptyDatabaseProvider() DoltDatabaseProvider {
|
||||
@@ -62,6 +67,10 @@ func (e emptyRevisionDatabaseProvider) FileSystem() filesys.Filesys {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e emptyRevisionDatabaseProvider) CloneDatabaseFromRemote(ctx *sql.Context, dbName, branch, remoteName, remoteUrl string, remoteParams map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e emptyRevisionDatabaseProvider) DropRevisionDb(ctx *sql.Context, revDB string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1990,3 +1990,102 @@ SQL
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "Fast-forward" ]] || false
|
||||
}
|
||||
|
||||
@test "remotes: dolt_clone procedure" {
|
||||
repoDir="$BATS_TMPDIR/dolt-repo-$$"
|
||||
tempDir=$(mktemp -d)
|
||||
|
||||
cd $tempDir
|
||||
mkdir remote
|
||||
mkdir repo1
|
||||
|
||||
cd repo1
|
||||
dolt init
|
||||
dolt remote add origin file://../remote
|
||||
dolt push origin main
|
||||
dolt checkout -b other
|
||||
dolt push --set-upstream origin other
|
||||
|
||||
cd $repoDir
|
||||
|
||||
run dolt sql -q "call dolt_clone()"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "error: invalid number of arguments" ]] || false
|
||||
|
||||
run dolt sql -q "call dolt_clone('file://$tempDir/remote', 'foo', 'bar')"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "error: invalid number of arguments" ]] || false
|
||||
|
||||
# Clone a local database and check for all the branches
|
||||
run dolt sql -q "call dolt_clone('file://$tempDir/remote');"
|
||||
[ "$status" -eq 0 ]
|
||||
cd remote
|
||||
run dolt branch
|
||||
[ "$status" -eq 0 ]
|
||||
[[ ! "$output" =~ "other" ]] || false
|
||||
[[ "$output" =~ "main" ]] || false
|
||||
run dolt branch --remote
|
||||
[[ "$output" =~ "origin/other" ]] || false
|
||||
[[ "$output" =~ "origin/main" ]] || false
|
||||
cd ..
|
||||
|
||||
# Ensure we can't clone it again
|
||||
run dolt sql -q "call dolt_clone('file://$tempDir/remote');"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "can't create database remote; database exists" ]] || false
|
||||
|
||||
# Drop the new database and re-clone it with a different name
|
||||
dolt sql -q "drop database remote"
|
||||
run dolt sql -q "show databases"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ ! "$output" =~ "repo2" ]] || false
|
||||
dolt sql -q "call dolt_clone('file://$tempDir/remote', 'repo2');"
|
||||
|
||||
# Sanity check that we can use the new database
|
||||
dolt sql << SQL
|
||||
use repo2;
|
||||
create table new_table(a int primary key);
|
||||
insert into new_table values (1), (2);
|
||||
SQL
|
||||
cd repo2
|
||||
dolt commit -am "a commit for main from repo2"
|
||||
dolt push origin main
|
||||
cd ..
|
||||
|
||||
# Test -remote option to customize the origin remote name for the cloned DB
|
||||
run dolt sql -q "call dolt_clone('-remote', 'custom', 'file://$tempDir/remote', 'custom_remote');"
|
||||
[ "$status" -eq 0 ]
|
||||
run dolt sql -q "use custom_remote; select name from dolt_remotes;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "custom" ]] || false
|
||||
|
||||
# Test -branch option to only clone a single branch
|
||||
run dolt sql -q "call dolt_clone('-branch', 'other', 'file://$tempDir/remote', 'single_branch');"
|
||||
[ "$status" -eq 0 ]
|
||||
run dolt sql -q "use single_branch; select name from dolt_branches;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "other" ]] || false
|
||||
[[ ! "$output" =~ "main" ]] || false
|
||||
run dolt sql -q "use single_branch; select active_branch();"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "other" ]] || false
|
||||
# TODO: To match Git's semantics, clone for a single branch should NOT create any other
|
||||
# remote tracking branches (https://github.com/dolthub/dolt/issues/3873)
|
||||
# run dolt checkout main
|
||||
# [ "$status" -eq 1 ]
|
||||
|
||||
# Set up a test repo in the remote server
|
||||
cd repo2
|
||||
dolt remote add test-remote http://localhost:50051/test-org/test-repo
|
||||
dolt sql -q "CREATE TABLE test_table (pk INT)"
|
||||
dolt commit -am "main commit"
|
||||
dolt push test-remote main
|
||||
cd ..
|
||||
|
||||
# Test cloning from a server remote
|
||||
run dolt sql -q "call dolt_clone('http://localhost:50051/test-org/test-repo');"
|
||||
[ "$status" -eq 0 ]
|
||||
run dolt sql -q "use test_repo; show tables;"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" =~ "test_table" ]] || false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user