Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dolt_clone stored procedure #3745

Merged
merged 36 commits into from Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a1fae36
Trying something
zachmu Jul 1, 2022
134e9d5
Inlined credtypes
zachmu Jul 1, 2022
6cf1d2d
working dolt_clone
zachmu Jul 1, 2022
3797be1
Added new db to session after clone
zachmu Jul 1, 2022
3f52707
Cleaned up clone
zachmu Jul 1, 2022
58d115c
[ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/upda…
zachmu Jul 1, 2022
d5fa232
Fixed compile error
zachmu Jul 1, 2022
44a1652
Merge main:
zachmu Jul 14, 2022
a65c3a7
Small cleanup
zachmu Jul 14, 2022
83347ed
Couple typos and comments
zachmu Jul 14, 2022
8bd4a0f
[ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/upda…
zachmu Jul 14, 2022
a597beb
Added bats test for dolt_clone procedure
zachmu Jul 14, 2022
990ffef
Merge main
zachmu Jul 14, 2022
67d59c4
Restoring changes after merge conflict
zachmu Jul 14, 2022
69a2a60
Merge branch 'zachmu/clone' of github.com:dolthub/dolt into zachmu/clone
zachmu Jul 14, 2022
9281576
Adding some godocs, error handling, and minor cleanup.
fulghum Jul 19, 2022
9e7fbc0
whitespace cleanup
fulghum Jul 19, 2022
92db69c
Filling in empty error message; disabling SetPrintUsage, since this i…
fulghum Jul 19, 2022
329f02b
Changing FileSystem.WithWorkingDir implementations to actually return…
fulghum Jul 19, 2022
b3d5592
Hack to fix a bug where clone logic was changing the process' current…
fulghum Jul 19, 2022
9526c9b
Expanding on interface documentation
fulghum Jul 19, 2022
a0ea4ca
Updated BATS tests
fulghum Jul 20, 2022
63a3d57
Reverting extra copy
fulghum Jul 20, 2022
d122e0b
Updating BATS tests to test remaining cases (single branch, overridin…
fulghum Jul 20, 2022
b6e6dd7
Switching to use remotesrv instead of DoltHub for tests
fulghum Jul 20, 2022
5ea361e
Adding issue URL for not creating remote tracking branches when cloni…
fulghum Jul 20, 2022
caefe9e
Merge branch 'main' into zachmu/clone
fulghum Jul 20, 2022
b6d48d0
[ga-format-pr] Run go/utils/repofmt/format_repo.sh and go/Godeps/upda…
fulghum Jul 20, 2022
abda8af
Fixing from dialer refactoring
fulghum Jul 20, 2022
e31d52f
Tweaking argument descriptions.
fulghum Jul 20, 2022
24b2afb
Removing duplicate function definition (from merge) and cleaning up i…
fulghum Jul 20, 2022
bbf3c99
Merge branch 'zachmu/clone' of https://github.com/dolthub/dolt into z…
fulghum Jul 20, 2022
44e4afa
Removing os.Chdir from actions.Clone since this isn't safe in a serve…
fulghum Jul 21, 2022
26cfe9d
Removing more usage of os.Chdir
fulghum Jul 21, 2022
0be183b
Renaming DoltEnv variables to be more explicit and clear
fulghum Jul 21, 2022
b161368
Updating godocs to better explain functions
fulghum Jul 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions go/cmd/dolt/cli/arg_parser_helpers.go
Expand Up @@ -95,6 +95,8 @@ const (
DeleteFlag = "delete"
DeleteForceFlag = "D"
OutputOnlyFlag = "output-only"
RemoteParam = "remote"
BranchParam = "branch"
TrackFlag = "track"
)

Expand Down Expand Up @@ -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.")
Expand Down
19 changes: 9 additions & 10 deletions go/cmd/dolt/commands/backup.go
Expand Up @@ -17,7 +17,6 @@ package commands
import (
"context"
"encoding/json"
"os"
"strings"

"github.com/dolthub/dolt/go/libraries/doltcore/env/actions"
Expand Down Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion go/cmd/dolt/commands/branch.go
Expand Up @@ -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)
Expand Down
40 changes: 14 additions & 26 deletions go/cmd/dolt/commands/clone.go
Expand Up @@ -16,7 +16,6 @@ package commands

import (
"context"
"os"
"path"

"github.com/dolthub/dolt/go/cmd/dolt/cli"
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
}
Expand All @@ -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)
}
Expand Down
4 changes: 1 addition & 3 deletions go/cmd/dolt/commands/remote.go
Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 3 additions & 7 deletions go/libraries/doltcore/env/actions/clone.go
Expand Up @@ -18,7 +18,6 @@ import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"sync"
Expand Down Expand Up @@ -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))

Expand All @@ -64,28 +64,24 @@ 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())
}

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())
}
Expand Down
4 changes: 2 additions & 2 deletions go/libraries/doltcore/env/environment.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand Down
84 changes: 84 additions & 0 deletions go/libraries/doltcore/sqle/database_provider.go
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is potentially a loooong time to be holding a lock! I think this is okay for v1, since dolt_clone() will not be frequently used, but it would be good to follow up and shrink down the code that executes with this 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()
Expand Down