...

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

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

View as plain text