...

Source file src/github.com/google/go-github/v45/github/git_commits.go

Documentation: github.com/google/go-github/v45/github

     1  // Copyright 2013 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"strings"
    14  	"time"
    15  
    16  	"golang.org/x/crypto/openpgp"
    17  )
    18  
    19  // SignatureVerification represents GPG signature verification.
    20  type SignatureVerification struct {
    21  	Verified  *bool   `json:"verified,omitempty"`
    22  	Reason    *string `json:"reason,omitempty"`
    23  	Signature *string `json:"signature,omitempty"`
    24  	Payload   *string `json:"payload,omitempty"`
    25  }
    26  
    27  // Commit represents a GitHub commit.
    28  type Commit struct {
    29  	SHA          *string                `json:"sha,omitempty"`
    30  	Author       *CommitAuthor          `json:"author,omitempty"`
    31  	Committer    *CommitAuthor          `json:"committer,omitempty"`
    32  	Message      *string                `json:"message,omitempty"`
    33  	Tree         *Tree                  `json:"tree,omitempty"`
    34  	Parents      []*Commit              `json:"parents,omitempty"`
    35  	Stats        *CommitStats           `json:"stats,omitempty"`
    36  	HTMLURL      *string                `json:"html_url,omitempty"`
    37  	URL          *string                `json:"url,omitempty"`
    38  	Verification *SignatureVerification `json:"verification,omitempty"`
    39  	NodeID       *string                `json:"node_id,omitempty"`
    40  
    41  	// CommentCount is the number of GitHub comments on the commit. This
    42  	// is only populated for requests that fetch GitHub data like
    43  	// Pulls.ListCommits, Repositories.ListCommits, etc.
    44  	CommentCount *int `json:"comment_count,omitempty"`
    45  
    46  	// SigningKey denotes a key to sign the commit with. If not nil this key will
    47  	// be used to sign the commit. The private key must be present and already
    48  	// decrypted. Ignored if Verification.Signature is defined.
    49  	SigningKey *openpgp.Entity `json:"-"`
    50  }
    51  
    52  func (c Commit) String() string {
    53  	return Stringify(c)
    54  }
    55  
    56  // CommitAuthor represents the author or committer of a commit. The commit
    57  // author may not correspond to a GitHub User.
    58  type CommitAuthor struct {
    59  	Date  *time.Time `json:"date,omitempty"`
    60  	Name  *string    `json:"name,omitempty"`
    61  	Email *string    `json:"email,omitempty"`
    62  
    63  	// The following fields are only populated by Webhook events.
    64  	Login *string `json:"username,omitempty"` // Renamed for go-github consistency.
    65  }
    66  
    67  func (c CommitAuthor) String() string {
    68  	return Stringify(c)
    69  }
    70  
    71  // GetCommit fetches the Commit object for a given SHA.
    72  //
    73  // GitHub API docs: https://docs.github.com/en/rest/git/commits#get-a-commit
    74  func (s *GitService) GetCommit(ctx context.Context, owner string, repo string, sha string) (*Commit, *Response, error) {
    75  	u := fmt.Sprintf("repos/%v/%v/git/commits/%v", owner, repo, sha)
    76  	req, err := s.client.NewRequest("GET", u, nil)
    77  	if err != nil {
    78  		return nil, nil, err
    79  	}
    80  
    81  	c := new(Commit)
    82  	resp, err := s.client.Do(ctx, req, c)
    83  	if err != nil {
    84  		return nil, resp, err
    85  	}
    86  
    87  	return c, resp, nil
    88  }
    89  
    90  // createCommit represents the body of a CreateCommit request.
    91  type createCommit struct {
    92  	Author    *CommitAuthor `json:"author,omitempty"`
    93  	Committer *CommitAuthor `json:"committer,omitempty"`
    94  	Message   *string       `json:"message,omitempty"`
    95  	Tree      *string       `json:"tree,omitempty"`
    96  	Parents   []string      `json:"parents,omitempty"`
    97  	Signature *string       `json:"signature,omitempty"`
    98  }
    99  
   100  // CreateCommit creates a new commit in a repository.
   101  // commit must not be nil.
   102  //
   103  // The commit.Committer is optional and will be filled with the commit.Author
   104  // data if omitted. If the commit.Author is omitted, it will be filled in with
   105  // the authenticated user’s information and the current date.
   106  //
   107  // GitHub API docs: https://docs.github.com/en/rest/git/commits#create-a-commit
   108  func (s *GitService) CreateCommit(ctx context.Context, owner string, repo string, commit *Commit) (*Commit, *Response, error) {
   109  	if commit == nil {
   110  		return nil, nil, fmt.Errorf("commit must be provided")
   111  	}
   112  
   113  	u := fmt.Sprintf("repos/%v/%v/git/commits", owner, repo)
   114  
   115  	parents := make([]string, len(commit.Parents))
   116  	for i, parent := range commit.Parents {
   117  		parents[i] = *parent.SHA
   118  	}
   119  
   120  	body := &createCommit{
   121  		Author:    commit.Author,
   122  		Committer: commit.Committer,
   123  		Message:   commit.Message,
   124  		Parents:   parents,
   125  	}
   126  	if commit.Tree != nil {
   127  		body.Tree = commit.Tree.SHA
   128  	}
   129  	if commit.SigningKey != nil {
   130  		signature, err := createSignature(commit.SigningKey, body)
   131  		if err != nil {
   132  			return nil, nil, err
   133  		}
   134  		body.Signature = &signature
   135  	}
   136  	if commit.Verification != nil {
   137  		body.Signature = commit.Verification.Signature
   138  	}
   139  
   140  	req, err := s.client.NewRequest("POST", u, body)
   141  	if err != nil {
   142  		return nil, nil, err
   143  	}
   144  
   145  	c := new(Commit)
   146  	resp, err := s.client.Do(ctx, req, c)
   147  	if err != nil {
   148  		return nil, resp, err
   149  	}
   150  
   151  	return c, resp, nil
   152  }
   153  
   154  func createSignature(signingKey *openpgp.Entity, commit *createCommit) (string, error) {
   155  	if signingKey == nil || commit == nil {
   156  		return "", errors.New("createSignature: invalid parameters")
   157  	}
   158  
   159  	message, err := createSignatureMessage(commit)
   160  	if err != nil {
   161  		return "", err
   162  	}
   163  
   164  	writer := new(bytes.Buffer)
   165  	reader := bytes.NewReader([]byte(message))
   166  	if err := openpgp.ArmoredDetachSign(writer, signingKey, reader, nil); err != nil {
   167  		return "", err
   168  	}
   169  
   170  	return writer.String(), nil
   171  }
   172  
   173  func createSignatureMessage(commit *createCommit) (string, error) {
   174  	if commit == nil || commit.Message == nil || *commit.Message == "" || commit.Author == nil {
   175  		return "", errors.New("createSignatureMessage: invalid parameters")
   176  	}
   177  
   178  	var message []string
   179  
   180  	if commit.Tree != nil {
   181  		message = append(message, fmt.Sprintf("tree %s", *commit.Tree))
   182  	}
   183  
   184  	for _, parent := range commit.Parents {
   185  		message = append(message, fmt.Sprintf("parent %s", parent))
   186  	}
   187  
   188  	message = append(message, fmt.Sprintf("author %s <%s> %d %s", commit.Author.GetName(), commit.Author.GetEmail(), commit.Author.GetDate().Unix(), commit.Author.GetDate().Format("-0700")))
   189  
   190  	committer := commit.Committer
   191  	if committer == nil {
   192  		committer = commit.Author
   193  	}
   194  
   195  	// There needs to be a double newline after committer
   196  	message = append(message, fmt.Sprintf("committer %s <%s> %d %s\n", committer.GetName(), committer.GetEmail(), committer.GetDate().Unix(), committer.GetDate().Format("-0700")))
   197  	message = append(message, *commit.Message)
   198  
   199  	return strings.Join(message, "\n"), nil
   200  }
   201  

View as plain text