gitee.com/h79/goutils@v1.22.10/common/git/git.go (about) 1 package git 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "gitee.com/h79/goutils/common/file" 7 "gitee.com/h79/goutils/common/random" 8 9 "net/http" 10 "os" 11 "path/filepath" 12 "strings" 13 "time" 14 15 "bytes" 16 "log" 17 "os/exec" 18 "runtime" 19 20 "golang.org/x/crypto/ssh" 21 "gopkg.in/src-d/go-git.v4" 22 "gopkg.in/src-d/go-git.v4/config" 23 "gopkg.in/src-d/go-git.v4/plumbing" 24 "gopkg.in/src-d/go-git.v4/plumbing/object" 25 "gopkg.in/src-d/go-git.v4/plumbing/transport" 26 "gopkg.in/src-d/go-git.v4/plumbing/transport/client" 27 githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" 28 gitssh "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh" 29 ) 30 31 const branchNamePrefix = "refs/heads/auto-" 32 33 type Git struct { 34 Url string `json:"url"` 35 SshKey string `json:"ssh_key"` 36 SshKeySalt string `json:"ssh_key_salt"` 37 Path string `json:"path"` 38 Branch string `json:"branch"` 39 Username string `json:"username"` 40 Password string `json:"password"` 41 DirUser string `json:"dir_user"` 42 } 43 44 func (g *Git) Validate() error { 45 if g.Path == "" { 46 return errors.New("git path param error") 47 } 48 if g.Url == "" { 49 return errors.New("git url param error") 50 } 51 if g.IsHTTP() { 52 if (g.Username != "" && g.Password == "") || 53 (g.Username == "" && g.Password != "") { 54 return errors.New("git param username and password error") 55 } 56 } else if g.SshKey == "" { 57 return errors.New("git ssh_key param error") 58 } 59 return nil 60 } 61 62 func (g *Git) Checkout(name string) (err error) { 63 r, err := git.PlainOpen(g.Path) 64 if err != nil { 65 return 66 } 67 w, err := r.Worktree() 68 if err != nil { 69 return 70 } 71 err = w.Checkout(&git.CheckoutOptions{ 72 Branch: plumbing.ReferenceName("refs/heads/" + name), 73 Force: true, 74 }) 75 return 76 } 77 78 func (g *Git) CleanBranch() (err error) { 79 //var hash string 80 //var hashObj plumbing.Hash 81 var r *git.Repository 82 //var ref *plumbing.Reference 83 if r, err = git.PlainOpen(g.Path); err != nil { 84 return 85 } 86 refs, err := r.References() 87 if err != nil { 88 return 89 } 90 h, err := r.Head() 91 if err != nil { 92 return 93 } 94 headBranchName := h.Name().String() 95 err = refs.ForEach(func(ref0 *plumbing.Reference) (e error) { 96 if headBranchName != ref0.Name().String() && strings.HasPrefix(ref0.Name().String(), "refs/heads/") { 97 e = r.Storer.RemoveReference(ref0.Name()) 98 return 99 } 100 return nil 101 }) 102 return 103 } 104 105 func (g *Git) CreateBranchName() (name string, err error) { 106 var hash string 107 // var hashObj plumbing.Hash 108 var ref *plumbing.Reference 109 hash, _, _, ref, err = g.GetHash() 110 if err != nil { 111 return 112 } 113 f := "" 114 if ref != nil { 115 f = filepath.Base(ref.Name().Short()) 116 } else { 117 f = hash 118 } 119 name = branchNamePrefix + f + "-" + random.GenerateString(8) 120 return 121 } 122 123 func (g *Git) Publish() (commitId string, err error) { 124 defer func() { 125 go func() { 126 switch runtime.GOOS { 127 case "windows": 128 default: 129 cmd := exec.Command("chown", "-R", g.DirUser, g.Path) 130 var out bytes.Buffer 131 cmd.Stderr = &out 132 err = cmd.Run() 133 if err != nil { 134 log.Println(out.String()) 135 } 136 } 137 }() 138 }() 139 140 if file.DirEmpty(g.Path) { 141 err = nil 142 _, err = g.Clone() 143 } else { 144 p := filepath.Join(g.Path, ".git") 145 if _, err := os.Stat(p); err != nil || file.DirEmpty(p) { 146 return "", errors.New("git: publish error, target path is not a git repository") 147 } 148 _, err = git.PlainOpen(g.Path) 149 if err == nil { 150 _, err = g.Fetch() 151 } 152 } 153 if err != nil { 154 return 155 } 156 branchShortName := "" 157 branchShortName, _, err = g.CreateBranch() 158 if err != nil { 159 return 160 } 161 //output := "" 162 err = g.Checkout(branchShortName) 163 if err != nil { 164 return 165 } 166 err = g.CleanBranch() 167 if err != nil { 168 return 169 } 170 // get commit_id 171 commitId, err = g.LastCommitId() 172 173 return 174 } 175 176 // CreateBranch 创建分支 177 func (g *Git) CreateBranch() (branchShortName, branchName string, err error) { 178 _, hashObj, r, _, err := g.GetHash() 179 if err != nil { 180 return 181 } 182 branchName, err = g.CreateBranchName() 183 if err != nil { 184 return 185 } 186 branchShortName = filepath.Base(branchName) 187 err = r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(branchName), hashObj)) 188 return 189 } 190 191 // GetHash 获取 hash 192 func (g *Git) GetHash() (hash string, hashObj plumbing.Hash, r *git.Repository, ref *plumbing.Reference, err error) { 193 if r, err = git.PlainOpen(g.Path); err != nil { 194 return 195 } 196 refs, err := r.References() 197 if err != nil { 198 return 199 } 200 _ = refs.ForEach(func(ref0 *plumbing.Reference) (e error) { 201 if !strings.HasPrefix(ref0.Name().String(), "refs/heads/") { 202 if g.Branch == filepath.Base(ref0.Name().String()) { 203 hash = ref0.Hash().String() 204 ref = ref0 205 hashObj = ref0.Hash() 206 return errors.New("") 207 } 208 } 209 return nil 210 }) 211 if hash == "" { 212 var c *object.Commit 213 c, err = r.CommitObject(plumbing.NewHash(g.Branch)) 214 if err == nil { 215 hash = g.Branch 216 hashObj = c.Hash 217 return 218 } 219 } 220 return 221 } 222 223 // Fetch 拉取代码 224 func (g *Git) Fetch() (r *git.Repository, err error) { 225 if err = g.Validate(); err != nil { 226 return 227 } 228 if r, err = git.PlainOpen(g.Path); err != nil { 229 return 230 } 231 rconfig, err := r.Storer.Config() 232 if err != nil { 233 return 234 } 235 remoteConfig := &config.RemoteConfig{ 236 Name: git.DefaultRemoteName, 237 URLs: []string{g.Url}, 238 Fetch: []config.RefSpec{"+refs/heads/*:refs/remotes/" + git.DefaultRemoteName + "/*"}, 239 } 240 rconfig.Remotes = map[string]*config.RemoteConfig{ 241 git.DefaultRemoteName: remoteConfig, 242 } 243 err = r.Storer.SetConfig(rconfig) 244 if err != nil { 245 return 246 } 247 var opt git.FetchOptions 248 if opt, err = g.FetchOptions(); err != nil { 249 return 250 } 251 if err = r.Fetch(&opt); err == git.NoErrAlreadyUpToDate { 252 err = nil 253 } 254 return 255 } 256 257 func (g *Git) Clone() (r *git.Repository, err error) { 258 if err = g.Validate(); err != nil { 259 return 260 } 261 var opt git.CloneOptions 262 opt, err = g.CloneOptions() 263 if err != nil { 264 return 265 } 266 r, err = git.PlainClone(g.Path, false, &opt) 267 return 268 } 269 270 func (g *Git) LastCommitId() (hash string, err error) { 271 if err = g.Validate(); err != nil { 272 return 273 } 274 var r *git.Repository 275 if r, err = git.PlainOpen(g.Path); err != nil { 276 return 277 } 278 commitIter, err := r.Log(&git.LogOptions{}) 279 if err != nil { 280 return 281 } 282 commit, err := commitIter.Next() 283 if err != nil { 284 return 285 } 286 287 hash = commit.Hash.String() 288 return 289 } 290 291 func (g *Git) CloneOptions() (opt git.CloneOptions, err error) { 292 opt.URL = g.Url 293 if g.IsNeedAuth() { 294 opt.Auth, err = g.GetAuth() 295 if err != nil { 296 return 297 } 298 } 299 return 300 } 301 302 func (g *Git) FetchOptions() (opt git.FetchOptions, err error) { 303 opt.RemoteName = git.DefaultRemoteName 304 opt.RefSpecs = []config.RefSpec{"+refs/heads/*:refs/remotes/" + git.DefaultRemoteName + "/*"} 305 if g.IsNeedAuth() { 306 opt.Auth, err = g.GetAuth() 307 if err != nil { 308 return 309 } 310 } 311 return 312 } 313 314 func (g *Git) GetAuth() (auth transport.AuthMethod, err error) { 315 var signer ssh.Signer 316 if g.IsHTTP() { 317 customClient := &http.Client{ 318 Transport: &http.Transport{ 319 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 320 }, 321 Timeout: 30 * time.Minute, 322 CheckRedirect: func(req *http.Request, via []*http.Request) error { 323 return http.ErrUseLastResponse 324 }, 325 } 326 client.InstallProtocol("https", githttp.NewClient(customClient)) 327 client.InstallProtocol("http", githttp.NewClient(customClient)) 328 auth = &githttp.BasicAuth{ 329 Username: g.Username, 330 Password: g.Password, 331 } 332 } else { 333 if g.SshKeySalt != "" { 334 signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(g.SshKey), []byte(g.SshKeySalt)) 335 } else { 336 signer, err = ssh.ParsePrivateKey([]byte(g.SshKey)) 337 } 338 if err != nil { 339 return 340 } 341 auth = &gitssh.PublicKeys{ 342 User: "git", 343 Signer: signer, 344 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{ 345 HostKeyCallback: ssh.InsecureIgnoreHostKey(), 346 }, 347 } 348 } 349 return 350 } 351 352 func (g *Git) IsHTTP() bool { 353 return strings.HasPrefix(g.Url, "http://") || strings.HasPrefix(g.Url, "https://") 354 } 355 356 func (g *Git) IsNeedAuth() bool { 357 if g.IsHTTP() { 358 return g.Username != "" && g.Password != "" 359 } 360 return g.SshKey != "" 361 }