github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/clone.go (about)

     1  package git
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  )
     9  
    10  type CloneOptions struct {
    11  	InitOptions
    12  	FetchPackOptions
    13  	Local                      bool
    14  	NoHardLinks                bool
    15  	Reference, ReferenceIfAble bool
    16  	Dissociate                 bool
    17  	Progress                   bool
    18  	NoCheckout                 bool
    19  	Mirror                     bool
    20  	// use name instead of origin as upstream remote.
    21  	Origin string
    22  	// Use branch instead of HEAD as default branch to checkout
    23  	Branch string
    24  	// Set configs in the newly created repository's config.
    25  	Configs map[string]string
    26  
    27  	// Only clone a single branch (either HEAD or Branch option)
    28  	SingleBranch bool
    29  
    30  	NoTags            bool
    31  	RecurseSubmodules bool
    32  	ShallowSubmodules bool
    33  	Jobs              int
    34  }
    35  
    36  // Clones a new repository from rmt into the directory dst, which must
    37  // not already exist.
    38  func Clone(opts CloneOptions, rmt Remote, dst File) error {
    39  	// This basically does the following:
    40  	// 1. Verify preconditions
    41  	// 2. Init
    42  	// 3. Fetch-pack --all
    43  	// 4. Set up some default config variables
    44  	// 5. UpdateRef master
    45  	// 6. Reset --hard
    46  	if dst == "" {
    47  		_, last := filepath.Split(rmt.String())
    48  		dst = File(last)
    49  	}
    50  	if dst.Exists() {
    51  		return fmt.Errorf("Directory %v already exists, can not clone.\n", dst)
    52  	}
    53  	c, err := Init(nil, opts.InitOptions, dst.String())
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	opts.FetchPackOptions.All = true
    59  	opts.FetchPackOptions.Verbose = true
    60  
    61  	refs, err := FetchPack(c, opts.FetchPackOptions, rmt, nil)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	config, err := LoadLocalConfig(c)
    66  	if err != nil {
    67  		return err
    68  	}
    69  
    70  	br := opts.Branch
    71  	if br == "" {
    72  		br = "master"
    73  	}
    74  	org := opts.Origin
    75  	if opts.Origin == "" {
    76  		org = "origin"
    77  	}
    78  	if rmt.IsFile() {
    79  		// the url in the config must point to an absolute path if
    80  		// passed on the command line as a relative one.
    81  		absurl, err := filepath.Abs(rmt.String())
    82  		if err != nil {
    83  			return err
    84  		}
    85  		config.SetConfig(fmt.Sprintf("remote.%v.url", org), absurl)
    86  	} else {
    87  		config.SetConfig(fmt.Sprintf("remote.%v.url", org), rmt.String())
    88  	}
    89  	config.SetConfig(fmt.Sprintf("branch.%v.remote", br), org)
    90  	// This should be smarter and get the HEAD symref from the connection.
    91  	// It isn't necessarily named refs/heads/master
    92  	config.SetConfig(fmt.Sprintf("branch.%v.merge", br), "refs/heads/master")
    93  	if err := config.WriteConfig(); err != nil {
    94  		return err
    95  	}
    96  
    97  	for _, ref := range refs {
    98  		if !strings.HasPrefix(ref.Name, "refs/heads/") {
    99  			// FIXME: This should have been done by GetRefs()
   100  			continue
   101  		}
   102  		refname := strings.Replace(ref.Name, "refs/heads/", "refs/remotes/"+org+"/", 1)
   103  		f := c.GitDir.File(File(refname))
   104  		cf, err := f.Create()
   105  		if err != nil {
   106  			return err
   107  		}
   108  		cf.Close()
   109  		if err := f.Append(fmt.Sprintf("%v\n", ref.Value)); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	// Now that we've populated all the remote names, we need to checkout
   114  	// the branch.
   115  	cmtish, err := RevParseCommitish(c, &RevParseOptions{}, org+"/"+br)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	cmt, err := cmtish.CommitID(c)
   120  	if err != nil {
   121  		return err
   122  	}
   123  	// Update the master branch to point to the same commit as origin/master
   124  	if err := UpdateRefSpec(
   125  		c,
   126  		UpdateRefOptions{CreateReflog: true, OldValue: CommitID{}},
   127  		RefSpec("refs/heads/master"),
   128  		cmt,
   129  		"clone: "+rmt.String(),
   130  	); err != nil {
   131  		return err
   132  	}
   133  
   134  	reflog, err := c.GitDir.ReadFile("logs/refs/heads/master")
   135  	if err != nil {
   136  		return err
   137  	}
   138  	// HEAD is already pointing to refs/heads/master from init, but the
   139  	// logs/HEAD reflog isn't created yet. We cheat by just copying the
   140  	// one created by UpdateRefSpec above.
   141  	if err := c.GitDir.WriteFile("logs/HEAD", reflog, 0755); err != nil {
   142  		return err
   143  	}
   144  	if opts.Bare {
   145  		return nil
   146  	}
   147  
   148  	// Finally, checkout the files. Since it's an initial clone, we just
   149  	// do a hard reset and don't try to be intelligent about what readtree
   150  	// does.
   151  	//
   152  	// We need to be sure we're within the repo, so that ReadTree and
   153  	// CheckoutIndexUncommitted don't have problems making paths relative,
   154  	// but then we restore the environment and c when we're done.
   155  	gwd := c.WorkDir
   156  	ggd := c.GitDir
   157  	pwd, err := os.Getwd()
   158  	defer func() {
   159  		os.Chdir(pwd)
   160  		c.WorkDir = gwd
   161  		c.GitDir = ggd
   162  	}()
   163  	if err != nil {
   164  		return err
   165  	}
   166  	if err := os.Chdir(c.WorkDir.String()); err != nil {
   167  		return err
   168  	}
   169  	// filepath.Rel can if workdir is ".", so use the absolute path
   170  	absdir, err := filepath.Abs(".")
   171  	if err != nil {
   172  		return err
   173  	}
   174  	c.WorkDir = WorkDir(absdir)
   175  	c.GitDir = GitDir(filepath.Join(c.WorkDir.String(), ".git"))
   176  	return Reset(c, ResetOptions{Hard: true}, nil)
   177  }