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  }