Implement `push` subcommand.
This commit is contained in:
Родитель
20fe60d834
Коммит
556ea9e527
|
@ -0,0 +1,32 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/github/codeql-action-sync/internal/cachedirectory"
|
||||
"github.com/github/codeql-action-sync/internal/push"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var pushCmd = &cobra.Command{
|
||||
Use: "push",
|
||||
Short: "Push the CodeQL Action from the local cache to a GitHub Enterprise Server installation.",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cacheDirectory := cachedirectory.NewCacheDirectory(rootFlags.cacheDir)
|
||||
return push.Push(cmd.Context(), cacheDirectory, pushFlags.destinationURL, pushFlags.destinationToken, pushFlags.destinationRepository)
|
||||
},
|
||||
}
|
||||
|
||||
type pushFlagFields struct {
|
||||
destinationURL string
|
||||
destinationToken string
|
||||
destinationRepository string
|
||||
}
|
||||
|
||||
var pushFlags = pushFlagFields{}
|
||||
|
||||
func (f *pushFlagFields) Init(cmd *cobra.Command) {
|
||||
cmd.Flags().StringVar(&f.destinationURL, "destination-url", "", "The URL of the GitHub Enterprise instance to push to.")
|
||||
cmd.MarkFlagRequired("destination-url")
|
||||
cmd.Flags().StringVar(&f.destinationToken, "destination-token", "", "A token to access the API on the GitHub Enterprise instance.")
|
||||
cmd.MarkFlagRequired("destination-token")
|
||||
cmd.Flags().StringVar(&f.destinationRepository, "destination-repository", "github/codeql-action", "The name of the repository to create on GitHub Enterprise.")
|
||||
}
|
|
@ -55,5 +55,8 @@ func Execute(ctx context.Context) error {
|
|||
rootCmd.AddCommand(pullCmd)
|
||||
pullFlags.Init(pullCmd)
|
||||
|
||||
rootCmd.AddCommand(pushCmd)
|
||||
pushFlags.Init(pushCmd)
|
||||
|
||||
return rootCmd.ExecuteContext(ctx)
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -10,4 +10,5 @@ require (
|
|||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
|
||||
)
|
||||
|
|
|
@ -52,7 +52,9 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -166,6 +168,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -187,6 +190,7 @@ golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGm
|
|||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -16,6 +17,8 @@ const errorNotACacheOrEmpty = "The cache directory you have selected is not empt
|
|||
const errorCacheParentDoesNotExist = "Cannot create cache directory because its parent, does not exist."
|
||||
const errorPushNonCache = "The directory you have provided does not appear to be valid. Please check it exists and that you have run the `pull` command to populate it."
|
||||
|
||||
const CacheReferencePrefix = "refs/remotes/" + git.DefaultRemoteName + "/"
|
||||
|
||||
type CacheDirectory struct {
|
||||
path string
|
||||
}
|
||||
|
|
|
@ -81,8 +81,8 @@ func (pullService *pullService) pullGit(fresh bool) error {
|
|||
err = localRepository.FetchContext(pullService.ctx, &git.FetchOptions{
|
||||
RemoteName: git.DefaultRemoteName,
|
||||
RefSpecs: []config.RefSpec{
|
||||
config.RefSpec("+refs/heads/*:refs/remotes/" + git.DefaultRemoteName + "/heads/*"),
|
||||
config.RefSpec("+refs/tags/*:refs/remotes/" + git.DefaultRemoteName + "/tags/*"),
|
||||
config.RefSpec("+refs/heads/*:" + cachedirectory.CacheReferencePrefix + "heads/*"),
|
||||
config.RefSpec("+refs/tags/*:" + cachedirectory.CacheReferencePrefix + "tags/*"),
|
||||
},
|
||||
Progress: os.Stderr,
|
||||
Tags: git.NoTags,
|
||||
|
|
|
@ -58,7 +58,7 @@ func TestPullGitFresh(t *testing.T) {
|
|||
pullService := getTestPullService(t, temporaryDirectory, initialActionRepository, "")
|
||||
err := pullService.pullGit(true)
|
||||
require.NoError(t, err)
|
||||
checkExpectedReferencesInCache(t, pullService.cacheDirectory, []string{
|
||||
test.CheckExpectedReferencesInRepository(t, pullService.cacheDirectory.GitPath(), []string{
|
||||
"b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/remotes/origin/heads/main",
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/heads/v1",
|
||||
"e529a54fad10a936308b2220e05f7f00757f8e7c refs/remotes/origin/heads/v3",
|
||||
|
@ -93,7 +93,7 @@ func TestPullGitNotFreshWithChanges(t *testing.T) {
|
|||
pullService = getTestPullService(t, temporaryDirectory, modifiedActionRepository, "")
|
||||
err = pullService.pullGit(false)
|
||||
require.NoError(t, err)
|
||||
checkExpectedReferencesInCache(t, pullService.cacheDirectory, []string{
|
||||
test.CheckExpectedReferencesInRepository(t, pullService.cacheDirectory.GitPath(), []string{
|
||||
"b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/remotes/origin/heads/main",
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/heads/v1",
|
||||
"33d42021633d74bcd0bf9c95e3d3159131a5faa7 refs/remotes/origin/heads/v3", // v3 was force-pushed, and should have been force-pulled too.
|
||||
|
|
|
@ -2,5 +2,3 @@
|
|||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
[remote "origin"]
|
||||
url = /tmp/codeql-sim
|
||||
|
|
|
@ -2,5 +2,3 @@
|
|||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
[remote "origin"]
|
||||
url = /tmp/codeql-sim
|
||||
|
|
|
@ -0,0 +1,342 @@
|
|||
package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/github/codeql-action-sync/internal/cachedirectory"
|
||||
"github.com/github/codeql-action-sync/internal/version"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"github.com/google/go-github/v32/github"
|
||||
"github.com/mitchellh/ioprogress"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const remoteName = "enterprise"
|
||||
const repositoryHomepage = "https://github.com/github/codeql-action-sync-tool/"
|
||||
|
||||
type pushService struct {
|
||||
ctx context.Context
|
||||
cacheDirectory cachedirectory.CacheDirectory
|
||||
githubEnterpriseClient *github.Client
|
||||
destinationRepositoryName string
|
||||
destinationRepositoryOwner string
|
||||
destinationToken string
|
||||
}
|
||||
|
||||
func (pushService *pushService) createRepository() (*github.Repository, error) {
|
||||
log.Printf("Ensuring repository exists...")
|
||||
user, _, err := pushService.githubEnterpriseClient.Users.Get(pushService.ctx, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error getting current user.")
|
||||
}
|
||||
|
||||
// When creating a repository we can either create it in a named organization or under the current user (represented in go-github by an empty string).
|
||||
destinationOrganization := ""
|
||||
if pushService.destinationRepositoryOwner != user.GetLogin() {
|
||||
destinationOrganization = pushService.destinationRepositoryOwner
|
||||
}
|
||||
|
||||
if destinationOrganization != "" {
|
||||
_, response, err := pushService.githubEnterpriseClient.Organizations.Get(pushService.ctx, pushService.destinationRepositoryOwner)
|
||||
if err != nil && (response == nil || response.StatusCode != http.StatusNotFound) {
|
||||
return nil, errors.Wrap(err, "Error checking if destination organization exists.")
|
||||
}
|
||||
if response.StatusCode == http.StatusNotFound {
|
||||
log.Printf("The organization %s does not exist. Creating it...", pushService.destinationRepositoryOwner)
|
||||
createOrganizationRequest, err := pushService.githubEnterpriseClient.NewRequest("POST", "admin/organizations", map[string]interface{}{
|
||||
"login": pushService.destinationRepositoryOwner,
|
||||
"profile_name": pushService.destinationRepositoryOwner,
|
||||
"admin": user.GetLogin(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error checking if destination organization exists.")
|
||||
}
|
||||
response, err = pushService.githubEnterpriseClient.Do(pushService.ctx, createOrganizationRequest, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating organization.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repository, response, err := pushService.githubEnterpriseClient.Repositories.Get(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName)
|
||||
if err != nil && (response == nil || response.StatusCode != http.StatusNotFound) {
|
||||
return nil, errors.Wrap(err, "Error checking if destination repository exists.")
|
||||
}
|
||||
desiredRepositoryProperties := github.Repository{
|
||||
Name: github.String(pushService.destinationRepositoryName),
|
||||
Homepage: github.String(repositoryHomepage),
|
||||
HasIssues: github.Bool(false),
|
||||
HasProjects: github.Bool(false),
|
||||
HasPages: github.Bool(false),
|
||||
HasWiki: github.Bool(false),
|
||||
HasDownloads: github.Bool(false),
|
||||
Archived: github.Bool(false),
|
||||
Private: github.Bool(false),
|
||||
}
|
||||
if response.StatusCode == http.StatusNotFound {
|
||||
repository, _, err = pushService.githubEnterpriseClient.Repositories.Create(pushService.ctx, destinationOrganization, &desiredRepositoryProperties)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating destination repository.")
|
||||
}
|
||||
} else {
|
||||
repository, _, err = pushService.githubEnterpriseClient.Repositories.Edit(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, &desiredRepositoryProperties)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error updating destination repository.")
|
||||
}
|
||||
}
|
||||
|
||||
return repository, nil
|
||||
}
|
||||
|
||||
func (pushService *pushService) pushGit(repository *github.Repository, initialPush bool) error {
|
||||
remoteURL := repository.GetCloneURL()
|
||||
if initialPush {
|
||||
log.Printf("Pushing Git releases to %s...", remoteURL)
|
||||
} else {
|
||||
log.Printf("Pushing Git references to %s...", remoteURL)
|
||||
}
|
||||
gitRepository, err := git.PlainOpen(pushService.cacheDirectory.GitPath())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading Git repository from cache.")
|
||||
}
|
||||
|
||||
_ = gitRepository.DeleteRemote(remoteName)
|
||||
gitRemote, err := gitRepository.CreateRemote(&config.RemoteConfig{
|
||||
Name: remoteName,
|
||||
URLs: []string{remoteURL},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error adding repository remote.")
|
||||
}
|
||||
|
||||
credentials := &githttp.BasicAuth{
|
||||
Username: "x-access-token",
|
||||
Password: pushService.destinationToken,
|
||||
}
|
||||
|
||||
refSpecBatches := [][]config.RefSpec{}
|
||||
if initialPush {
|
||||
releasePathStats, err := ioutil.ReadDir(pushService.cacheDirectory.ReleasesPath())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading releases.")
|
||||
}
|
||||
refSpecBatches = append(refSpecBatches, []config.RefSpec{})
|
||||
for _, releasePathStat := range releasePathStats {
|
||||
refSpecBatches[0] = append(refSpecBatches[0], config.RefSpec("+"+cachedirectory.CacheReferencePrefix+"tags/"+releasePathStat.Name()+":refs/tags/"+releasePathStat.Name()))
|
||||
}
|
||||
} else {
|
||||
// We've got to push `main` on its own, so that it will be made the default branch if the repository has just been created. We then push everything else afterwards.
|
||||
refSpecBatches = [][]config.RefSpec{
|
||||
[]config.RefSpec{
|
||||
config.RefSpec("+" + cachedirectory.CacheReferencePrefix + "heads/main:refs/heads/main"),
|
||||
},
|
||||
[]config.RefSpec{
|
||||
config.RefSpec("+" + cachedirectory.CacheReferencePrefix + "heads/*:refs/heads/*"),
|
||||
config.RefSpec("+" + cachedirectory.CacheReferencePrefix + "tags/*:refs/tags/*"),
|
||||
},
|
||||
}
|
||||
}
|
||||
for _, refSpecs := range refSpecBatches {
|
||||
err = gitRemote.PushContext(pushService.ctx, &git.PushOptions{
|
||||
RemoteName: remoteName,
|
||||
RefSpecs: refSpecs,
|
||||
Auth: credentials,
|
||||
Progress: os.Stderr,
|
||||
Force: true,
|
||||
})
|
||||
if err != nil && errors.Cause(err) != git.NoErrAlreadyUpToDate {
|
||||
return errors.Wrap(err, "Error pushing Action to GitHub Enterprise Server.")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pushService *pushService) createOrUpdateRelease(releaseName string) (*github.RepositoryRelease, error) {
|
||||
releaseMetadata := github.RepositoryRelease{}
|
||||
releaseMetadataPath := pushService.cacheDirectory.MetadataPath(releaseName)
|
||||
releaseMetadataFile, err := ioutil.ReadFile(releaseMetadataPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error reading release metadata.")
|
||||
}
|
||||
err = json.Unmarshal([]byte(releaseMetadataFile), &releaseMetadata)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error converting release from JSON.")
|
||||
}
|
||||
// Some of our target commitishes are invalid as they point to `main` which we've not pushed yet.
|
||||
releaseMetadata.TargetCommitish = nil
|
||||
|
||||
release, response, err := pushService.githubEnterpriseClient.Repositories.GetReleaseByTag(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, releaseMetadata.GetTagName())
|
||||
if err != nil && response.StatusCode != http.StatusNotFound {
|
||||
return nil, errors.Wrap(err, "Error checking for existing CodeQL release.")
|
||||
}
|
||||
if release == nil {
|
||||
log.Printf("Creating release %s...", releaseMetadata.GetTagName())
|
||||
release, _, err := pushService.githubEnterpriseClient.Repositories.CreateRelease(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, &releaseMetadata)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error creating release.")
|
||||
}
|
||||
return release, nil
|
||||
}
|
||||
release, _, err = pushService.githubEnterpriseClient.Repositories.EditRelease(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, release.GetID(), &releaseMetadata)
|
||||
if err != nil {
|
||||
log.Printf("Updating release %s...", releaseMetadata.GetTagName())
|
||||
return nil, errors.Wrap(err, "Error updating release.")
|
||||
}
|
||||
return release, nil
|
||||
}
|
||||
|
||||
func (pushService *pushService) uploadReleaseAsset(release *github.RepositoryRelease, assetPathStat os.FileInfo, reader io.Reader) (*github.ReleaseAsset, *github.Response, error) {
|
||||
// This is technically already part of the go-github library, but we re-implement it here since otherwise we can't get a progress bar.
|
||||
url := fmt.Sprintf("repos/%s/%s/releases/%d/assets?name=%s", pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, release.GetID(), url.QueryEscape(assetPathStat.Name()))
|
||||
|
||||
mediaType := mime.TypeByExtension(filepath.Ext(assetPathStat.Name()))
|
||||
request, err := pushService.githubEnterpriseClient.NewUploadRequest(url, reader, assetPathStat.Size(), mediaType)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Error constructing upload request.")
|
||||
}
|
||||
|
||||
asset := &github.ReleaseAsset{}
|
||||
response, err := pushService.githubEnterpriseClient.Do(pushService.ctx, request, asset)
|
||||
if err != nil {
|
||||
return nil, response, errors.Wrap(err, "Error uploading release asset.")
|
||||
}
|
||||
return asset, response, nil
|
||||
}
|
||||
|
||||
func (pushService *pushService) createOrUpdateReleaseAsset(release *github.RepositoryRelease, existingAssets []*github.ReleaseAsset, assetPathStat os.FileInfo) error {
|
||||
for _, existingAsset := range existingAssets {
|
||||
if existingAsset.GetName() == assetPathStat.Name() {
|
||||
if int64(existingAsset.GetSize()) == assetPathStat.Size() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Printf("Uploading release asset %s...", assetPathStat.Name())
|
||||
assetFile, err := os.Open(pushService.cacheDirectory.AssetPath(release.GetTagName(), assetPathStat.Name()))
|
||||
defer assetFile.Close()
|
||||
progressReader := &ioprogress.Reader{
|
||||
Reader: assetFile,
|
||||
Size: assetPathStat.Size(),
|
||||
DrawFunc: ioprogress.DrawTerminalf(os.Stderr, ioprogress.DrawTextFormatBytes),
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error opening release asset.")
|
||||
}
|
||||
_, _, err = pushService.uploadReleaseAsset(release, assetPathStat, progressReader)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error uploading release asset.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pushService *pushService) pushReleases() error {
|
||||
log.Print("Pushing CodeQL bundles...")
|
||||
releasesPath := pushService.cacheDirectory.ReleasesPath()
|
||||
|
||||
releasePathStats, err := ioutil.ReadDir(releasesPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading releases.")
|
||||
}
|
||||
for _, releasePathStat := range releasePathStats {
|
||||
releaseName := releasePathStat.Name()
|
||||
release, err := pushService.createOrUpdateRelease(releaseName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingAssets := []*github.ReleaseAsset{}
|
||||
for page := 1; ; page++ {
|
||||
assets, _, err := pushService.githubEnterpriseClient.Repositories.ListReleaseAssets(pushService.ctx, pushService.destinationRepositoryOwner, pushService.destinationRepositoryName, release.GetID(), &github.ListOptions{Page: page})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error fetching existing release assets.")
|
||||
}
|
||||
if len(assets) == 0 {
|
||||
break
|
||||
}
|
||||
existingAssets = append(existingAssets, assets...)
|
||||
}
|
||||
|
||||
assetsPath := pushService.cacheDirectory.AssetsPath(releaseName)
|
||||
assetPathStats, err := ioutil.ReadDir(assetsPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error reading release assets.")
|
||||
}
|
||||
for _, assetPathStat := range assetPathStats {
|
||||
err := pushService.createOrUpdateReleaseAsset(release, existingAssets, assetPathStat)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error uploading release assets.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Push(ctx context.Context, cacheDirectory cachedirectory.CacheDirectory, destinationURL string, destinationToken string, destinationRepository string) error {
|
||||
err := cacheDirectory.CheckOrCreateVersionFile(false, version.Version())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
destinationURL = strings.TrimRight(destinationURL, "/")
|
||||
tokenSource := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: destinationToken},
|
||||
)
|
||||
tokenClient := oauth2.NewClient(ctx, tokenSource)
|
||||
client, err := github.NewEnterpriseClient(destinationURL+"/api/v3", destinationURL+"/api/uploads", tokenClient)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error creating GitHub Enterprise client.")
|
||||
}
|
||||
|
||||
destinationRepositorySplit := strings.Split(destinationRepository, "/")
|
||||
destinationRepositoryOwner := destinationRepositorySplit[0]
|
||||
destinationRepositoryName := destinationRepositorySplit[1]
|
||||
|
||||
pushService := pushService{
|
||||
ctx: ctx,
|
||||
cacheDirectory: cacheDirectory,
|
||||
githubEnterpriseClient: client,
|
||||
destinationRepositoryOwner: destinationRepositoryOwner,
|
||||
destinationRepositoryName: destinationRepositoryName,
|
||||
destinationToken: destinationToken,
|
||||
}
|
||||
|
||||
repository, err := pushService.createRepository()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// "He was going to live forever, or die in the attempt." - Catch-22, Joseph Heller
|
||||
// We can't push the releases first because you can't create tags in an empty Git repository.
|
||||
// We can't push the Git content first because then we'd have Git content that references releases that don't exist yet.
|
||||
// In this compromise solution we push only the tags that are referenced by releases, we then push the releases, and then finally we push the rest of the Git content.
|
||||
// This should work so long as no one uses a tag both to reference a specific version of the CodeQL Action and as a storage mechanism for a CodeQL bundle.
|
||||
err = pushService.pushGit(repository, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pushService.pushReleases()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = pushService.pushGit(repository, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("Finished pushing CodeQL Action to %s!", destinationRepository)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/github/codeql-action-sync/internal/cachedirectory"
|
||||
"github.com/github/codeql-action-sync/test"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/google/go-github/v32/github"
|
||||
)
|
||||
|
||||
func getTestPushService(t *testing.T, cacheDirectoryString string, githubEnterpriseURL string) pushService {
|
||||
cacheDirectory := cachedirectory.NewCacheDirectory(cacheDirectoryString)
|
||||
var githubEnterpriseClient *github.Client
|
||||
if githubEnterpriseURL != "" {
|
||||
client, err := github.NewEnterpriseClient(githubEnterpriseURL+"/api/v3", githubEnterpriseURL+"/api/uploads", &http.Client{})
|
||||
githubEnterpriseClient = client
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
githubEnterpriseClient = nil
|
||||
}
|
||||
return pushService{
|
||||
ctx: context.Background(),
|
||||
cacheDirectory: cacheDirectory,
|
||||
githubEnterpriseClient: githubEnterpriseClient,
|
||||
destinationRepositoryOwner: "destination-repository-owner",
|
||||
destinationRepositoryName: "destination-repository-name",
|
||||
destinationToken: "token",
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateRepositoryWhenUserIsOwner(t *testing.T) {
|
||||
temporaryDirectory := test.CreateTemporaryDirectory(t)
|
||||
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
|
||||
pushService := getTestPushService(t, temporaryDirectory, githubEnterpriseURL)
|
||||
githubTestServer.HandleFunc("/api/v3/user", func(response http.ResponseWriter, request *http.Request) {
|
||||
test.ServeHTTPResponseFromFile(t, http.StatusOK, "./push_test/api/user-is-owner.json", response)
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/user/repos", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write([]byte("{}"))
|
||||
}).Methods("POST")
|
||||
_, err := pushService.createRepository()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUpdateRepositoryWhenUserIsOwner(t *testing.T) {
|
||||
temporaryDirectory := test.CreateTemporaryDirectory(t)
|
||||
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
|
||||
pushService := getTestPushService(t, temporaryDirectory, githubEnterpriseURL)
|
||||
githubTestServer.HandleFunc("/api/v3/user", func(response http.ResponseWriter, request *http.Request) {
|
||||
test.ServeHTTPResponseFromFile(t, http.StatusOK, "./push_test/api/user-is-owner.json", response)
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write([]byte("{}"))
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write([]byte("{}"))
|
||||
}).Methods("PATCH")
|
||||
githubTestServer.HandleFunc("/api/v3/user/repos", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write([]byte("{}"))
|
||||
}).Methods("POST")
|
||||
_, err := pushService.createRepository()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCreateOrganizationAndRepositoryWhenOrganizationIsOwner(t *testing.T) {
|
||||
temporaryDirectory := test.CreateTemporaryDirectory(t)
|
||||
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
|
||||
pushService := getTestPushService(t, temporaryDirectory, githubEnterpriseURL)
|
||||
organizationCreated := false
|
||||
githubTestServer.HandleFunc("/api/v3/user", func(response http.ResponseWriter, request *http.Request) {
|
||||
test.ServeHTTPResponseFromFile(t, http.StatusOK, "./push_test/api/user-is-not-owner.json", response)
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/orgs/destination-repository-owner", func(response http.ResponseWriter, request *http.Request) {
|
||||
if organizationCreated {
|
||||
response.Write([]byte("{}"))
|
||||
} else {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/admin/organizations", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write([]byte("{}"))
|
||||
organizationCreated = true
|
||||
}).Methods("POST")
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/orgs/destination-repository-owner/repos", func(response http.ResponseWriter, request *http.Request) {
|
||||
response.Write([]byte("{}"))
|
||||
}).Methods("POST")
|
||||
_, err := pushService.createRepository()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPushGit(t *testing.T) {
|
||||
temporaryDirectory := test.CreateTemporaryDirectory(t)
|
||||
destinationPath := path.Join(temporaryDirectory, "target")
|
||||
_, err := git.PlainInit(destinationPath, true)
|
||||
require.NoError(t, err)
|
||||
pushService := getTestPushService(t, "./push_test/action-cache-initial/", "")
|
||||
repository := github.Repository{
|
||||
CloneURL: github.String(destinationPath),
|
||||
}
|
||||
|
||||
err = pushService.pushGit(&repository, true)
|
||||
require.NoError(t, err)
|
||||
test.CheckExpectedReferencesInRepository(t, destinationPath, []string{
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200101",
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200630",
|
||||
})
|
||||
|
||||
err = pushService.pushGit(&repository, false)
|
||||
require.NoError(t, err)
|
||||
test.CheckExpectedReferencesInRepository(t, destinationPath, []string{
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200101",
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/tags/codeql-bundle-20200630",
|
||||
"b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/heads/main",
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/heads/v1",
|
||||
"e529a54fad10a936308b2220e05f7f00757f8e7c refs/heads/v3",
|
||||
"bd82b85707bc13904e3526517677039d4da4a9bb refs/heads/very-ignored-branch",
|
||||
"bd82b85707bc13904e3526517677039d4da4a9bb refs/tags/an-ignored-tag-too",
|
||||
"26936381e619a01122ea33993e3cebc474496805 refs/tags/v2",
|
||||
})
|
||||
}
|
||||
|
||||
func TestPushReleases(t *testing.T) {
|
||||
githubTestServer, githubEnterpriseURL := test.GetTestHTTPServer(t)
|
||||
pushService := getTestPushService(t, "./push_test/action-cache-initial/", githubEnterpriseURL)
|
||||
existingReleases := map[string]github.RepositoryRelease{}
|
||||
existingAssets := map[int][]github.ReleaseAsset{}
|
||||
existingAssetBodys := map[int]map[string][]byte{}
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name/releases/tags/{tag}", func(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
if value, ok := existingReleases[vars["tag"]]; ok {
|
||||
test.ServeHTTPResponseFromObject(t, value, response)
|
||||
} else {
|
||||
response.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name/releases", func(response http.ResponseWriter, request *http.Request) {
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
require.NoError(t, err)
|
||||
var release *github.RepositoryRelease
|
||||
err = json.Unmarshal(body, &release)
|
||||
require.NoError(t, err)
|
||||
release.ID = github.Int64(rand.Int63())
|
||||
existingReleases[release.GetTagName()] = *release
|
||||
test.ServeHTTPResponseFromObject(t, release, response)
|
||||
}).Methods("POST")
|
||||
githubTestServer.HandleFunc("/api/v3/repos/destination-repository-owner/destination-repository-name/releases/{id:[0-9]+}/assets", func(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
releaseID, err := strconv.Atoi(vars["id"])
|
||||
require.NoError(t, err)
|
||||
test.ServeHTTPResponseFromObject(t, existingAssets[releaseID], response)
|
||||
}).Methods("GET")
|
||||
githubTestServer.HandleFunc("/api/uploads/repos/destination-repository-owner/destination-repository-name/releases/{id:[0-9]+}/assets", func(response http.ResponseWriter, request *http.Request) {
|
||||
vars := mux.Vars(request)
|
||||
releaseID, err := strconv.Atoi(vars["id"])
|
||||
require.NoError(t, err)
|
||||
assetName := request.URL.Query().Get("name")
|
||||
asset := github.ReleaseAsset{
|
||||
Name: github.String(assetName),
|
||||
}
|
||||
existingAssets[releaseID] = append(existingAssets[releaseID], asset)
|
||||
if existingAssetBodys[releaseID] == nil {
|
||||
existingAssetBodys[releaseID] = map[string][]byte{}
|
||||
}
|
||||
existingAssetBodys[releaseID][assetName], err = ioutil.ReadAll(request.Body)
|
||||
require.NoError(t, err)
|
||||
test.ServeHTTPResponseFromObject(t, asset, response)
|
||||
}).Methods("POST")
|
||||
err := pushService.pushReleases()
|
||||
require.NoError(t, err)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
ref: refs/heads/main
|
|
@ -0,0 +1,7 @@
|
|||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = true
|
||||
bare = true
|
||||
[remote "enterprise"]
|
||||
url = /tmp/codeql-action-sync-tests128816160/target
|
||||
fetch = +refs/heads/*:refs/remotes/enterprise/*
|
|
@ -0,0 +1,6 @@
|
|||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/26/936381e619a01122ea33993e3cebc474496805
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/26/936381e619a01122ea33993e3cebc474496805
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/28/509716746a9e77e3f752e9df740cb443bf9960
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/28/509716746a9e77e3f752e9df740cb443bf9960
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/61/8dc8ffee7af67e4e2dfdbe416c53cca6099fab
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/61/8dc8ffee7af67e4e2dfdbe416c53cca6099fab
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/80/8d4d2d6b7aa39089b99ca0d89f1fc6a2bc63ec
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/80/8d4d2d6b7aa39089b99ca0d89f1fc6a2bc63ec
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/86/e83156c07aea03c86a7cd7c5f6b0ee1453c2d7
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/86/e83156c07aea03c86a7cd7c5f6b0ee1453c2d7
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/8e/38374296a8ca208f5e526da24c5b09f3f48624
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/8e/38374296a8ca208f5e526da24c5b09f3f48624
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/b9/f01aa2c50f49898d4c7845a66be8824499fe9d
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/b9/f01aa2c50f49898d4c7845a66be8824499fe9d
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/bd/82b85707bc13904e3526517677039d4da4a9bb
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/bd/82b85707bc13904e3526517677039d4da4a9bb
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/cd/0f803f72414d81157588de0cb622f47f4bb9bc
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/cd/0f803f72414d81157588de0cb622f47f4bb9bc
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/d1/231c269d15582e06ded28babdb8c224ba7e6ed
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/d1/231c269d15582e06ded28babdb8c224ba7e6ed
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/e5/29a54fad10a936308b2220e05f7f00757f8e7c
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/e5/29a54fad10a936308b2220e05f7f00757f8e7c
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/e5/f2bcf5a8815c924448ff3a54455983df118ee4
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/e5/f2bcf5a8815c924448ff3a54455983df118ee4
Normal file
Двоичный файл не отображается.
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/e7/014e2af66d5b702946763455de2dcf9eb93c4f
Normal file
Двоичные данные
internal/push/push_test/action-cache-initial/git/objects/e7/014e2af66d5b702946763455de2dcf9eb93c4f
Normal file
Двоичный файл не отображается.
|
@ -0,0 +1,9 @@
|
|||
# pack-refs with: peeled fully-peeled sorted
|
||||
b9f01aa2c50f49898d4c7845a66be8824499fe9d refs/remotes/origin/heads/main
|
||||
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/heads/v1
|
||||
e529a54fad10a936308b2220e05f7f00757f8e7c refs/remotes/origin/heads/v3
|
||||
bd82b85707bc13904e3526517677039d4da4a9bb refs/remotes/origin/heads/very-ignored-branch
|
||||
bd82b85707bc13904e3526517677039d4da4a9bb refs/remotes/origin/tags/an-ignored-tag-too
|
||||
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/tags/codeql-bundle-20200101
|
||||
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/tags/codeql-bundle-20200630
|
||||
26936381e619a01122ea33993e3cebc474496805 refs/remotes/origin/tags/v2
|
|
@ -0,0 +1 @@
|
|||
b9f01aa2c50f49898d4c7845a66be8824499fe9d
|
|
@ -0,0 +1 @@
|
|||
26936381e619a01122ea33993e3cebc474496805
|
|
@ -0,0 +1 @@
|
|||
e529a54fad10a936308b2220e05f7f00757f8e7c
|
|
@ -0,0 +1 @@
|
|||
bd82b85707bc13904e3526517677039d4da4a9bb
|
|
@ -0,0 +1 @@
|
|||
This isn't really a CodeQL bundle either!
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"tag_name": "codeql-bundle-20200101",
|
||||
"target_commitish": "main"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
This isn't really a CodeQL bundle!
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"tag_name": "codeql-bundle-20200630",
|
||||
"target_commitish": "main"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"login": "user"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"login": "destination-repository-owner"
|
||||
}
|
28
test/test.go
28
test/test.go
|
@ -1,6 +1,7 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -8,6 +9,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -40,6 +44,13 @@ func ServeHTTPResponseFromFile(t *testing.T, statusCode int, path string, respon
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func ServeHTTPResponseFromObject(t *testing.T, object interface{}, response http.ResponseWriter) {
|
||||
bytes, err := json.Marshal(object)
|
||||
require.NoError(t, err)
|
||||
_, err = response.Write(bytes)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func RequireFilesAreEqual(t *testing.T, expectedPath string, actualPath string) {
|
||||
expectedData, err := ioutil.ReadFile(expectedPath)
|
||||
require.NoError(t, err)
|
||||
|
@ -47,3 +58,20 @@ func RequireFilesAreEqual(t *testing.T, expectedPath string, actualPath string)
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, expectedData, actualData)
|
||||
}
|
||||
|
||||
func CheckExpectedReferencesInRepository(t *testing.T, repositoryPath string, expectedReferences []string) {
|
||||
localRepository, err := git.PlainOpen(repositoryPath)
|
||||
require.NoError(t, err)
|
||||
referenceIterator, err := localRepository.References()
|
||||
require.NoError(t, err)
|
||||
actualReferences := []string{}
|
||||
err = referenceIterator.ForEach(func(reference *plumbing.Reference) error {
|
||||
referenceString := reference.String()
|
||||
if referenceString != "ref: refs/heads/master HEAD" {
|
||||
actualReferences = append(actualReferences, referenceString)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, expectedReferences, actualReferences)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче