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 }