Merge pull request #3745 from dolthub/zachmu/clone

dolt_clone stored procedure
This commit is contained in:
Jason Fulghum
2022-07-25 09:16:09 -07:00
committed by GitHub
12 changed files with 329 additions and 49 deletions
+13
View File
@@ -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.")
+9 -10
View File
@@ -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)
}
+1 -1
View File
@@ -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)
+14 -26
View File
@@ -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)
}
+1 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+99
View File
@@ -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
}