diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..9e56bb94 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,21 @@ +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.14.x, 1.15.x] + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Install Task + run: curl -sL https://taskfile.dev/install.sh | sh + - name: Run CI Task + run: ./bin/task ci + diff --git a/.travis.yml b/.travis.yml index c6110759..d9698cfd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,9 @@ sudo: false matrix: include: - os: osx - go: 1.13.x + go: 1.14.x - os: linux - go: 1.13.x + go: 1.14.x notifications: email: false script: diff --git a/Dockerfile b/Dockerfile index 4420d2b1..fc00995a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,17 @@ -FROM golang:1.13 as builder +FROM golang:1.14 as builder ADD . /go/src/github.com/telia-oss/github-pr-resource WORKDIR /go/src/github.com/telia-oss/github-pr-resource RUN curl -sL https://taskfile.dev/install.sh | sh RUN ./bin/task build -FROM alpine:3.8 as resource +FROM alpine:3.11 as resource COPY --from=builder /go/src/github.com/telia-oss/github-pr-resource/build /opt/resource RUN apk add --update --no-cache \ git \ + git-lfs \ openssh \ && chmod +x /opt/resource/* +COPY scripts/askpass.sh /usr/local/bin/askpass.sh ADD scripts/install_git_crypt.sh install_git_crypt.sh RUN ./install_git_crypt.sh && rm ./install_git_crypt.sh diff --git a/README.md b/README.md index c5abc127..d46077ff 100644 --- a/README.md +++ b/README.md @@ -25,15 +25,18 @@ Make sure to check out [#migrating](#migrating) to learn more. | `access_token` | Yes | | A Github Access Token with repository access (required for setting status on commits). N.B. If you want github-pr-resource to work with a private repository. Set `repo:full` permissions on the access token you create on GitHub. If it is a public repository, `repo:status` is enough. | | `v3_endpoint` | No | `https://api.github.com` | Endpoint to use for the V3 Github API (Restful). | | `v4_endpoint` | No | `https://api.github.com/graphql` | Endpoint to use for the V4 Github API (Graphql). | -| `paths` | No | `terraform/*/*.tf` | Only produce new versions if the PR includes changes to files that match one or more glob patterns or prefixes. | -| `ignore_paths` | No | `.ci/` | Inverse of the above. Pattern syntax is documented in [filepath.Match](https://golang.org/pkg/path/filepath/#Match), or a path prefix can be specified (e.g. `.ci/` will match everything in the `.ci` directory). | +| `paths` | No | `["terraform/*/*.tf"]` | Only produce new versions if the PR includes changes to files that match one or more glob patterns or prefixes. | +| `ignore_paths` | No | `[".ci/"]` | Inverse of the above. Pattern syntax is documented in [filepath.Match](https://golang.org/pkg/path/filepath/#Match), or a path prefix can be specified (e.g. `.ci/` will match everything in the `.ci` directory). | | `disable_ci_skip` | No | `true` | Disable ability to skip builds with `[ci skip]` and `[skip ci]` in commit message or pull request title. | | `skip_ssl_verification` | No | `true` | Disable SSL/TLS certificate validation on git and API clients. Use with care! | | `disable_forks` | No | `true` | Disable triggering of the resource if the pull request's fork repository is different to the configured repository. | +| `ignore_drafts` | No | `false` | Disable triggering of the resource if the pull request is in Draft status. | | `required_review_approvals` | No | `2` | Disable triggering of the resource if the pull request does not have at least `X` approved review(s). | | `git_crypt_key` | No | `AEdJVENSWVBUS0VZAAAAA...` | Base64 encoded git-crypt key. Setting this will unlock / decrypt the repository with git-crypt. To get the key simply execute `git-crypt export-key -- - | base64` in an encrypted repository. | | `base_branch` | No | `master` | Name of a branch. The pipeline will only trigger on pull requests against the specified branch. | | `labels` | No | `["bug", "enhancement"]` | The labels on the PR. The pipeline will only trigger on pull requests having at least one of the specified labels. | +| `disable_git_lfs` | No | `true` | Disable Git LFS, skipping an attempt to convert pointers of files tracked into their corresponding objects when checked out into a working copy. | +| `states` | No | `["OPEN", "MERGED"]` | The PR states to select (`OPEN`, `MERGED` or `CLOSED`). The pipeline will only trigger on pull requests matching one of the specified states. Default is ["OPEN"]. | Notes: - If `v3_endpoint` is set, `v4_endpoint` must also be set (and the other way around). @@ -51,6 +54,7 @@ A version is represented as follows: - `pr`: The pull request number. - `commit`: The commit SHA. - `committed`: Timestamp of when the commit was committed. Used to filter subsequent checks. +- `approved_review_count`: The number of reviews approving of the PR. If several commits are pushed to a given PR at the same time, the last commit will be the new version. @@ -68,7 +72,9 @@ generate notifications over the webhook. So if you have a repository with little | `skip_download` | No | `true` | Use with `get_params` in a `put` step to do nothing on the implicit get. | | `integration_tool` | No | `rebase` | The integration tool to use, `merge`, `rebase` or `checkout`. Defaults to `merge`. | | `git_depth` | No | `1` | Shallow clone the repository using the `--depth` Git option | +| `submodules` | No | `true` | Recursively clone git submodules. Defaults to false. | | `list_changed_files` | No | `true` | Generate a list of changed files and save alongside metadata | +| `fetch_tags` | No | `true` | Fetch tags from remote repository | Clones the base (e.g. `master` branch) at the latest commit, and merges the pull request at the specified commit into master. This ensures that we are both testing and setting status on the exact commit that was requested in @@ -80,14 +86,13 @@ requested version and the metadata emitted by `get` are available to your tasks - `.git/resource/changed_files` (if enabled by `list_changed_files`) The information in `metadata.json` is also available as individual files in the `.git/resource` directory, e.g. the `base_sha` -is available as `.git/resource/base_sha`. For a complete list of available (individual) metadata files, please check the code +is available as `.git/resource/base_sha`. For a complete list of available (individual) metadata files, please check the code [here](https://github.com/telia-oss/github-pr-resource/blob/master/in.go#L66). When specifying `skip_download` the pull request volume mounted to subsequent tasks will be empty, which is a problem when you set e.g. the pending status before running the actual tests. The workaround for this is to use an alias for the `put` (see https://github.com/telia-oss/github-pr-resource/issues/32 for more details). - -git-crypt encrypted repositories will automatically be decrypted when the `git_crypt_key` is set in the source configuration. +Example here: ```yaml put: update-status <-- Use an alias for the pull-request resource @@ -98,6 +103,8 @@ params: get_params: {skip_download: true} ``` +git-crypt encrypted repositories will automatically be decrypted when the `git_crypt_key` is set in the source configuration. + Note that, should you retrigger a build in the hopes of testing the last commit to a PR against a newer version of the base, Concourse will reuse the volume (i.e. not trigger a new `get`) if it still exists, which can produce unexpected results (#5). As such, re-testing a PR against a newer version of the base is best done by *pushing an diff --git a/Taskfile.yml b/Taskfile.yml index 64480f09..f01ebc28 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,8 +1,5 @@ version: '2' -env: - GO111MODULE: on - vars: BUILD_DIR: build DOCKER_REPO: teliaoss/github-pr-resource diff --git a/check.go b/check.go index df128717..64df9e24 100644 --- a/check.go +++ b/check.go @@ -6,13 +6,21 @@ import ( "regexp" "sort" "strings" + + "github.com/shurcooL/githubv4" ) // Check (business logic) func Check(request CheckRequest, manager Github) (CheckResponse, error) { var response CheckResponse - pulls, err := manager.ListOpenPullRequests() + // Filter out pull request if it does not have a filtered state + filterStates := []githubv4.PullRequestState{githubv4.PullRequestStateOpen} + if len(request.Source.States) > 0 { + filterStates = request.Source.States + } + + pulls, err := manager.ListPullRequests(filterStates) if err != nil { return nil, fmt.Errorf("failed to get last commits: %s", err) } @@ -25,16 +33,19 @@ Loop: if !disableSkipCI && ContainsSkipCI(p.Title) { continue } + // [ci skip]/[skip ci] in Commit message if !disableSkipCI && ContainsSkipCI(p.Tip.Message) { continue } + // Filter pull request if the BaseBranch does not match the one specified in source if request.Source.BaseBranch != "" && p.PullRequestObject.BaseRefName != request.Source.BaseBranch { continue } + // Filter out commits that are too old. - if !p.Tip.CommittedDate.Time.After(request.Version.CommittedDate) { + if !p.UpdatedDate().Time.After(request.Version.CommittedDate) { continue } @@ -62,6 +73,11 @@ Loop: continue } + // Filter out drafts. + if request.Source.IgnoreDrafts && p.IsDraft { + continue + } + // Filter pull request if it does not have the required number of approved review(s). if p.ApprovedReviewCount < request.Source.RequiredReviewApprovals { continue diff --git a/check_test.go b/check_test.go index 9715394c..8c422914 100644 --- a/check_test.go +++ b/check_test.go @@ -3,6 +3,7 @@ package resource_test import ( "testing" + "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" resource "github.com/telia-oss/github-pr-resource" "github.com/telia-oss/github-pr-resource/fakes" @@ -10,15 +11,18 @@ import ( var ( testPullRequests = []*resource.PullRequest{ - createTestPR(1, "master", true, false, 0, nil), - createTestPR(2, "master", false, false, 0, nil), - createTestPR(3, "master", false, false, 0, nil), - createTestPR(4, "master", false, false, 0, nil), - createTestPR(5, "master", false, true, 0, nil), - createTestPR(6, "master", false, false, 0, nil), - createTestPR(7, "develop", false, false, 0, []string{"enhancement"}), - createTestPR(8, "master", false, false, 1, []string{"wontfix"}), - createTestPR(9, "master", false, false, 0, nil), + createTestPR(1, "master", true, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(2, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(3, "master", false, false, 0, nil, true, githubv4.PullRequestStateOpen), + createTestPR(4, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(5, "master", false, true, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(6, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(7, "develop", false, false, 0, []string{"enhancement"}, false, githubv4.PullRequestStateOpen), + createTestPR(8, "master", false, false, 1, []string{"wontfix"}, false, githubv4.PullRequestStateOpen), + createTestPR(9, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + createTestPR(10, "master", false, false, 0, nil, false, githubv4.PullRequestStateClosed), + createTestPR(11, "master", false, false, 0, nil, false, githubv4.PullRequestStateMerged), + createTestPR(12, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), } ) @@ -126,6 +130,35 @@ func TestCheck(t *testing.T) { }, }, + { + description: "check correctly ignores drafts when drafts are ignored", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + IgnoreDrafts: true, + }, + version: resource.NewVersion(testPullRequests[3]), + pullRequests: testPullRequests, + expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[1]), + }, + }, + + { + description: "check does not ignore drafts when drafts are not ignored", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + IgnoreDrafts: false, + }, + version: resource.NewVersion(testPullRequests[3]), + pullRequests: testPullRequests, + expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[2]), + resource.NewVersion(testPullRequests[1]), + }, + }, + { description: "check correctly ignores cross repo pull requests", source: resource.Source{ @@ -185,12 +218,69 @@ func TestCheck(t *testing.T) { resource.NewVersion(testPullRequests[6]), }, }, + + { + description: "check returns latest version from a PR with a single state filter", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + States: []githubv4.PullRequestState{githubv4.PullRequestStateClosed}, + }, + version: resource.Version{}, + pullRequests: testPullRequests, + files: [][]string{}, + expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[9]), + }, + }, + + { + description: "check filters out versions from a PR which do not match the state filter", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + States: []githubv4.PullRequestState{githubv4.PullRequestStateOpen}, + }, + version: resource.Version{}, + pullRequests: testPullRequests[9:11], + files: [][]string{}, + expected: resource.CheckResponse(nil), + }, + + { + description: "check returns versions from a PR with multiple state filters", + source: resource.Source{ + Repository: "itsdalmo/test-repository", + AccessToken: "oauthtoken", + States: []githubv4.PullRequestState{githubv4.PullRequestStateClosed, githubv4.PullRequestStateMerged}, + }, + version: resource.NewVersion(testPullRequests[11]), + pullRequests: testPullRequests, + files: [][]string{}, + expected: resource.CheckResponse{ + resource.NewVersion(testPullRequests[9]), + resource.NewVersion(testPullRequests[10]), + }, + }, } for _, tc := range tests { t.Run(tc.description, func(t *testing.T) { github := new(fakes.FakeGithub) - github.ListOpenPullRequestsReturns(tc.pullRequests, nil) + pullRequests := []*resource.PullRequest{} + filterStates := []githubv4.PullRequestState{githubv4.PullRequestStateOpen} + if len(tc.source.States) > 0 { + filterStates = tc.source.States + } + for i := range tc.pullRequests { + for j := range filterStates { + if filterStates[j] == tc.pullRequests[i].PullRequestObject.State { + pullRequests = append(pullRequests, tc.pullRequests[i]) + break + } + } + } + github.ListPullRequestsReturns(pullRequests, nil) for i, file := range tc.files { github.ListModifiedFilesReturnsOnCall(i, file, nil) @@ -202,7 +292,7 @@ func TestCheck(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, tc.expected, output) } - assert.Equal(t, 1, github.ListOpenPullRequestsCallCount()) + assert.Equal(t, 1, github.ListPullRequestsCallCount()) }) } } diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index e9b5afba..523838c2 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -35,7 +35,6 @@ var ( ) func TestCheckE2E(t *testing.T) { - tests := []struct { description string source resource.Source @@ -256,7 +255,7 @@ func TestGetAndPutE2E(t *testing.T) { getParameters: resource.GetParameters{}, putParameters: resource.PutParameters{}, versionString: `{"pr":"4","commit":"a5114f6ab89f4b736655642a11e8d15ce363d882","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"4"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"4"},{"name":"title","value":"Add comment from 2nd pull request."},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, metadataFiles: map[string]string{ "pr": "4", "url": "https://github.com/itsdalmo/test-repository/pull/4", @@ -288,7 +287,7 @@ func TestGetAndPutE2E(t *testing.T) { }, putParameters: resource.PutParameters{}, versionString: `{"pr":"4","commit":"a5114f6ab89f4b736655642a11e8d15ce363d882","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"4"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"4"},{"name":"title","value":"Add comment from 2nd pull request."},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, expectedCommitCount: 9, expectedCommits: []string{"Push 2."}, }, @@ -310,7 +309,7 @@ func TestGetAndPutE2E(t *testing.T) { }, putParameters: resource.PutParameters{}, versionString: `{"pr":"4","commit":"a5114f6ab89f4b736655642a11e8d15ce363d882","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"4"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"4"},{"name":"title","value":"Add comment from 2nd pull request."},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, expectedCommitCount: 7, expectedCommits: []string{ "Push 2.", @@ -338,7 +337,7 @@ func TestGetAndPutE2E(t *testing.T) { getParameters: resource.GetParameters{}, putParameters: resource.PutParameters{}, versionString: `{"pr":"6","commit":"ac771f3b69cbd63b22bbda553f827ab36150c640","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"6"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/6"},{"name":"head_name","value":"test-develop-pr"},{"name":"head_sha","value":"ac771f3b69cbd63b22bbda553f827ab36150c640"},{"name":"base_name","value":"develop"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"[skip ci] Add a PR with a non-master base"},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"6"},{"name":"title","value":"[skip ci] Add a PR with a non-master base"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/6"},{"name":"head_name","value":"test-develop-pr"},{"name":"head_sha","value":"ac771f3b69cbd63b22bbda553f827ab36150c640"},{"name":"base_name","value":"develop"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"[skip ci] Add a PR with a non-master base"},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, expectedCommitCount: 5, expectedCommits: []string{"[skip ci] Add a PR with a non-master base"}, // This merge ends up being fast-forwarded }, @@ -359,7 +358,7 @@ func TestGetAndPutE2E(t *testing.T) { getParameters: resource.GetParameters{}, putParameters: resource.PutParameters{}, versionString: `{"pr":"4","commit":"a5114f6ab89f4b736655642a11e8d15ce363d882","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"4"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"4"},{"name":"title","value":"Add comment from 2nd pull request."},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, expectedCommitCount: 10, expectedCommits: []string{"Merge commit 'a5114f6ab89f4b736655642a11e8d15ce363d882'"}, }, @@ -377,7 +376,7 @@ func TestGetAndPutE2E(t *testing.T) { getParameters: resource.GetParameters{GitDepth: 6}, putParameters: resource.PutParameters{}, versionString: `{"pr":"4","commit":"a5114f6ab89f4b736655642a11e8d15ce363d882","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"4"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"4"},{"name":"title","value":"Add comment from 2nd pull request."},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, expectedCommitCount: 9, expectedCommits: []string{ "Merge commit 'a5114f6ab89f4b736655642a11e8d15ce363d882'", @@ -407,7 +406,7 @@ func TestGetAndPutE2E(t *testing.T) { }, putParameters: resource.PutParameters{}, versionString: `{"pr":"4","commit":"a5114f6ab89f4b736655642a11e8d15ce363d882","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"4"},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"}]`, + metadataString: `[{"name":"pr","value":"4"},{"name":"title","value":"Add comment from 2nd pull request."},{"name":"url","value":"https://github.com/itsdalmo/test-repository/pull/4"},{"name":"head_name","value":"my_second_pull"},{"name":"head_sha","value":"a5114f6ab89f4b736655642a11e8d15ce363d882"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"93eeeedb8a16e6662062d1eca5655108977cc59a"},{"name":"message","value":"Push 2."},{"name":"author","value":"itsdalmo"},{"name":"author_email","value":"kristian@doingit.no"}]`, filesString: "README.md\ntest.txt\n", expectedCommitCount: 10, expectedCommits: []string{"Merge commit 'a5114f6ab89f4b736655642a11e8d15ce363d882'"}, @@ -472,12 +471,89 @@ func TestGetAndPutE2E(t *testing.T) { } } +func TestGetSubmodules(t *testing.T) { + tests := []struct { + description string + source resource.Source + version resource.Version + getParameters resource.GetParameters + expectedFiles []string + }{ + { + description: "get works with submodules", + source: resource.Source{ + Repository: "itsdalmo/test-repository-active", + AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), + }, + version: resource.Version{ + PR: "4", + Commit: "49398613d1f23d14518aadf6023cddba5db649ee", + }, + getParameters: resource.GetParameters{ + Submodules: true, + }, + expectedFiles: []string{ + ".git", + "README.md", + "latest-test.txt", + "new-test.txt", + "pipeline.yml", + "test.txt", + }, + }, + { + description: "submodules are optional", + source: resource.Source{ + Repository: "itsdalmo/test-repository-active", + AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), + }, + version: resource.Version{ + PR: "4", + Commit: "49398613d1f23d14518aadf6023cddba5db649ee", + }, + getParameters: resource.GetParameters{ + Submodules: false, + }, + expectedFiles: []string{}, + }, + } + + for _, tc := range tests { + t.Run(tc.description, func(t *testing.T) { + // Create temporary directory + dir, err := ioutil.TempDir("", "github-pr-resource") + require.NoError(t, err) + defer os.RemoveAll(dir) + + githubClient, err := resource.NewGithubClient(&tc.source) + require.NoError(t, err) + + git, err := resource.NewGitClient(&tc.source, dir, ioutil.Discard) + require.NoError(t, err) + + // Get (output and files) + getRequest := resource.GetRequest{Source: tc.source, Version: tc.version, Params: tc.getParameters} + _, err = resource.Get(getRequest, githubClient, git, dir) + require.NoError(t, err) + + files, err := ioutil.ReadDir(filepath.Join(dir, "submodule")) + require.NoError(t, err) + + for _, f := range files { + assert.Contains(t, tc.expectedFiles, f.Name()) + } + }) + } +} + func TestPutCommentsE2E(t *testing.T) { - owner := "itsdalmo" - repo := "github-pr-resource-e2e" + var ( + owner = "itsdalmo" + repository = "test-repository-active" + ) tests := []struct { - description, branch string + description string source resource.Source getParams resource.GetParameters putParameters resource.PutParameters @@ -485,9 +561,8 @@ func TestPutCommentsE2E(t *testing.T) { }{ { description: "delete previous comments removes old comments and makes new one", - branch: "delete-previous-comments-remove-old-add-new", source: resource.Source{ - Repository: fmt.Sprintf("%s/%s", owner, repo), + Repository: fmt.Sprintf("%s/%s", owner, repository), V3Endpoint: "https://api.github.com/", V4Endpoint: "https://api.github.com/graphql", AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), @@ -504,9 +579,8 @@ func TestPutCommentsE2E(t *testing.T) { }, { description: "delete previous comments removes all comments when no new comment", - branch: "delete-previous-comments-remove-old", source: resource.Source{ - Repository: fmt.Sprintf("%s/%s", owner, repo), + Repository: fmt.Sprintf("%s/%s", owner, repository), V3Endpoint: "https://api.github.com/", V4Endpoint: "https://api.github.com/graphql", AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), @@ -520,9 +594,8 @@ func TestPutCommentsE2E(t *testing.T) { }, { description: "delete previous comments should not delete comments when false", - branch: "delete-previous-comments-false", source: resource.Source{ - Repository: fmt.Sprintf("%s/%s", owner, repo), + Repository: fmt.Sprintf("%s/%s", owner, repository), V3Endpoint: "https://api.github.com/", V4Endpoint: "https://api.github.com/graphql", AccessToken: os.Getenv("GITHUB_ACCESS_TOKEN"), @@ -552,15 +625,15 @@ func TestPutCommentsE2E(t *testing.T) { git, err := resource.NewGitClient(&tc.source, dir, ioutil.Discard) require.NoError(t, err) - pullRequest, _, err := githubClient.V3.PullRequests.Create(context.TODO(), owner, repo, &github.NewPullRequest{ + pullRequest, _, err := githubClient.V3.PullRequests.Create(context.TODO(), owner, repository, &github.NewPullRequest{ Title: github.String(tc.description), Base: github.String("master"), - Head: github.String(fmt.Sprintf("%s:%s", owner, tc.branch)), + Head: github.String(fmt.Sprintf("%s:%s", owner, "test-comments")), }) require.NoError(t, err) for _, comment := range tc.previousComments { - _, _, err = githubClient.V3.Issues.CreateComment(context.TODO(), owner, repo, pullRequest.GetNumber(), &github.IssueComment{ + _, _, err = githubClient.V3.Issues.CreateComment(context.TODO(), owner, repository, pullRequest.GetNumber(), &github.IssueComment{ Body: github.String(comment), }) require.NoError(t, err) @@ -581,7 +654,7 @@ func TestPutCommentsE2E(t *testing.T) { _, err = resource.Put(putRequest, githubClient, dir) require.NoError(t, err) - comments, _, err := githubClient.V3.Issues.ListComments(context.TODO(), owner, repo, pullRequest.GetNumber(), nil) + comments, _, err := githubClient.V3.Issues.ListComments(context.TODO(), owner, repository, pullRequest.GetNumber(), nil) require.NoError(t, err) require.Len(t, comments, len(tc.expectedComments)) @@ -589,7 +662,7 @@ func TestPutCommentsE2E(t *testing.T) { require.Equal(t, tc.expectedComments[index], comment.GetBody()) } - _, _, err = githubClient.V3.PullRequests.Edit(context.TODO(), owner, repo, pullRequest.GetNumber(), &github.PullRequest{ + _, _, err = githubClient.V3.PullRequests.Edit(context.TODO(), owner, repository, pullRequest.GetNumber(), &github.PullRequest{ State: github.String("closed"), }) require.NoError(t, err) diff --git a/fakes/fake_git.go b/fakes/fake_git.go index 9971dfa5..2a395475 100644 --- a/fakes/fake_git.go +++ b/fakes/fake_git.go @@ -8,11 +8,12 @@ import ( ) type FakeGit struct { - CheckoutStub func(string, string) error + CheckoutStub func(string, string, bool) error checkoutMutex sync.RWMutex checkoutArgsForCall []struct { arg1 string arg2 string + arg3 bool } checkoutReturns struct { result1 error @@ -20,12 +21,13 @@ type FakeGit struct { checkoutReturnsOnCall map[int]struct { result1 error } - FetchStub func(string, int, int) error + FetchStub func(string, int, int, bool) error fetchMutex sync.RWMutex fetchArgsForCall []struct { arg1 string arg2 int arg3 int + arg4 bool } fetchReturns struct { result1 error @@ -55,10 +57,11 @@ type FakeGit struct { initReturnsOnCall map[int]struct { result1 error } - MergeStub func(string) error + MergeStub func(string, bool) error mergeMutex sync.RWMutex mergeArgsForCall []struct { arg1 string + arg2 bool } mergeReturns struct { result1 error @@ -66,12 +69,14 @@ type FakeGit struct { mergeReturnsOnCall map[int]struct { result1 error } - PullStub func(string, string, int) error + PullStub func(string, string, int, bool, bool) error pullMutex sync.RWMutex pullArgsForCall []struct { arg1 string arg2 string arg3 int + arg4 bool + arg5 bool } pullReturns struct { result1 error @@ -79,11 +84,12 @@ type FakeGit struct { pullReturnsOnCall map[int]struct { result1 error } - RebaseStub func(string, string) error + RebaseStub func(string, string, bool) error rebaseMutex sync.RWMutex rebaseArgsForCall []struct { arg1 string arg2 string + arg3 bool } rebaseReturns struct { result1 error @@ -108,17 +114,18 @@ type FakeGit struct { invocationsMutex sync.RWMutex } -func (fake *FakeGit) Checkout(arg1 string, arg2 string) error { +func (fake *FakeGit) Checkout(arg1 string, arg2 string, arg3 bool) error { fake.checkoutMutex.Lock() ret, specificReturn := fake.checkoutReturnsOnCall[len(fake.checkoutArgsForCall)] fake.checkoutArgsForCall = append(fake.checkoutArgsForCall, struct { arg1 string arg2 string - }{arg1, arg2}) - fake.recordInvocation("Checkout", []interface{}{arg1, arg2}) + arg3 bool + }{arg1, arg2, arg3}) + fake.recordInvocation("Checkout", []interface{}{arg1, arg2, arg3}) fake.checkoutMutex.Unlock() if fake.CheckoutStub != nil { - return fake.CheckoutStub(arg1, arg2) + return fake.CheckoutStub(arg1, arg2, arg3) } if specificReturn { return ret.result1 @@ -133,17 +140,17 @@ func (fake *FakeGit) CheckoutCallCount() int { return len(fake.checkoutArgsForCall) } -func (fake *FakeGit) CheckoutCalls(stub func(string, string) error) { +func (fake *FakeGit) CheckoutCalls(stub func(string, string, bool) error) { fake.checkoutMutex.Lock() defer fake.checkoutMutex.Unlock() fake.CheckoutStub = stub } -func (fake *FakeGit) CheckoutArgsForCall(i int) (string, string) { +func (fake *FakeGit) CheckoutArgsForCall(i int) (string, string, bool) { fake.checkoutMutex.RLock() defer fake.checkoutMutex.RUnlock() argsForCall := fake.checkoutArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeGit) CheckoutReturns(result1 error) { @@ -169,18 +176,19 @@ func (fake *FakeGit) CheckoutReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeGit) Fetch(arg1 string, arg2 int, arg3 int) error { +func (fake *FakeGit) Fetch(arg1 string, arg2 int, arg3 int, arg4 bool) error { fake.fetchMutex.Lock() ret, specificReturn := fake.fetchReturnsOnCall[len(fake.fetchArgsForCall)] fake.fetchArgsForCall = append(fake.fetchArgsForCall, struct { arg1 string arg2 int arg3 int - }{arg1, arg2, arg3}) - fake.recordInvocation("Fetch", []interface{}{arg1, arg2, arg3}) + arg4 bool + }{arg1, arg2, arg3, arg4}) + fake.recordInvocation("Fetch", []interface{}{arg1, arg2, arg3, arg4}) fake.fetchMutex.Unlock() if fake.FetchStub != nil { - return fake.FetchStub(arg1, arg2, arg3) + return fake.FetchStub(arg1, arg2, arg3, arg4) } if specificReturn { return ret.result1 @@ -195,17 +203,17 @@ func (fake *FakeGit) FetchCallCount() int { return len(fake.fetchArgsForCall) } -func (fake *FakeGit) FetchCalls(stub func(string, int, int) error) { +func (fake *FakeGit) FetchCalls(stub func(string, int, int, bool) error) { fake.fetchMutex.Lock() defer fake.fetchMutex.Unlock() fake.FetchStub = stub } -func (fake *FakeGit) FetchArgsForCall(i int) (string, int, int) { +func (fake *FakeGit) FetchArgsForCall(i int) (string, int, int, bool) { fake.fetchMutex.RLock() defer fake.fetchMutex.RUnlock() argsForCall := fake.fetchArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } func (fake *FakeGit) FetchReturns(result1 error) { @@ -351,16 +359,17 @@ func (fake *FakeGit) InitReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeGit) Merge(arg1 string) error { +func (fake *FakeGit) Merge(arg1 string, arg2 bool) error { fake.mergeMutex.Lock() ret, specificReturn := fake.mergeReturnsOnCall[len(fake.mergeArgsForCall)] fake.mergeArgsForCall = append(fake.mergeArgsForCall, struct { arg1 string - }{arg1}) - fake.recordInvocation("Merge", []interface{}{arg1}) + arg2 bool + }{arg1, arg2}) + fake.recordInvocation("Merge", []interface{}{arg1, arg2}) fake.mergeMutex.Unlock() if fake.MergeStub != nil { - return fake.MergeStub(arg1) + return fake.MergeStub(arg1, arg2) } if specificReturn { return ret.result1 @@ -375,17 +384,17 @@ func (fake *FakeGit) MergeCallCount() int { return len(fake.mergeArgsForCall) } -func (fake *FakeGit) MergeCalls(stub func(string) error) { +func (fake *FakeGit) MergeCalls(stub func(string, bool) error) { fake.mergeMutex.Lock() defer fake.mergeMutex.Unlock() fake.MergeStub = stub } -func (fake *FakeGit) MergeArgsForCall(i int) string { +func (fake *FakeGit) MergeArgsForCall(i int) (string, bool) { fake.mergeMutex.RLock() defer fake.mergeMutex.RUnlock() argsForCall := fake.mergeArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeGit) MergeReturns(result1 error) { @@ -411,18 +420,20 @@ func (fake *FakeGit) MergeReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeGit) Pull(arg1 string, arg2 string, arg3 int) error { +func (fake *FakeGit) Pull(arg1 string, arg2 string, arg3 int, arg4 bool, arg5 bool) error { fake.pullMutex.Lock() ret, specificReturn := fake.pullReturnsOnCall[len(fake.pullArgsForCall)] fake.pullArgsForCall = append(fake.pullArgsForCall, struct { arg1 string arg2 string arg3 int - }{arg1, arg2, arg3}) - fake.recordInvocation("Pull", []interface{}{arg1, arg2, arg3}) + arg4 bool + arg5 bool + }{arg1, arg2, arg3, arg4, arg5}) + fake.recordInvocation("Pull", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.pullMutex.Unlock() if fake.PullStub != nil { - return fake.PullStub(arg1, arg2, arg3) + return fake.PullStub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1 @@ -437,17 +448,17 @@ func (fake *FakeGit) PullCallCount() int { return len(fake.pullArgsForCall) } -func (fake *FakeGit) PullCalls(stub func(string, string, int) error) { +func (fake *FakeGit) PullCalls(stub func(string, string, int, bool, bool) error) { fake.pullMutex.Lock() defer fake.pullMutex.Unlock() fake.PullStub = stub } -func (fake *FakeGit) PullArgsForCall(i int) (string, string, int) { +func (fake *FakeGit) PullArgsForCall(i int) (string, string, int, bool, bool) { fake.pullMutex.RLock() defer fake.pullMutex.RUnlock() argsForCall := fake.pullArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *FakeGit) PullReturns(result1 error) { @@ -473,17 +484,18 @@ func (fake *FakeGit) PullReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeGit) Rebase(arg1 string, arg2 string) error { +func (fake *FakeGit) Rebase(arg1 string, arg2 string, arg3 bool) error { fake.rebaseMutex.Lock() ret, specificReturn := fake.rebaseReturnsOnCall[len(fake.rebaseArgsForCall)] fake.rebaseArgsForCall = append(fake.rebaseArgsForCall, struct { arg1 string arg2 string - }{arg1, arg2}) - fake.recordInvocation("Rebase", []interface{}{arg1, arg2}) + arg3 bool + }{arg1, arg2, arg3}) + fake.recordInvocation("Rebase", []interface{}{arg1, arg2, arg3}) fake.rebaseMutex.Unlock() if fake.RebaseStub != nil { - return fake.RebaseStub(arg1, arg2) + return fake.RebaseStub(arg1, arg2, arg3) } if specificReturn { return ret.result1 @@ -498,17 +510,17 @@ func (fake *FakeGit) RebaseCallCount() int { return len(fake.rebaseArgsForCall) } -func (fake *FakeGit) RebaseCalls(stub func(string, string) error) { +func (fake *FakeGit) RebaseCalls(stub func(string, string, bool) error) { fake.rebaseMutex.Lock() defer fake.rebaseMutex.Unlock() fake.RebaseStub = stub } -func (fake *FakeGit) RebaseArgsForCall(i int) (string, string) { +func (fake *FakeGit) RebaseArgsForCall(i int) (string, string, bool) { fake.rebaseMutex.RLock() defer fake.rebaseMutex.RUnlock() argsForCall := fake.rebaseArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeGit) RebaseReturns(result1 error) { diff --git a/fakes/fake_github.go b/fakes/fake_github.go index ac8242a6..1847478f 100644 --- a/fakes/fake_github.go +++ b/fakes/fake_github.go @@ -4,6 +4,7 @@ package fakes import ( "sync" + "github.com/shurcooL/githubv4" resource "github.com/telia-oss/github-pr-resource" ) @@ -60,15 +61,16 @@ type FakeGithub struct { result1 []string result2 error } - ListOpenPullRequestsStub func() ([]*resource.PullRequest, error) - listOpenPullRequestsMutex sync.RWMutex - listOpenPullRequestsArgsForCall []struct { + ListPullRequestsStub func([]githubv4.PullRequestState) ([]*resource.PullRequest, error) + listPullRequestsMutex sync.RWMutex + listPullRequestsArgsForCall []struct { + arg1 []githubv4.PullRequestState } - listOpenPullRequestsReturns struct { + listPullRequestsReturns struct { result1 []*resource.PullRequest result2 error } - listOpenPullRequestsReturnsOnCall map[int]struct { + listPullRequestsReturnsOnCall map[int]struct { result1 []*resource.PullRequest result2 error } @@ -355,56 +357,69 @@ func (fake *FakeGithub) ListModifiedFilesReturnsOnCall(i int, result1 []string, }{result1, result2} } -func (fake *FakeGithub) ListOpenPullRequests() ([]*resource.PullRequest, error) { - fake.listOpenPullRequestsMutex.Lock() - ret, specificReturn := fake.listOpenPullRequestsReturnsOnCall[len(fake.listOpenPullRequestsArgsForCall)] - fake.listOpenPullRequestsArgsForCall = append(fake.listOpenPullRequestsArgsForCall, struct { - }{}) - fake.recordInvocation("ListOpenPullRequests", []interface{}{}) - fake.listOpenPullRequestsMutex.Unlock() - if fake.ListOpenPullRequestsStub != nil { - return fake.ListOpenPullRequestsStub() +func (fake *FakeGithub) ListPullRequests(arg1 []githubv4.PullRequestState) ([]*resource.PullRequest, error) { + var arg1Copy []githubv4.PullRequestState + if arg1 != nil { + arg1Copy = make([]githubv4.PullRequestState, len(arg1)) + copy(arg1Copy, arg1) + } + fake.listPullRequestsMutex.Lock() + ret, specificReturn := fake.listPullRequestsReturnsOnCall[len(fake.listPullRequestsArgsForCall)] + fake.listPullRequestsArgsForCall = append(fake.listPullRequestsArgsForCall, struct { + arg1 []githubv4.PullRequestState + }{arg1Copy}) + fake.recordInvocation("ListPullRequests", []interface{}{arg1Copy}) + fake.listPullRequestsMutex.Unlock() + if fake.ListPullRequestsStub != nil { + return fake.ListPullRequestsStub(arg1) } if specificReturn { return ret.result1, ret.result2 } - fakeReturns := fake.listOpenPullRequestsReturns + fakeReturns := fake.listPullRequestsReturns return fakeReturns.result1, fakeReturns.result2 } -func (fake *FakeGithub) ListOpenPullRequestsCallCount() int { - fake.listOpenPullRequestsMutex.RLock() - defer fake.listOpenPullRequestsMutex.RUnlock() - return len(fake.listOpenPullRequestsArgsForCall) +func (fake *FakeGithub) ListPullRequestsCallCount() int { + fake.listPullRequestsMutex.RLock() + defer fake.listPullRequestsMutex.RUnlock() + return len(fake.listPullRequestsArgsForCall) +} + +func (fake *FakeGithub) ListPullRequestsCalls(stub func([]githubv4.PullRequestState) ([]*resource.PullRequest, error)) { + fake.listPullRequestsMutex.Lock() + defer fake.listPullRequestsMutex.Unlock() + fake.ListPullRequestsStub = stub } -func (fake *FakeGithub) ListOpenPullRequestsCalls(stub func() ([]*resource.PullRequest, error)) { - fake.listOpenPullRequestsMutex.Lock() - defer fake.listOpenPullRequestsMutex.Unlock() - fake.ListOpenPullRequestsStub = stub +func (fake *FakeGithub) ListPullRequestsArgsForCall(i int) []githubv4.PullRequestState { + fake.listPullRequestsMutex.RLock() + defer fake.listPullRequestsMutex.RUnlock() + argsForCall := fake.listPullRequestsArgsForCall[i] + return argsForCall.arg1 } -func (fake *FakeGithub) ListOpenPullRequestsReturns(result1 []*resource.PullRequest, result2 error) { - fake.listOpenPullRequestsMutex.Lock() - defer fake.listOpenPullRequestsMutex.Unlock() - fake.ListOpenPullRequestsStub = nil - fake.listOpenPullRequestsReturns = struct { +func (fake *FakeGithub) ListPullRequestsReturns(result1 []*resource.PullRequest, result2 error) { + fake.listPullRequestsMutex.Lock() + defer fake.listPullRequestsMutex.Unlock() + fake.ListPullRequestsStub = nil + fake.listPullRequestsReturns = struct { result1 []*resource.PullRequest result2 error }{result1, result2} } -func (fake *FakeGithub) ListOpenPullRequestsReturnsOnCall(i int, result1 []*resource.PullRequest, result2 error) { - fake.listOpenPullRequestsMutex.Lock() - defer fake.listOpenPullRequestsMutex.Unlock() - fake.ListOpenPullRequestsStub = nil - if fake.listOpenPullRequestsReturnsOnCall == nil { - fake.listOpenPullRequestsReturnsOnCall = make(map[int]struct { +func (fake *FakeGithub) ListPullRequestsReturnsOnCall(i int, result1 []*resource.PullRequest, result2 error) { + fake.listPullRequestsMutex.Lock() + defer fake.listPullRequestsMutex.Unlock() + fake.ListPullRequestsStub = nil + if fake.listPullRequestsReturnsOnCall == nil { + fake.listPullRequestsReturnsOnCall = make(map[int]struct { result1 []*resource.PullRequest result2 error }) } - fake.listOpenPullRequestsReturnsOnCall[i] = struct { + fake.listPullRequestsReturnsOnCall[i] = struct { result1 []*resource.PullRequest result2 error }{result1, result2} @@ -547,8 +562,8 @@ func (fake *FakeGithub) Invocations() map[string][][]interface{} { defer fake.getPullRequestMutex.RUnlock() fake.listModifiedFilesMutex.RLock() defer fake.listModifiedFilesMutex.RUnlock() - fake.listOpenPullRequestsMutex.RLock() - defer fake.listOpenPullRequestsMutex.RUnlock() + fake.listPullRequestsMutex.RLock() + defer fake.listPullRequestsMutex.RUnlock() fake.postCommentMutex.RLock() defer fake.postCommentMutex.RUnlock() fake.updateCommitStatusMutex.RLock() diff --git a/git.go b/git.go index 874b85f5..53f339d4 100644 --- a/git.go +++ b/git.go @@ -17,12 +17,12 @@ import ( //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o fakes/fake_git.go . Git type Git interface { Init(string) error - Pull(string, string, int) error + Pull(string, string, int, bool, bool) error RevParse(string) (string, error) - Fetch(string, int, int) error - Checkout(string, string) error - Merge(string) error - Rebase(string, string) error + Fetch(string, int, int, bool) error + Checkout(string, string, bool) error + Merge(string, bool) error + Rebase(string, string, bool) error GitCryptUnlock(string) error } @@ -31,6 +31,9 @@ func NewGitClient(source *Source, dir string, output io.Writer) (*GitClient, err if source.SkipSSLVerification { os.Setenv("GIT_SSL_NO_VERIFY", "true") } + if source.DisableGitLFS { + os.Setenv("GIT_LFS_SKIP_SMUDGE", "true") + } return &GitClient{ AccessToken: source.AccessToken, Directory: dir, @@ -50,6 +53,10 @@ func (g *GitClient) command(name string, arg ...string) *exec.Cmd { cmd.Dir = g.Directory cmd.Stdout = g.Output cmd.Stderr = g.Output + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, + "X_OAUTH_BASIC_TOKEN="+g.AccessToken, + "GIT_ASKPASS=/usr/local/bin/askpass.sh") return cmd } @@ -67,20 +74,36 @@ func (g *GitClient) Init(branch string) error { if err := g.command("git", "config", "user.email", "concourse@local").Run(); err != nil { return fmt.Errorf("failed to configure git email: %s", err) } + if err := g.command("git", "config", "url.https://x-oauth-basic@github.com/.insteadOf", "git@github.com:").Run(); err != nil { + return fmt.Errorf("failed to configure github url: %s", err) + } + if err := g.command("git", "config", "url.https://.insteadOf", "git://").Run(); err != nil { + return fmt.Errorf("failed to configure github url: %s", err) + } return nil } // Pull ... -func (g *GitClient) Pull(uri, branch string, depth int) error { +func (g *GitClient) Pull(uri, branch string, depth int, submodules bool, fetchTags bool) error { endpoint, err := g.Endpoint(uri) if err != nil { return err } - args := []string{"pull", endpoint + ".git", branch} + if err := g.command("git", "remote", "add", "origin", endpoint).Run(); err != nil { + return fmt.Errorf("setting 'origin' remote to '%s' failed: %s", endpoint, err) + } + + args := []string{"pull", "origin", branch} if depth > 0 { args = append(args, "--depth", strconv.Itoa(depth)) } + if fetchTags { + args = append(args, "--tags") + } + if submodules { + args = append(args, "--recurse-submodules") + } cmd := g.command("git", args...) // Discard output to have zero chance of logging the access token. @@ -88,7 +111,13 @@ func (g *GitClient) Pull(uri, branch string, depth int) error { cmd.Stderr = ioutil.Discard if err := cmd.Run(); err != nil { - return fmt.Errorf("clone failed: %s", err) + return fmt.Errorf("pull failed: %s", cmd) + } + if submodules { + submodulesGet := g.command("git", "submodule", "update", "--init", "--recursive") + if err := submodulesGet.Run(); err != nil { + return fmt.Errorf("submodule update failed: %s", err) + } } return nil } @@ -105,7 +134,7 @@ func (g *GitClient) RevParse(branch string) (string, error) { } // Fetch ... -func (g *GitClient) Fetch(uri string, prNumber int, depth int) error { +func (g *GitClient) Fetch(uri string, prNumber int, depth int, submodules bool) error { endpoint, err := g.Endpoint(uri) if err != nil { return err @@ -115,6 +144,9 @@ func (g *GitClient) Fetch(uri string, prNumber int, depth int) error { if depth > 0 { args = append(args, "--depth", strconv.Itoa(depth)) } + if submodules { + args = append(args, "--recurse-submodules") + } cmd := g.command("git", args...) // Discard output to have zero chance of logging the access token. @@ -128,27 +160,47 @@ func (g *GitClient) Fetch(uri string, prNumber int, depth int) error { } // CheckOut -func (g *GitClient) Checkout(branch, sha string) error { +func (g *GitClient) Checkout(branch, sha string, submodules bool) error { if err := g.command("git", "checkout", "-b", branch, sha).Run(); err != nil { return fmt.Errorf("checkout failed: %s", err) } + if submodules { + if err := g.command("git", "submodule", "update", "--init", "--recursive", "--checkout").Run(); err != nil { + return fmt.Errorf("submodule update failed: %s", err) + } + } + return nil } // Merge ... -func (g *GitClient) Merge(sha string) error { +func (g *GitClient) Merge(sha string, submodules bool) error { if err := g.command("git", "merge", sha, "--no-stat").Run(); err != nil { return fmt.Errorf("merge failed: %s", err) } + + if submodules { + if err := g.command("git", "submodule", "update", "--init", "--recursive", "--merge").Run(); err != nil { + return fmt.Errorf("submodule update failed: %s", err) + } + } + return nil } // Rebase ... -func (g *GitClient) Rebase(baseRef string, headSha string) error { +func (g *GitClient) Rebase(baseRef string, headSha string, submodules bool) error { if err := g.command("git", "rebase", baseRef, headSha).Run(); err != nil { return fmt.Errorf("rebase failed: %s", err) } + + if submodules { + if err := g.command("git", "submodule", "update", "--init", "--recursive", "--rebase").Run(); err != nil { + return fmt.Errorf("submodule update failed: %s", err) + } + } + return nil } @@ -164,7 +216,7 @@ func (g *GitClient) GitCryptUnlock(base64key string) error { return fmt.Errorf("failed to decode git-crypt key") } keyPath := filepath.Join(keyDir, "git-crypt-key") - if err := ioutil.WriteFile(keyPath, decodedKey, 600); err != nil { + if err := ioutil.WriteFile(keyPath, decodedKey, os.FileMode(0600)); err != nil { return fmt.Errorf("failed to write git-crypt key to file: %s", err) } if err := g.command("git-crypt", "unlock", keyPath).Run(); err != nil { diff --git a/github.go b/github.go index fbbe4734..ab10cbdc 100644 --- a/github.go +++ b/github.go @@ -20,7 +20,7 @@ import ( // Github for testing purposes. //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o fakes/fake_github.go . Github type Github interface { - ListOpenPullRequests() ([]*PullRequest, error) + ListPullRequests([]githubv4.PullRequestState) ([]*PullRequest, error) ListModifiedFiles(int) ([]string, error) PostComment(string, string) error GetPullRequest(string, string) (*PullRequest, error) @@ -97,8 +97,8 @@ func NewGithubClient(s *Source) (*GithubClient, error) { }, nil } -// ListOpenPullRequests gets the last commit on all open pull requests. -func (m *GithubClient) ListOpenPullRequests() ([]*PullRequest, error) { +// ListPullRequests gets the last commit on all pull requests with the matching state. +func (m *GithubClient) ListPullRequests(prStates []githubv4.PullRequestState) ([]*PullRequest, error) { var query struct { Repository struct { PullRequests struct { @@ -136,7 +136,7 @@ func (m *GithubClient) ListOpenPullRequests() ([]*PullRequest, error) { "repositoryOwner": githubv4.String(m.Owner), "repositoryName": githubv4.String(m.Repository), "prFirst": githubv4.Int(100), - "prStates": []githubv4.PullRequestState{githubv4.PullRequestStateOpen}, + "prStates": prStates, "prCursor": (*githubv4.String)(nil), "commitsLast": githubv4.Int(1), "prReviewStates": []githubv4.PullRequestReviewState{githubv4.PullRequestReviewStateApproved}, diff --git a/go.mod b/go.mod index fd7ed191..ef1f5ca4 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,17 @@ module github.com/telia-oss/github-pr-resource require ( - github.com/golang/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.4.0 // indirect github.com/google/go-github/v28 v28.1.1 - github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2 - github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0 + github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3 + github.com/shurcooL/githubv4 v0.0.0-20200414012201-bbc966b061dd github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect github.com/stretchr/testify v1.3.0 - golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 // indirect - golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect - golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 - google.golang.org/appengine v1.6.5 // indirect + golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 // indirect + golang.org/x/net v0.0.0-20200421231249-e086a090c8fd // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/tools v0.0.0-20200423205358-59e73619c742 // indirect + google.golang.org/appengine v1.6.6 // indirect ) -go 1.13 +go 1.14 diff --git a/go.sum b/go.sum index 281d7227..82ec24e7 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,16 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -21,29 +29,37 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2 h1:g+4J5sZg6osfvEfkRZxJ1em0VT95/UOZgi/l7zi1/oE= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3 h1:z1lXirM9f9WTcdmzSZahKh/t+LCqPiiwK2/DB1kLlI4= +github.com/maxbrunsfeld/counterfeiter/v6 v6.2.3/go.mod h1:1ftk08SazyElaaNvmqAfZWGwJzshjCfBXDLoQtPAMNk= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0 h1:T9uus1QvcPgeLShS30YOnnzk3r9Vvygp45muhlrufgY= -github.com/shurcooL/githubv4 v0.0.0-20191127044304-8f68eb5628d0/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= +github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= +github.com/shurcooL/githubv4 v0.0.0-20200414012201-bbc966b061dd h1:EwtC+kDj8s9OKiaStPZtTv3neldOyr98AXIxvmn3Gss= +github.com/shurcooL/githubv4 v0.0.0-20200414012201-bbc966b061dd/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk= github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 h1:Q7tZBpemrlsc2I7IyODzhtallWRSm4Q0d09pL6XbQtU= +golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -52,32 +68,49 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db h1:9hRk1xeL9LTT3yX/941DqeBz87XgHAQuj+TbimYJuiw= -golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200301222351-066e0c02454c h1:FD7jysxM+EJqg5UYYy3XYDsAiUickFsn4UiaanJkf8c= +golang.org/x/tools v0.0.0-20200301222351-066e0c02454c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200423205358-59e73619c742 h1:9OGWpORUXvk8AsaBJlpzzDx7Srv/rSK6rvjcsJq4rJo= +golang.org/x/tools v0.0.0-20200423205358-59e73619c742/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= @@ -86,7 +119,5 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/in.go b/in.go index c8ce9d1b..fa0eb242 100644 --- a/in.go +++ b/in.go @@ -24,7 +24,7 @@ func Get(request GetRequest, github Github, git Git, outputDir string) (*GetResp if err := git.Init(pull.BaseRefName); err != nil { return nil, err } - if err := git.Pull(pull.Repository.URL, pull.BaseRefName, request.Params.GitDepth); err != nil { + if err := git.Pull(pull.Repository.URL, pull.BaseRefName, request.Params.GitDepth, request.Params.Submodules, request.Params.FetchTags); err != nil { return nil, err } @@ -35,36 +35,14 @@ func Get(request GetRequest, github Github, git Git, outputDir string) (*GetResp } // Fetch the PR and merge the specified commit into the base - if err := git.Fetch(pull.Repository.URL, pull.Number, request.Params.GitDepth); err != nil { + if err := git.Fetch(pull.Repository.URL, pull.Number, request.Params.GitDepth, request.Params.Submodules); err != nil { return nil, err } - switch tool := request.Params.IntegrationTool; tool { - case "rebase": - if err := git.Rebase(pull.BaseRefName, pull.Tip.OID); err != nil { - return nil, err - } - case "merge", "": - if err := git.Merge(pull.Tip.OID); err != nil { - return nil, err - } - case "checkout": - if err := git.Checkout(pull.HeadRefName, pull.Tip.OID); err != nil { - return nil, err - } - default: - return nil, fmt.Errorf("invalid integration tool specified: %s", tool) - } - - if request.Source.GitCryptKey != "" { - if err := git.GitCryptUnlock(request.Source.GitCryptKey); err != nil { - return nil, err - } - } - // Create the metadata var metadata Metadata metadata.Add("pr", strconv.Itoa(pull.Number)) + metadata.Add("title", pull.Title) metadata.Add("url", pull.URL) metadata.Add("head_name", pull.HeadRefName) metadata.Add("head_sha", pull.Tip.OID) @@ -72,6 +50,8 @@ func Get(request GetRequest, github Github, git Git, outputDir string) (*GetResp metadata.Add("base_sha", baseSHA) metadata.Add("message", pull.Tip.Message) metadata.Add("author", pull.Tip.Author.User.Login) + metadata.Add("author_email", pull.Tip.Author.Email) + metadata.Add("state", string(pull.State)) // Write version and metadata for reuse in PUT path := filepath.Join(outputDir, ".git", "resource") @@ -99,7 +79,29 @@ func Get(request GetRequest, github Github, git Git, outputDir string) (*GetResp if err := ioutil.WriteFile(filepath.Join(path, filename), content, 0644); err != nil { return nil, fmt.Errorf("failed to write metadata file %s: %s", filename, err) } + } + switch tool := request.Params.IntegrationTool; tool { + case "rebase": + if err := git.Rebase(pull.BaseRefName, pull.Tip.OID, request.Params.Submodules); err != nil { + return nil, err + } + case "merge", "": + if err := git.Merge(pull.Tip.OID, request.Params.Submodules); err != nil { + return nil, err + } + case "checkout": + if err := git.Checkout(pull.HeadRefName, pull.Tip.OID, request.Params.Submodules); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid integration tool specified: %s", tool) + } + + if request.Source.GitCryptKey != "" { + if err := git.GitCryptUnlock(request.Source.GitCryptKey); err != nil { + return nil, err + } } if request.Params.ListChangedFiles { @@ -131,7 +133,9 @@ type GetParameters struct { SkipDownload bool `json:"skip_download"` IntegrationTool string `json:"integration_tool"` GitDepth int `json:"git_depth"` + Submodules bool `json:"submodules"` ListChangedFiles bool `json:"list_changed_files"` + FetchTags bool `json:"fetch_tags"` } // GetRequest ... diff --git a/in_test.go b/in_test.go index 80dd60ed..17e7abfc 100644 --- a/in_test.go +++ b/in_test.go @@ -35,14 +35,16 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + ApprovedReviewCount: "0", + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{}, - pullRequest: createTestPR(1, "master", false, false, 0, nil), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"1"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"}]`, + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { description: "get supports unlocking with git crypt", @@ -52,14 +54,16 @@ func TestGet(t *testing.T) { GitCryptKey: "gitcryptkey", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + ApprovedReviewCount: "0", + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{}, - pullRequest: createTestPR(1, "master", false, false, 0, nil), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"1"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"}]`, + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { description: "get supports rebasing", @@ -68,16 +72,18 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + ApprovedReviewCount: "0", + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ IntegrationTool: "rebase", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"1"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"}]`, + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { description: "get supports checkout", @@ -86,16 +92,18 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + ApprovedReviewCount: "0", + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ IntegrationTool: "checkout", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"1"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"}]`, + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { description: "get supports git_depth", @@ -104,16 +112,18 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + ApprovedReviewCount: "0", + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ GitDepth: 2, }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"1"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"}]`, + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, }, { description: "get supports list_changed_files", @@ -122,14 +132,16 @@ func TestGet(t *testing.T) { AccessToken: "oauthtoken", }, version: resource.Version{ - PR: "pr1", - Commit: "commit1", - CommittedDate: time.Time{}, + PR: "pr1", + Commit: "commit1", + CommittedDate: time.Time{}, + ApprovedReviewCount: "0", + State: githubv4.PullRequestStateOpen, }, parameters: resource.GetParameters{ ListChangedFiles: true, }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), files: []resource.ChangedFileObject{ { Path: "README.md", @@ -138,8 +150,8 @@ func TestGet(t *testing.T) { Path: "Other.md", }, }, - versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z"}`, - metadataString: `[{"name":"pr","value":"1"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"}]`, + versionString: `{"pr":"pr1","commit":"commit1","committed":"0001-01-01T00:00:00Z","approved_review_count":"0","state":"OPEN"}`, + metadataString: `[{"name":"pr","value":"1"},{"name":"title","value":"pr1 title"},{"name":"url","value":"pr1 url"},{"name":"head_name","value":"pr1"},{"name":"head_sha","value":"oid1"},{"name":"base_name","value":"master"},{"name":"base_sha","value":"sha"},{"name":"message","value":"commit message1"},{"name":"author","value":"login1"},{"name":"author_email","value":"user@example.com"},{"name":"state","value":"OPEN"}]`, filesString: "README.md\nOther.md\n", }, } @@ -175,14 +187,16 @@ func TestGet(t *testing.T) { // Verify individual files files := map[string]string{ - "pr": "1", - "url": "pr1 url", - "head_name": "pr1", - "head_sha": "oid1", - "base_name": "master", - "base_sha": "sha", - "message": "commit message1", - "author": "login1", + "pr": "1", + "url": "pr1 url", + "head_name": "pr1", + "head_sha": "oid1", + "base_name": "master", + "base_sha": "sha", + "message": "commit message1", + "author": "login1", + "author_email": "user@example.com", + "title": "pr1 title", } for filename, expected := range files { @@ -210,10 +224,12 @@ func TestGet(t *testing.T) { } if assert.Equal(t, 1, git.PullCallCount()) { - url, base, depth := git.PullArgsForCall(0) + url, base, depth, submodules, fetchTags := git.PullArgsForCall(0) assert.Equal(t, tc.pullRequest.Repository.URL, url) assert.Equal(t, tc.pullRequest.BaseRefName, base) assert.Equal(t, tc.parameters.GitDepth, depth) + assert.Equal(t, tc.parameters.Submodules, submodules) + assert.Equal(t, tc.parameters.FetchTags, fetchTags) } if assert.Equal(t, 1, git.RevParseCallCount()) { @@ -222,29 +238,33 @@ func TestGet(t *testing.T) { } if assert.Equal(t, 1, git.FetchCallCount()) { - url, pr, depth := git.FetchArgsForCall(0) + url, pr, depth, submodules := git.FetchArgsForCall(0) assert.Equal(t, tc.pullRequest.Repository.URL, url) assert.Equal(t, tc.pullRequest.Number, pr) assert.Equal(t, tc.parameters.GitDepth, depth) + assert.Equal(t, tc.parameters.Submodules, submodules) } switch tc.parameters.IntegrationTool { case "rebase": if assert.Equal(t, 1, git.RebaseCallCount()) { - branch, tip := git.RebaseArgsForCall(0) + branch, tip, submodules := git.RebaseArgsForCall(0) assert.Equal(t, tc.pullRequest.BaseRefName, branch) assert.Equal(t, tc.pullRequest.Tip.OID, tip) + assert.Equal(t, tc.parameters.Submodules, submodules) } case "checkout": if assert.Equal(t, 1, git.CheckoutCallCount()) { - branch, sha := git.CheckoutArgsForCall(0) + branch, sha, submodules := git.CheckoutArgsForCall(0) assert.Equal(t, tc.pullRequest.HeadRefName, branch) assert.Equal(t, tc.pullRequest.Tip.OID, sha) + assert.Equal(t, tc.parameters.Submodules, submodules) } default: if assert.Equal(t, 1, git.MergeCallCount()) { - tip := git.MergeArgsForCall(0) + tip, submodules := git.MergeArgsForCall(0) assert.Equal(t, tc.pullRequest.Tip.OID, tip) + assert.Equal(t, tc.parameters.Submodules, submodules) } } if tc.source.GitCryptKey != "" { @@ -305,6 +325,8 @@ func createTestPR( isCrossRepo bool, approvedReviews int, labels []string, + isDraft bool, + state githubv4.PullRequestState, ) *resource.PullRequest { n := strconv.Itoa(count) d := time.Now().AddDate(0, 0, -count) @@ -335,16 +357,24 @@ func createTestPR( URL: fmt.Sprintf("repo%s url", n), }, IsCrossRepository: isCrossRepo, + IsDraft: isDraft, + State: state, + ClosedAt: githubv4.DateTime{Time: time.Now()}, + MergedAt: githubv4.DateTime{Time: time.Now()}, }, Tip: resource.CommitObject{ ID: fmt.Sprintf("commit%s", n), OID: fmt.Sprintf("oid%s", n), CommittedDate: githubv4.DateTime{Time: d}, Message: m, - Author: struct{ User struct{ Login string } }{ + Author: struct { + User struct{ Login string } + Email string + }{ User: struct{ Login string }{ Login: fmt.Sprintf("login%s", n), }, + Email: "user@example.com", }, }, ApprovedReviewCount: approvedCount, diff --git a/models.go b/models.go index 71309e14..9e4e7b1c 100644 --- a/models.go +++ b/models.go @@ -2,6 +2,7 @@ package resource import ( "errors" + "fmt" "strconv" "time" @@ -10,19 +11,22 @@ import ( // Source represents the configuration for the resource. type Source struct { - Repository string `json:"repository"` - AccessToken string `json:"access_token"` - V3Endpoint string `json:"v3_endpoint"` - V4Endpoint string `json:"v4_endpoint"` - Paths []string `json:"paths"` - IgnorePaths []string `json:"ignore_paths"` - DisableCISkip bool `json:"disable_ci_skip"` - SkipSSLVerification bool `json:"skip_ssl_verification"` - DisableForks bool `json:"disable_forks"` - GitCryptKey string `json:"git_crypt_key"` - BaseBranch string `json:"base_branch"` - RequiredReviewApprovals int `json:"required_review_approvals"` - Labels []string `json:"labels"` + Repository string `json:"repository"` + AccessToken string `json:"access_token"` + V3Endpoint string `json:"v3_endpoint"` + V4Endpoint string `json:"v4_endpoint"` + Paths []string `json:"paths"` + IgnorePaths []string `json:"ignore_paths"` + DisableCISkip bool `json:"disable_ci_skip"` + DisableGitLFS bool `json:"disable_git_lfs"` + SkipSSLVerification bool `json:"skip_ssl_verification"` + DisableForks bool `json:"disable_forks"` + IgnoreDrafts bool `json:"ignore_drafts"` + GitCryptKey string `json:"git_crypt_key"` + BaseBranch string `json:"base_branch"` + RequiredReviewApprovals int `json:"required_review_approvals"` + Labels []string `json:"labels"` + States []githubv4.PullRequestState `json:"states"` } // Validate the source configuration. @@ -39,6 +43,15 @@ func (s *Source) Validate() error { if s.V4Endpoint != "" && s.V3Endpoint == "" { return errors.New("v3_endpoint must be set together with v4_endpoint") } + for _, state := range s.States { + switch state { + case githubv4.PullRequestStateOpen: + case githubv4.PullRequestStateClosed: + case githubv4.PullRequestStateMerged: + default: + return errors.New(fmt.Sprintf("states value \"%s\" must be one of: OPEN, MERGED, CLOSED", state)) + } + } return nil } @@ -58,17 +71,21 @@ type MetadataField struct { // Version communicated with Concourse. type Version struct { - PR string `json:"pr"` - Commit string `json:"commit"` - CommittedDate time.Time `json:"committed,omitempty"` + PR string `json:"pr"` + Commit string `json:"commit"` + CommittedDate time.Time `json:"committed,omitempty"` + ApprovedReviewCount string `json:"approved_review_count"` + State githubv4.PullRequestState `json:"state"` } // NewVersion constructs a new Version. func NewVersion(p *PullRequest) Version { return Version{ - PR: strconv.Itoa(p.Number), - Commit: p.Tip.OID, - CommittedDate: p.Tip.CommittedDate.Time, + PR: strconv.Itoa(p.Number), + Commit: p.Tip.OID, + CommittedDate: p.UpdatedDate().Time, + ApprovedReviewCount: strconv.Itoa(p.ApprovedReviewCount), + State: p.State, } } @@ -93,6 +110,23 @@ type PullRequestObject struct { URL string } IsCrossRepository bool + IsDraft bool + State githubv4.PullRequestState + ClosedAt githubv4.DateTime + MergedAt githubv4.DateTime +} + +// UpdatedDate returns the last time a PR was updated, either by commit +// or being closed/merged. +func (p *PullRequest) UpdatedDate() githubv4.DateTime { + date := p.Tip.CommittedDate + switch p.State { + case githubv4.PullRequestStateClosed: + date = p.ClosedAt + case githubv4.PullRequestStateMerged: + date = p.MergedAt + } + return date } // CommitObject represents the GraphQL commit node. @@ -106,6 +140,7 @@ type CommitObject struct { User struct { Login string } + Email string } } diff --git a/out.go b/out.go index bf2f3166..cd6245c9 100644 --- a/out.go +++ b/out.go @@ -49,7 +49,7 @@ func Put(request PutRequest, manager Github, inputDir string) (*PutResponse, err description = string(content) } - if err := manager.UpdateCommitStatus(version.Commit, p.BaseContext, p.Context, p.Status, safeExpandEnv(p.TargetURL), description); err != nil { + if err := manager.UpdateCommitStatus(version.Commit, p.BaseContext, safeExpandEnv(p.Context), p.Status, safeExpandEnv(p.TargetURL), description); err != nil { return nil, fmt.Errorf("failed to set status: %s", err) } } diff --git a/out_test.go b/out_test.go index b1fe56d7..4d430c7b 100644 --- a/out_test.go +++ b/out_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" resource "github.com/telia-oss/github-pr-resource" @@ -33,7 +34,7 @@ func TestPut(t *testing.T) { CommittedDate: time.Time{}, }, parameters: resource.PutParameters{}, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -50,7 +51,7 @@ func TestPut(t *testing.T) { parameters: resource.PutParameters{ Status: "success", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -68,7 +69,7 @@ func TestPut(t *testing.T) { Status: "failure", Context: "build", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -87,7 +88,7 @@ func TestPut(t *testing.T) { BaseContext: "concourse-ci-custom", Context: "build", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -105,7 +106,7 @@ func TestPut(t *testing.T) { Status: "failure", TargetURL: "https://targeturl.com/concourse", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -123,7 +124,7 @@ func TestPut(t *testing.T) { Status: "failure", Description: "Concourse CI build", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -140,7 +141,7 @@ func TestPut(t *testing.T) { parameters: resource.PutParameters{ Comment: "comment", }, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -157,7 +158,7 @@ func TestPut(t *testing.T) { parameters: resource.PutParameters{ DeletePreviousComments: true, }, - pullRequest: createTestPR(1, "master", false, false, 0, []string{}), + pullRequest: createTestPR(1, "master", false, false, 0, []string{}, false, githubv4.PullRequestStateOpen), }, } @@ -250,7 +251,7 @@ func TestVariableSubstitution(t *testing.T) { Comment: fmt.Sprintf("$%s", variableName), }, expectedComment: variableValue, - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -269,7 +270,7 @@ func TestVariableSubstitution(t *testing.T) { TargetURL: fmt.Sprintf("%s$%s", variableURL, variableName), }, expectedTargetURL: fmt.Sprintf("%s%s", variableURL, variableValue), - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, { @@ -287,7 +288,7 @@ func TestVariableSubstitution(t *testing.T) { Comment: "$THIS_IS_NOT_SUBSTITUTED", }, expectedComment: "$THIS_IS_NOT_SUBSTITUTED", - pullRequest: createTestPR(1, "master", false, false, 0, nil), + pullRequest: createTestPR(1, "master", false, false, 0, nil, false, githubv4.PullRequestStateOpen), }, } diff --git a/scripts/askpass.sh b/scripts/askpass.sh new file mode 100755 index 00000000..49916739 --- /dev/null +++ b/scripts/askpass.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec echo "$X_OAUTH_BASIC_TOKEN"