github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/env/remotes.go (about)

     1  // Copyright 2019 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package env
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"net/url"
    22  	"path"
    23  	"path/filepath"
    24  	"sort"
    25  	"strings"
    26  
    27  	goerrors "gopkg.in/src-d/go-errors.v1"
    28  
    29  	"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
    30  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    31  	"github.com/dolthub/dolt/go/libraries/doltcore/ref"
    32  	"github.com/dolthub/dolt/go/libraries/utils/argparser"
    33  	"github.com/dolthub/dolt/go/libraries/utils/concurrentmap"
    34  	"github.com/dolthub/dolt/go/libraries/utils/config"
    35  	"github.com/dolthub/dolt/go/libraries/utils/earl"
    36  	filesys2 "github.com/dolthub/dolt/go/libraries/utils/filesys"
    37  	"github.com/dolthub/dolt/go/store/types"
    38  )
    39  
    40  var NoRemote = Remote{}
    41  
    42  var ErrBranchDoesNotMatchUpstream = errors.New("the upstream branch of your current branch does not match the name of your current branch")
    43  var ErrFailedToReadDb = errors.New("failed to read from the db")
    44  var ErrUnknownBranch = errors.New("unknown branch")
    45  var ErrCannotSetUpstreamForTag = errors.New("cannot set upstream for tag")
    46  var ErrCannotPushRef = errors.New("cannot push ref")
    47  var ErrNoRefSpecForRemote = errors.New("no refspec for remote")
    48  var ErrInvalidFetchSpec = errors.New("invalid fetch spec")
    49  var ErrPullWithRemoteNoUpstream = errors.New("You asked to pull from the remote '%s', but did not specify a branch. Because this is not the default configured remote for your current branch, you must specify a branch.")
    50  var ErrPullWithNoRemoteAndNoUpstream = errors.New("There is no tracking information for the current branch.\nPlease specify which branch you want to merge with.\n\n\tdolt pull <remote> <branch>\n\nIf you wish to set tracking information for this branch you can do so with:\n\n\t dolt push --set-upstream <remote> <branch>\n")
    51  
    52  var ErrCurrentBranchHasNoUpstream = goerrors.NewKind("fatal: The current branch %s has no upstream branch.\n" +
    53  	"To push the current branch and set the remote as upstream, use\n" +
    54  	"\tdolt push --set-upstream %s %s\n" +
    55  	"To have this happen automatically for branches without a tracking\n" +
    56  	"upstream, see 'push.autoSetupRemote' in 'dolt config --help'.")
    57  var ErrInvalidRepository = goerrors.NewKind("fatal: remote '%s' not found.\n" +
    58  	"Please make sure the remote exists.")
    59  var ErrAllFlagCannotBeUsedWithRefSpec = goerrors.NewKind("fatal: --all can't be combined with refspecs")
    60  var ErrNoPushDestination = goerrors.NewKind("fatal: No configured push destination.\n" +
    61  	"Either specify the URL from the command-line or configure a remote repository using\n\n" +
    62  	"\tdolt remote add <name> <url>\n\n" +
    63  	"and then push using the remote name\n\n" +
    64  	"\tdolt push <name>\n\n")
    65  var ErrFailedToPush = goerrors.NewKind("error: failed to push some refs to '%s'\n" +
    66  	"hint: Updates were rejected because the tip of your current branch is behind\n" +
    67  	"hint: its remote counterpart. Integrate the remote changes (e.g.\n" +
    68  	"hint: 'dolt pull ...') before pushing again.\n")
    69  
    70  func IsEmptyRemote(r Remote) bool {
    71  	return len(r.Name) == 0 && len(r.Url) == 0 && r.FetchSpecs == nil && r.Params == nil
    72  }
    73  
    74  type Remote struct {
    75  	Name       string            `json:"name"`
    76  	Url        string            `json:"url"`
    77  	FetchSpecs []string          `json:"fetch_specs"`
    78  	Params     map[string]string `json:"params"`
    79  }
    80  
    81  func NewRemote(name, url string, params map[string]string) Remote {
    82  	return Remote{name, url, []string{"refs/heads/*:refs/remotes/" + name + "/*"}, params}
    83  }
    84  
    85  func (r *Remote) GetParam(pName string) (string, bool) {
    86  	val, ok := r.Params[pName]
    87  	return val, ok
    88  }
    89  
    90  func (r *Remote) GetParamOrDefault(pName, defVal string) string {
    91  	val, ok := r.Params[pName]
    92  
    93  	if !ok {
    94  		return defVal
    95  	}
    96  
    97  	return val
    98  }
    99  
   100  func (r *Remote) GetRemoteDB(ctx context.Context, nbf *types.NomsBinFormat, dialer dbfactory.GRPCDialProvider) (*doltdb.DoltDB, error) {
   101  	params := make(map[string]interface{})
   102  	for k, v := range r.Params {
   103  		params[k] = v
   104  	}
   105  
   106  	params[dbfactory.GRPCDialProviderParam] = dialer
   107  
   108  	return doltdb.LoadDoltDBWithParams(ctx, nbf, r.Url, filesys2.LocalFS, params)
   109  }
   110  
   111  // Prepare does whatever work is necessary to prepare the remote given to receive pushes. Not all remote types can
   112  // support this operations and must be prepared manually. For existing remotes, no work is done.
   113  func (r *Remote) Prepare(ctx context.Context, nbf *types.NomsBinFormat, dialer dbfactory.GRPCDialProvider) error {
   114  	params := make(map[string]interface{})
   115  	for k, v := range r.Params {
   116  		params[k] = v
   117  	}
   118  
   119  	params[dbfactory.GRPCDialProviderParam] = dialer
   120  
   121  	return dbfactory.PrepareDB(ctx, nbf, r.Url, params)
   122  }
   123  
   124  func (r *Remote) GetRemoteDBWithoutCaching(ctx context.Context, nbf *types.NomsBinFormat, dialer dbfactory.GRPCDialProvider) (*doltdb.DoltDB, error) {
   125  	params := make(map[string]interface{})
   126  	for k, v := range r.Params {
   127  		params[k] = v
   128  	}
   129  	params[dbfactory.NoCachingParameter] = "true"
   130  	params[dbfactory.GRPCDialProviderParam] = dialer
   131  
   132  	return doltdb.LoadDoltDBWithParams(ctx, nbf, r.Url, filesys2.LocalFS, params)
   133  }
   134  
   135  func (r Remote) WithParams(params map[string]string) Remote {
   136  	fetchSpecs := make([]string, len(r.FetchSpecs))
   137  	copy(fetchSpecs, r.FetchSpecs)
   138  	for k, v := range r.Params {
   139  		params[k] = v
   140  	}
   141  	r.Params = params
   142  	return r
   143  }
   144  
   145  // PushOptions contains information needed for push for
   146  // one or more branches or a tag for a specific remote database.
   147  type PushOptions struct {
   148  	Targets []*PushTarget
   149  	Rsr     RepoStateReader
   150  	Rsw     RepoStateWriter
   151  	Remote  *Remote
   152  	SrcDb   *doltdb.DoltDB
   153  	DestDb  *doltdb.DoltDB
   154  	TmpDir  string
   155  }
   156  
   157  // PushTarget contains information needed for push per branch or tag.
   158  type PushTarget struct {
   159  	SrcRef      ref.DoltRef
   160  	DestRef     ref.DoltRef
   161  	RemoteRef   ref.DoltRef
   162  	Mode        ref.UpdateMode
   163  	SetUpstream bool
   164  	HasUpstream bool
   165  }
   166  
   167  func NewPushOpts(ctx context.Context, apr *argparser.ArgParseResults, rsr RepoStateReader, ddb *doltdb.DoltDB, force, setUpstream, pushAutoSetupRemote, all bool) ([]*PushTarget, *Remote, error) {
   168  	if apr.NArg() == 0 {
   169  		return getPushTargetsAndRemoteFromNoArg(ctx, rsr, ddb, force, setUpstream, pushAutoSetupRemote, all)
   170  	}
   171  
   172  	rsrBranches, err := rsr.GetBranches()
   173  	if err != nil {
   174  		return nil, nil, err
   175  	}
   176  
   177  	currentBranch, err := rsr.CWBHeadRef()
   178  	if err != nil {
   179  		return nil, nil, err
   180  	}
   181  
   182  	// the first argument defines the remote name
   183  	remoteName := apr.Arg(0)
   184  	if apr.NArg() == 1 {
   185  		remote, err := getRemote(rsr, remoteName)
   186  		if err != nil {
   187  			return nil, nil, err
   188  		}
   189  
   190  		if all {
   191  			return getPushTargetsAndRemoteForAllBranches(ctx, rsrBranches, currentBranch, &remote, ddb, force, setUpstream)
   192  		} else {
   193  			defaultRemote, err := GetDefaultRemote(rsr)
   194  			if err != nil {
   195  				return nil, nil, err
   196  			}
   197  
   198  			refSpec, _, hasUpstream, err := getCurrentBranchRefSpec(ctx, rsrBranches, rsr, ddb, remoteName, defaultRemote.Name == remoteName, true, setUpstream, pushAutoSetupRemote)
   199  			if err != nil {
   200  				return nil, nil, err
   201  			}
   202  
   203  			opts, err := getPushTargetFromRefSpec(refSpec, currentBranch, &remote, force, setUpstream, hasUpstream)
   204  			if err != nil {
   205  				return nil, nil, err
   206  			}
   207  			return []*PushTarget{opts}, &remote, nil
   208  		}
   209  	} else {
   210  		if all {
   211  			return nil, nil, ErrAllFlagCannotBeUsedWithRefSpec.New()
   212  		}
   213  
   214  		refSpecNames := apr.Args[1:]
   215  		// validate given refSpec names
   216  		for _, refSpecName := range refSpecNames {
   217  			if len(refSpecName) == 0 {
   218  				return nil, nil, fmt.Errorf("%w: '%s'", ref.ErrInvalidRefSpec, refSpecName)
   219  			}
   220  		}
   221  
   222  		remote, err := getRemote(rsr, apr.Arg(0))
   223  		if err != nil {
   224  			return nil, nil, err
   225  		}
   226  		return getPushTargetsAndRemoteForBranchRefs(ctx, rsrBranches, refSpecNames, currentBranch, &remote, ddb, force, setUpstream)
   227  	}
   228  }
   229  
   230  func getRemote(rsr RepoStateReader, name string) (Remote, error) {
   231  	remotes, err := rsr.GetRemotes()
   232  	if err != nil {
   233  		return NoRemote, err
   234  	}
   235  
   236  	remote, ok := remotes.Get(name)
   237  	if !ok {
   238  		return NoRemote, ErrInvalidRepository.New(name)
   239  	}
   240  	return remote, nil
   241  }
   242  
   243  // getPushTargetsAndRemoteFromNoArg pushes the current branch on default remote if upstream is set or `-u` is defined;
   244  // otherwise, all branches of default remote if `--all` flag is used.
   245  func getPushTargetsAndRemoteFromNoArg(ctx context.Context, rsr RepoStateReader, ddb *doltdb.DoltDB, force, setUpstream, pushAutoSetupRemote, all bool) ([]*PushTarget, *Remote, error) {
   246  	rsrBranches, err := rsr.GetBranches()
   247  	if err != nil {
   248  		return nil, nil, err
   249  	}
   250  
   251  	currentBranch, err := rsr.CWBHeadRef()
   252  	if err != nil {
   253  		return nil, nil, err
   254  	}
   255  
   256  	remote, err := GetDefaultRemote(rsr)
   257  	if err != nil {
   258  		if err == ErrNoRemote {
   259  			err = ErrNoPushDestination.New()
   260  		}
   261  		return nil, nil, err
   262  	}
   263  	if all {
   264  		return getPushTargetsAndRemoteForAllBranches(ctx, rsrBranches, currentBranch, &remote, ddb, force, setUpstream)
   265  	} else {
   266  		refSpec, remoteName, hasUpstream, err := getCurrentBranchRefSpec(ctx, rsrBranches, rsr, ddb, remote.Name, true, false, setUpstream, pushAutoSetupRemote)
   267  		if err != nil {
   268  			return nil, nil, err
   269  		}
   270  		if remoteName != remote.Name {
   271  			remote, err = getRemote(rsr, remoteName)
   272  			if err != nil {
   273  				return nil, nil, err
   274  			}
   275  		}
   276  
   277  		opts, err := getPushTargetFromRefSpec(refSpec, currentBranch, &remote, force, setUpstream, hasUpstream)
   278  		if err != nil {
   279  			return nil, nil, err
   280  		}
   281  		return []*PushTarget{opts}, &remote, nil
   282  	}
   283  }
   284  
   285  func getPushTargetsAndRemoteForAllBranches(ctx context.Context, rsrBranches *concurrentmap.Map[string, BranchConfig], currentBranch ref.DoltRef, remote *Remote, ddb *doltdb.DoltDB, force, setUpstream bool) ([]*PushTarget, *Remote, error) {
   286  	localBranches, err := ddb.GetBranches(ctx)
   287  	if err != nil {
   288  		return nil, nil, err
   289  	}
   290  	var lbNames = make([]string, len(localBranches))
   291  	for i, branch := range localBranches {
   292  		lbNames[i] = branch.GetPath()
   293  	}
   294  	return getPushTargetsAndRemoteForBranchRefs(ctx, rsrBranches, lbNames, currentBranch, remote, ddb, force, setUpstream)
   295  }
   296  
   297  func getPushTargetsAndRemoteForBranchRefs(ctx context.Context, rsrBranches *concurrentmap.Map[string, BranchConfig], localBranches []string, currentBranch ref.DoltRef, remote *Remote, ddb *doltdb.DoltDB, force, setUpstream bool) ([]*PushTarget, *Remote, error) {
   298  	var pushOptsList []*PushTarget
   299  	for _, refSpecName := range localBranches {
   300  		refSpec, err := getRefSpecFromStr(ctx, ddb, refSpecName)
   301  		if err != nil {
   302  			return nil, nil, err
   303  		}
   304  
   305  		// if the remote of upstream does not match the remote given,
   306  		// it should push to the given remote creating new remote branch
   307  		upstream, hasUpstream := rsrBranches.Get(refSpecName)
   308  		hasUpstream = hasUpstream && upstream.Remote == remote.Name
   309  
   310  		opts, err := getPushTargetFromRefSpec(refSpec, currentBranch, remote, force, setUpstream, hasUpstream)
   311  		if err != nil {
   312  			return nil, nil, err
   313  		}
   314  
   315  		pushOptsList = append(pushOptsList, opts)
   316  	}
   317  	return pushOptsList, remote, nil
   318  }
   319  
   320  func getPushTargetFromRefSpec(refSpec ref.RefSpec, currentBranch ref.DoltRef, remote *Remote, force, setUpstream, hasUpstream bool) (*PushTarget, error) {
   321  	src := refSpec.SrcRef(currentBranch)
   322  	dest := refSpec.DestRef(src)
   323  
   324  	var remoteRef ref.DoltRef
   325  	var err error
   326  	switch src.GetType() {
   327  	case ref.BranchRefType:
   328  		remoteRef, err = GetTrackingRef(dest, *remote)
   329  	case ref.TagRefType:
   330  		if setUpstream {
   331  			err = ErrCannotSetUpstreamForTag
   332  		}
   333  	default:
   334  		err = fmt.Errorf("%w: '%s' of type '%s'", ErrCannotPushRef, src.String(), src.GetType())
   335  	}
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	return &PushTarget{
   341  		SrcRef:    src,
   342  		DestRef:   dest,
   343  		RemoteRef: remoteRef,
   344  		Mode: ref.UpdateMode{
   345  			Force: force,
   346  		},
   347  		SetUpstream: setUpstream,
   348  		HasUpstream: hasUpstream,
   349  	}, nil
   350  }
   351  
   352  // getCurrentBranchRefSpec is called when refSpec is NOT specified. Whether to push depends on the specified remote.
   353  // If the specified remote is the default or the only remote, then it cannot push without its upstream set.
   354  // If the specified remote is one of many and non-default remote, then it pushes regardless of upstream is set.
   355  // If there is no remote specified, the current branch needs to have upstream set to push; otherwise, returns error.
   356  // This function returns |refSpec| for current branch, name of the remote the branch is associated with and
   357  // whether the current branch has upstream set.
   358  func getCurrentBranchRefSpec(ctx context.Context, branches *concurrentmap.Map[string, BranchConfig], rsr RepoStateReader, ddb *doltdb.DoltDB, remoteName string, isDefaultRemote, remoteSpecified, setUpstream, pushAutoSetupRemote bool) (ref.RefSpec, string, bool, error) {
   359  	var refSpec ref.RefSpec
   360  	currentBranch, err := rsr.CWBHeadRef()
   361  	if err != nil {
   362  		return nil, "", false, err
   363  	}
   364  
   365  	currentBranchName := currentBranch.GetPath()
   366  	upstream, hasUpstream := branches.Get(currentBranchName)
   367  
   368  	if remoteSpecified || pushAutoSetupRemote {
   369  		if isDefaultRemote && !pushAutoSetupRemote {
   370  			return nil, "", false, ErrCurrentBranchHasNoUpstream.New(currentBranchName, remoteName, currentBranchName)
   371  		}
   372  		setUpstream = true
   373  		refSpec, err = getRefSpecFromStr(ctx, ddb, currentBranchName)
   374  		if err != nil {
   375  			return nil, "", false, err
   376  		}
   377  	} else if hasUpstream {
   378  		remoteName = upstream.Remote
   379  		refSpec, err = getCurrentBranchRefSpecFromUpstream(currentBranch, upstream)
   380  		if err != nil {
   381  			return nil, "", false, err
   382  		}
   383  	} else {
   384  		return nil, "", false, ErrCurrentBranchHasNoUpstream.New(currentBranchName, remoteName, currentBranchName)
   385  	}
   386  	return refSpec, remoteName, hasUpstream && upstream.Remote == remoteName, nil
   387  }
   388  
   389  // RemoteForFetchArgs returns the remote and remaining arg strings for a fetch command
   390  func RemoteForFetchArgs(args []string, rsr RepoStateReader) (Remote, []string, error) {
   391  	var err error
   392  	remotes, err := rsr.GetRemotes()
   393  	if err != nil {
   394  		return NoRemote, nil, err
   395  	}
   396  
   397  	if remotes.Len() == 0 {
   398  		return NoRemote, nil, ErrNoRemote
   399  	}
   400  
   401  	var remName string
   402  	if len(args) == 0 {
   403  		remName = "origin"
   404  	} else {
   405  		remName = args[0]
   406  		args = args[1:]
   407  	}
   408  
   409  	remote, ok := remotes.Get(remName)
   410  	if !ok {
   411  		msg := "does not appear to be a dolt database. could not read from the remote database. please make sure you have the correct access rights and the database exists"
   412  		return NoRemote, nil, fmt.Errorf("%w; '%s' %s", ErrUnknownRemote, remName, msg)
   413  	}
   414  
   415  	return remote, args, nil
   416  }
   417  
   418  // ParseRefSpecs returns the ref specs for the string arguments given for the remote provided, or the default ref
   419  // specs for that remote if no arguments are provided.
   420  func ParseRefSpecs(args []string, rsr RepoStateReader, remote Remote) ([]ref.RemoteRefSpec, error) {
   421  	if len(args) != 0 {
   422  		return ParseRSFromArgs(remote.Name, args)
   423  	} else {
   424  		return GetRefSpecs(rsr, remote.Name)
   425  	}
   426  }
   427  
   428  func ParseRSFromArgs(remName string, args []string) ([]ref.RemoteRefSpec, error) {
   429  	var refSpecs []ref.RemoteRefSpec
   430  	for i := 0; i < len(args); i++ {
   431  		rsStr := args[i]
   432  		rs, err := ref.ParseRefSpec(rsStr)
   433  
   434  		if err != nil {
   435  			return nil, fmt.Errorf("%w: '%s'", ErrInvalidFetchSpec, rsStr)
   436  		}
   437  
   438  		if _, ok := rs.(ref.BranchToBranchRefSpec); ok {
   439  			local := "refs/heads/" + rsStr
   440  			remTracking := "remotes/" + remName + "/" + rsStr
   441  			rs2, err := ref.ParseRefSpec(local + ":" + remTracking)
   442  
   443  			if err == nil {
   444  				rs = rs2
   445  			}
   446  		}
   447  
   448  		if rrs, ok := rs.(ref.RemoteRefSpec); !ok {
   449  			return nil, fmt.Errorf("%w: '%s'", ErrInvalidFetchSpec, rsStr)
   450  
   451  		} else {
   452  			refSpecs = append(refSpecs, rrs)
   453  		}
   454  	}
   455  
   456  	return refSpecs, nil
   457  }
   458  
   459  // if possible, convert refs to full spec names. prefer branches over tags.
   460  // eg "main" -> "refs/heads/main", "v1" -> "refs/tags/v1"
   461  func disambiguateRefSpecStr(ctx context.Context, ddb *doltdb.DoltDB, refSpecStr string) (string, error) {
   462  	brachRefs, err := ddb.GetBranches(ctx)
   463  
   464  	if err != nil {
   465  		return "", err
   466  	}
   467  
   468  	for _, br := range brachRefs {
   469  		if br.GetPath() == refSpecStr {
   470  			return br.String(), nil
   471  		}
   472  	}
   473  
   474  	tagRefs, err := ddb.GetTags(ctx)
   475  
   476  	if err != nil {
   477  		return "", err
   478  	}
   479  
   480  	for _, tr := range tagRefs {
   481  		if tr.GetPath() == refSpecStr {
   482  			return tr.String(), nil
   483  		}
   484  	}
   485  
   486  	return refSpecStr, nil
   487  }
   488  
   489  // getRefSpecFromStr returns ref.RefSpec object using given branch/refSpec name
   490  func getRefSpecFromStr(ctx context.Context, ddb *doltdb.DoltDB, refSpecStr string) (ref.RefSpec, error) {
   491  	refSpecStr, err := disambiguateRefSpecStr(ctx, ddb, refSpecStr)
   492  	if err != nil {
   493  		return nil, err
   494  	}
   495  
   496  	refSpec, err := ref.ParseRefSpec(refSpecStr)
   497  	if err != nil {
   498  		return nil, fmt.Errorf("%w: '%s'", err, refSpecStr)
   499  	}
   500  
   501  	return refSpec, nil
   502  }
   503  
   504  // getCurrentBranchRefSpecFromUpstream validates the number of args defined and returns ref.RefSpec object of
   505  // current branch corresponding to the given upstream.
   506  func getCurrentBranchRefSpecFromUpstream(currentBranch ref.DoltRef, upstream BranchConfig) (ref.RefSpec, error) {
   507  	if currentBranch.GetPath() != upstream.Merge.Ref.GetPath() {
   508  		return nil, ErrBranchDoesNotMatchUpstream
   509  	}
   510  
   511  	refSpec, _ := ref.NewBranchToBranchRefSpec(currentBranch.(ref.BranchRef), upstream.Merge.Ref.(ref.BranchRef))
   512  	return refSpec, nil
   513  }
   514  
   515  func GetTrackingRef(branchRef ref.DoltRef, remote Remote) (ref.DoltRef, error) {
   516  	for _, fsStr := range remote.FetchSpecs {
   517  		fs, err := ref.ParseRefSpecForRemote(remote.Name, fsStr)
   518  
   519  		if err != nil {
   520  			return nil, fmt.Errorf("%w '%s' for remote '%s'", ErrInvalidFetchSpec, fsStr, remote.Name)
   521  		}
   522  
   523  		remoteRef := fs.DestRef(branchRef)
   524  
   525  		if remoteRef != nil {
   526  			return remoteRef, nil
   527  		}
   528  	}
   529  
   530  	return nil, nil
   531  }
   532  
   533  type PullSpec struct {
   534  	Squash     bool
   535  	NoFF       bool
   536  	NoCommit   bool
   537  	NoEdit     bool
   538  	Force      bool
   539  	RemoteName string
   540  	Remote     Remote
   541  	RefSpecs   []ref.RemoteRefSpec
   542  	Branch     ref.DoltRef
   543  }
   544  
   545  type PullSpecOpt func(*PullSpec)
   546  
   547  func WithSquash(squash bool) PullSpecOpt {
   548  	return func(ps *PullSpec) {
   549  		ps.Squash = squash
   550  	}
   551  }
   552  
   553  func WithNoFF(noff bool) PullSpecOpt {
   554  	return func(ps *PullSpec) {
   555  		ps.NoFF = noff
   556  	}
   557  }
   558  
   559  func WithNoCommit(nocommit bool) PullSpecOpt {
   560  	return func(ps *PullSpec) {
   561  		ps.NoCommit = nocommit
   562  	}
   563  }
   564  
   565  func WithNoEdit(noedit bool) PullSpecOpt {
   566  	return func(ps *PullSpec) {
   567  		ps.NoEdit = noedit
   568  	}
   569  }
   570  
   571  func WithForce(force bool) PullSpecOpt {
   572  	return func(ps *PullSpec) {
   573  		ps.Force = force
   574  	}
   575  }
   576  
   577  // NewPullSpec returns a PullSpec for the arguments given. This function validates remote and gets remoteRef
   578  // for given remoteRefName; if it's not defined, it uses current branch to get its upstream branch if it exists.
   579  func NewPullSpec(
   580  	_ context.Context,
   581  	rsr RepoStateReader,
   582  	remoteName, remoteRefName string,
   583  	remoteOnly bool,
   584  	opts ...PullSpecOpt,
   585  ) (*PullSpec, error) {
   586  	refSpecs, err := GetRefSpecs(rsr, remoteName)
   587  	if err != nil {
   588  		return nil, err
   589  	}
   590  	if len(refSpecs) == 0 {
   591  		return nil, ErrNoRefSpecForRemote
   592  	}
   593  
   594  	remotes, err := rsr.GetRemotes()
   595  	if err != nil {
   596  		return nil, err
   597  	}
   598  	remote, found := remotes.Get(refSpecs[0].GetRemote())
   599  	if !found {
   600  		return nil, ErrPullWithNoRemoteAndNoUpstream
   601  	}
   602  
   603  	var remoteRef ref.DoltRef
   604  	if remoteRefName == "" {
   605  		branch, err := rsr.CWBHeadRef()
   606  		if err != nil {
   607  			return nil, err
   608  		}
   609  		trackedBranches, err := rsr.GetBranches()
   610  		if err != nil {
   611  			return nil, err
   612  		}
   613  
   614  		trackedBranch, hasUpstream := trackedBranches.Get(branch.GetPath())
   615  		if !hasUpstream {
   616  			if remoteOnly {
   617  				return nil, fmt.Errorf(ErrPullWithRemoteNoUpstream.Error(), remoteName)
   618  			} else {
   619  				return nil, ErrPullWithNoRemoteAndNoUpstream
   620  			}
   621  		}
   622  
   623  		remoteRef = trackedBranch.Merge.Ref
   624  	} else {
   625  		remoteRef = ref.NewBranchRef(remoteRefName)
   626  	}
   627  
   628  	spec := &PullSpec{
   629  		RemoteName: remoteName,
   630  		Remote:     remote,
   631  		RefSpecs:   refSpecs,
   632  		Branch:     remoteRef,
   633  	}
   634  
   635  	for _, opt := range opts {
   636  		opt(spec)
   637  	}
   638  
   639  	return spec, nil
   640  }
   641  
   642  func GetAbsRemoteUrl(fs filesys2.Filesys, cfg config.ReadableConfig, urlArg string) (string, string, error) {
   643  	u, err := earl.Parse(urlArg)
   644  	if err != nil {
   645  		return "", "", err
   646  	}
   647  
   648  	if u.Scheme != "" && fs != nil {
   649  		if u.Scheme == dbfactory.FileScheme || u.Scheme == dbfactory.LocalBSScheme {
   650  			absUrl, err := getAbsFileRemoteUrl(u, fs)
   651  
   652  			if err != nil {
   653  				return "", "", err
   654  			}
   655  
   656  			return u.Scheme, absUrl, err
   657  		}
   658  
   659  		return u.Scheme, urlArg, nil
   660  	} else if u.Host != "" {
   661  		return dbfactory.HTTPSScheme, "https://" + urlArg, nil
   662  	}
   663  
   664  	hostName, err := cfg.GetString(config.RemotesApiHostKey)
   665  
   666  	if err != nil {
   667  		if err != config.ErrConfigParamNotFound {
   668  			return "", "", err
   669  		}
   670  
   671  		hostName = DefaultRemotesApiHost
   672  	}
   673  
   674  	hostName = strings.TrimSpace(hostName)
   675  
   676  	return dbfactory.HTTPSScheme, "https://" + path.Join(hostName, u.Path), nil
   677  }
   678  
   679  func getAbsFileRemoteUrl(u *url.URL, fs filesys2.Filesys) (string, error) {
   680  	urlStr := u.Host + u.Path
   681  	scheme := u.Scheme
   682  
   683  	var err error
   684  	urlStr = filepath.Clean(urlStr)
   685  	urlStr, err = fs.Abs(urlStr)
   686  
   687  	if err != nil {
   688  		return "", err
   689  	}
   690  
   691  	exists, isDir := fs.Exists(urlStr)
   692  
   693  	if !exists {
   694  		// TODO: very odd that GetAbsRemoteUrl will create a directory if it doesn't exist.
   695  		//  This concern should be separated
   696  		err = fs.MkDirs(urlStr)
   697  		if err != nil {
   698  			return "", fmt.Errorf("failed to create directory '%s': %w", urlStr, err)
   699  		}
   700  	} else if !isDir {
   701  		return "", filesys2.ErrIsFile
   702  	}
   703  
   704  	urlStr = filepath.ToSlash(urlStr)
   705  	return scheme + "://" + urlStr, nil
   706  }
   707  
   708  // GetDefaultBranch returns the default branch from among the branches given, returning
   709  // the configs default config branch first, then init branch main, then the old init branch master,
   710  // and finally the first lexicographical branch if none of the others are found
   711  func GetDefaultBranch(dEnv *DoltEnv, branches []ref.DoltRef) string {
   712  	if len(branches) == 0 {
   713  		return DefaultInitBranch
   714  	}
   715  
   716  	sort.Slice(branches, func(i, j int) bool {
   717  		return branches[i].GetPath() < branches[j].GetPath()
   718  	})
   719  
   720  	branchMap := make(map[string]ref.DoltRef)
   721  	for _, b := range branches {
   722  		branchMap[b.GetPath()] = b
   723  	}
   724  
   725  	if _, ok := branchMap[DefaultInitBranch]; ok {
   726  		return DefaultInitBranch
   727  	}
   728  	if _, ok := branchMap["master"]; ok {
   729  		return "master"
   730  	}
   731  
   732  	// todo: do we care about this during clone?
   733  	defaultOrMain := GetDefaultInitBranch(dEnv.Config)
   734  	if _, ok := branchMap[defaultOrMain]; ok {
   735  		return defaultOrMain
   736  	}
   737  
   738  	return branches[0].GetPath()
   739  }
   740  
   741  // SetRemoteUpstreamForRefSpec set upstream for given RefSpec, remote name and branch ref. It uses given RepoStateWriter
   742  // to persist upstream tracking branch information.
   743  func SetRemoteUpstreamForRefSpec(rsw RepoStateWriter, refSpec ref.RefSpec, remote string, branchRef ref.DoltRef) error {
   744  	src := refSpec.SrcRef(branchRef)
   745  	dest := refSpec.DestRef(src)
   746  
   747  	return rsw.UpdateBranch(branchRef.GetPath(), BranchConfig{
   748  		Merge: ref.MarshalableRef{
   749  			Ref: dest,
   750  		},
   751  		Remote: remote,
   752  	})
   753  }