diff --git a/README.md b/README.md index 40c7ca80..12ce2213 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,15 @@ The Step scans for: * **Flutter** projects, the Step checks for the `pubspec.yaml` files. ### Configuring the Step - -To successfully run the Step, you need: -1. An SSH key + +To successfully run the Step, you need: +1. An SSH key or Git HTTP credentials 1. A repository To configure the Step: 1. **POST url to send the scan results to**: You can send your app's scan results to an URL as a POST request. -1. **URL to get app icon candidates upload URLs**: You can upload your app's icons using this input. +1. **URL to get app icon candidates upload URLs**: You can upload your app's icons using this input. 1. **Verbose log option**: You can set this input to `yes` to produce more informative logs. 1. **Activate SSH key and clone git repo inside the Step**: You can set this input to `true` to activate an SSH key and clone the git repository of your app. @@ -61,6 +61,8 @@ You can also run this step directly with [Bitrise CLI](https://github.com/bitris | `verbose_log` | You can enable the verbose log for easier debugging. | | `false` | | `enable_repo_clone` | If set to yes then it will setup the ssh key and will clone the repo with the provided url and branch name. | | `no` | | `ssh_rsa_private_key` | SSH key to be used for the git clone. | sensitive | `$SSH_RSA_PRIVATE_KEY` | +| `git_http_username` | Username for establishing an HTTP(S) connection to the repository | sensitive | `$GIT_HTTP_USERNAME` | +| `git_http_password` | Personal access token (or password) for establishing an HTTP(S) connection to the repository | sensitive | `$GIT_HTTP_PASSWORD` | | `repository_url` | Url to be used for the git clone. | | `$GIT_REPOSITORY_URL` | | `branch` | Branch to be used for the git clone. | | `$BITRISE_GIT_BRANCH` | </details> diff --git a/go.mod b/go.mod index 77cef9f3..5258f9f1 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.17 require ( github.com/bitrise-io/bitrise-init v0.0.0-20230615082523-d68c1dbf7b63 - github.com/bitrise-io/go-steputils v1.0.2 + github.com/bitrise-io/go-steputils v1.0.5 github.com/bitrise-io/go-utils v1.0.1 github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.18 github.com/bitrise-steplib/steps-activate-ssh-key v0.0.0-20210518131750-a0d69ff2d203 - github.com/bitrise-steplib/steps-git-clone v0.0.0-20230130163124-2237d7df95a9 + github.com/bitrise-steplib/steps-git-clone v0.0.0-20230619101604-67b0d018398a ) require ( @@ -22,6 +22,7 @@ require ( github.com/bitrise-io/go-xcode v1.0.6 // indirect github.com/bitrise-io/goinp v0.0.0-20211005113137-305e91b481f4 // indirect github.com/bitrise-io/stepman v0.0.0-20220107162322-d37bb82e8983 // indirect + github.com/bitrise-steplib/steps-authenticate-host-with-netrc v0.0.0-20230216105320-8cb845d52e28 // indirect github.com/gofrs/uuid v4.3.1+incompatible // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.1 // indirect diff --git a/go.sum b/go.sum index f3b02151..95c597bc 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/bitrise-io/bitrise-init v0.0.0-20210518121553-1e678625c45d/go.mod h1: github.com/bitrise-io/bitrise-init v0.0.0-20230615082523-d68c1dbf7b63 h1:/VyhHlqnd1c6+LjBS4hEq4+MllASrELQNW3R0UAdwAw= github.com/bitrise-io/bitrise-init v0.0.0-20230615082523-d68c1dbf7b63/go.mod h1:dfGAIaFZCssxdiogcGFQnAmcwOMsyId7dT3FMR7Ik1Y= github.com/bitrise-io/colorstring v0.0.0-20180614154802-a8cd70115192/go.mod h1:CIHVcxZUvsG99XUJV6JlR7okNsMMGY81jMvPC20W+O0= +github.com/bitrise-io/depman v0.0.0-20160708153333-4c59bc31f02a/go.mod h1:jeK3Koe+wXMSwfl8t6M7kovGgXsayUJ5ybqo3gDJgts= github.com/bitrise-io/envman v0.0.0-20200512105748-919e33f391ee/go.mod h1:m8pTp1o3Sw9uzDxb1WRm5IBRnMau2iOvPMSnRCAhQNI= github.com/bitrise-io/envman v0.0.0-20210517135508-b2b4fe89eac5/go.mod h1:m8pTp1o3Sw9uzDxb1WRm5IBRnMau2iOvPMSnRCAhQNI= github.com/bitrise-io/envman v0.0.0-20211026063720-03283f9c3f32 h1:obUCcqTUMTvk9ajXaJHjxE3MGA07btE0AUQGJi3OSG4= @@ -22,10 +23,11 @@ github.com/bitrise-io/go-plist v0.0.0-20210301100253-4b1a112ccd10/go.mod h1:pARu github.com/bitrise-io/go-steputils v0.0.0-20210507072936-92fde382fb33/go.mod h1:YCtb1VETn/rF9tCt9oInhd/cwbt1ETPm+dTlDIfyD+A= github.com/bitrise-io/go-steputils v0.0.0-20210514150206-5b6261447e77/go.mod h1:H0iZjgsAR5NA6pnlD/zKB6AbxEsskq55pwJ9klVmP8w= github.com/bitrise-io/go-steputils v1.0.1/go.mod h1:YIUaQnIAyK4pCvQG0hYHVkSzKNT9uL2FWmkFNW4mfNI= -github.com/bitrise-io/go-steputils v1.0.2 h1:BEFG87r7uA/Yabk4SmuxP2yOgjjO+YGsDOYXtUH8IJ0= -github.com/bitrise-io/go-steputils v1.0.2/go.mod h1:YIUaQnIAyK4pCvQG0hYHVkSzKNT9uL2FWmkFNW4mfNI= +github.com/bitrise-io/go-steputils v1.0.5 h1:OBH7CPXeqIWFWJw6BOUMQnUb8guspwKr2RhYBhM9tfc= +github.com/bitrise-io/go-steputils v1.0.5/go.mod h1:YIUaQnIAyK4pCvQG0hYHVkSzKNT9uL2FWmkFNW4mfNI= github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.15 h1:wG037NV+pS8cEwtalE5K58bmKLyUkU0+4m4IuXjTzmo= github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.15/go.mod h1:M09BbxYoh6B7KJnXk/yvtuU5nZPh7RQBJGKc1dp+0hQ= +github.com/bitrise-io/go-utils v0.0.0-20170404153453-c8163959178a/go.mod h1:Pp48eQd8BSNEzWWTBxdFUd/ufS5b5Bgkmt9D5NedjWg= github.com/bitrise-io/go-utils v0.0.0-20200224122728-e212188d99b4/go.mod h1:tTEsKvbz1LbzuN/KpVFHXnLtcAPdEgIdM41s0lL407s= github.com/bitrise-io/go-utils v0.0.0-20201019131314-6cc2aa4d248a/go.mod h1:tTEsKvbz1LbzuN/KpVFHXnLtcAPdEgIdM41s0lL407s= github.com/bitrise-io/go-utils v0.0.0-20210505091801-98b7dc39ee61/go.mod h1:nhdaDQFvaMny1CugVV6KjK92/q97ENo0RuKSW5I4fbA= @@ -58,8 +60,10 @@ github.com/bitrise-io/xcode-project v0.0.0-20201203153351-7ad13a1dd021/go.mod h1 github.com/bitrise-io/xcode-project v0.0.0-20210302080829-f3e0bfbcd5cb/go.mod h1:t9Gj5Pe/FBDTUrkFmw2lK6PbzESB6o27eE97ukZj8Rs= github.com/bitrise-steplib/steps-activate-ssh-key v0.0.0-20210518131750-a0d69ff2d203 h1:4dsq0dBxobriC7Q31g1EV5gaHiR5fv6nlnwtDunVPGk= github.com/bitrise-steplib/steps-activate-ssh-key v0.0.0-20210518131750-a0d69ff2d203/go.mod h1:zrkn07He4SgP2HP9GgJQgbC6SOKW3h6W7I08mgDlGbE= -github.com/bitrise-steplib/steps-git-clone v0.0.0-20230130163124-2237d7df95a9 h1:Q1Q4NTq7vEJJFBLYpt4YWXnesJbb0arGezb9+/okz0c= -github.com/bitrise-steplib/steps-git-clone v0.0.0-20230130163124-2237d7df95a9/go.mod h1:YL7euI4AOzCl5ytxtmD9KbaA0jQEeBcbWI86onfV6aQ= +github.com/bitrise-steplib/steps-authenticate-host-with-netrc v0.0.0-20230216105320-8cb845d52e28 h1:Xwe7VK9RT9QhfQ689qD2tFBK3OxeUqgeaiErOybZXGE= +github.com/bitrise-steplib/steps-authenticate-host-with-netrc v0.0.0-20230216105320-8cb845d52e28/go.mod h1:X1+WYJT3/vJpfeU5gbQwNdAq5Me0TD0ysWpAcXs01Uw= +github.com/bitrise-steplib/steps-git-clone v0.0.0-20230619101604-67b0d018398a h1:JUkKvD3gkqWrkL7dULDpPPxMCr7cpafLHjB2iZLzdOw= +github.com/bitrise-steplib/steps-git-clone v0.0.0-20230619101604-67b0d018398a/go.mod h1:ohFvSG8CMRMiWJQggcmM1vI+CNCbfYOCBaiDXucmKHk= github.com/bmatcuk/doublestar/v4 v4.2.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -118,6 +122,7 @@ github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.1.5-0.20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= diff --git a/main.go b/main.go index e0de68c3..e8d7a671 100644 --- a/main.go +++ b/main.go @@ -14,11 +14,15 @@ import ( "github.com/bitrise-io/go-utils/command" "github.com/bitrise-io/go-utils/log" "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/retry" cmdv2 "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/env" logv2 "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-steplib/steps-activate-ssh-key/activatesshkey" "github.com/bitrise-steplib/steps-git-clone/gitclone" + "github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi" + "github.com/bitrise-steplib/steps-git-clone/gitclone/tracker" + "github.com/bitrise-steplib/steps-git-clone/transport" ) type config struct { @@ -34,6 +38,10 @@ type config struct { // Activate SSH Key step SSHRsaPrivateKey stepconf.Secret `env:"ssh_rsa_private_key"` + // Git HTTP credentials + GitHTTPUsername stepconf.Secret `env:"git_http_username"` + GitHTTPPassword stepconf.Secret `env:"git_http_password"` + // Git clone step RepositoryURL string `env:"repository_url"` Branch string `env:"branch"` @@ -63,6 +71,8 @@ type repoConfig struct { CloneIntoDir string RepositoryURL string SSHRsaPrivateKey stepconf.Secret + GitHTTPUsername stepconf.Secret + GitHTTPPassword stepconf.Secret Branch string } @@ -95,21 +105,31 @@ func cloneRepo(cfg repoConfig) error { } } + // Activate Git HTTP credentials + if err := transport.Setup(transport.Config{ + URL: cfg.RepositoryURL, + HTTPUsername: string(cfg.GitHTTPUsername), + HTTPPassword: string(cfg.GitHTTPPassword), + }); err != nil { + return err + } + // Git clone logger := logv2.NewLogger() envRepo := env.NewRepository() - tracker := gitclone.NewStepTracker(envRepo, logger) + + tracker := tracker.NewStepTracker(envRepo, logger) cmdFactory := cmdv2.NewFactory(envRepo) - gitcloner := gitclone.NewGitCloner(logger, tracker, cmdFactory) + // patchSource and mergeRefChecker used for merging only + // build URL and build api token doesn't apply here + patchSource := bitriseapi.NewPatchSource("", "") + mergeRefChecker := bitriseapi.NewMergeRefChecker("", "", retry.NewHTTPClient(), logger, tracker) + gitcloner := gitclone.NewGitCloner(logger, tracker, cmdFactory, patchSource, mergeRefChecker) config := gitclone.Config{ RepositoryURL: cfg.RepositoryURL, CloneIntoDir: cfg.CloneIntoDir, // Using same directory later to run scan Branch: cfg.Branch, - // BuildURL and BuildAPIToken used for merging only - BuildURL: "", - BuildAPIToken: "", - UpdateSubmodules: true, } if _, err := gitcloner.CheckoutState(config); err != nil { diff --git a/step.yml b/step.yml index 8b88e3db..00a2ea3d 100755 --- a/step.yml +++ b/step.yml @@ -14,15 +14,15 @@ description: |- * **Flutter** projects, the Step checks for the `pubspec.yaml` files. ### Configuring the Step - - To successfully run the Step, you need: + + To successfully run the Step, you need: 1. An SSH key 1. A repository To configure the Step: - + 1. **POST url to send the scan results to**: You can send your app's scan results to an URL as a POST request. - 1. **URL to get app icon candidates upload URLs**: You can upload your app's icons using this input. + 1. **URL to get app icon candidates upload URLs**: You can upload your app's icons using this input. 1. **Verbose log option**: You can set this input to `yes` to produce more informative logs. 1. **Activate SSH key and clone git repo inside the Step**: You can set this input to `true` to activate an SSH key and clone the git repository of your app. @@ -84,7 +84,7 @@ inputs: opts: title: "Activate SSH key and clone git repo inside the Step" description: | - If set to yes then it will setup the ssh key and will clone the repo with the provided url and branch name. + If set to yes then it will setup the SSH key (or HTTP credentials) and will clone the repo with the provided url and branch name. value_options: - "yes" - "no" @@ -95,6 +95,18 @@ inputs: is_expand: true is_dont_change_value: true is_sensitive: true + - git_http_username: $GIT_HTTP_USERNAME + opts: + title: Username for establishing an HTTP(S) connection to the repository + is_dont_change_value: true + is_sensitive: true + + - git_http_password: $GIT_HTTP_PASSWORD + opts: + title: Personal access token (or password) for establishing an HTTP(S) connection to the repository + is_dont_change_value: true + is_sensitive: true + - repository_url: "$GIT_REPOSITORY_URL" opts: title: "Git repository URL" diff --git a/vendor/github.com/bitrise-io/go-steputils/step/steperror.go b/vendor/github.com/bitrise-io/go-steputils/step/steperror.go index 31f9252f..7be819f7 100644 --- a/vendor/github.com/bitrise-io/go-steputils/step/steperror.go +++ b/vendor/github.com/bitrise-io/go-steputils/step/steperror.go @@ -1,5 +1,7 @@ package step +import "errors" + // Error is an error occuring top level in a step type Error struct { StepID, Tag, ShortMsg string @@ -34,3 +36,11 @@ func NewErrorWithRecommendations(stepID, tag string, err error, shortMsg string, func (e *Error) Error() string { return e.Err.Error() } + +func (e *Error) Unwrap() error { + if err := errors.Unwrap(e.Err); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/bitrise-steplib/steps-authenticate-host-with-netrc/LICENSE b/vendor/github.com/bitrise-steplib/steps-authenticate-host-with-netrc/LICENSE new file mode 100644 index 00000000..021df7b8 --- /dev/null +++ b/vendor/github.com/bitrise-steplib/steps-authenticate-host-with-netrc/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 bitrise-steplib + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/bitrise-steplib/steps-authenticate-host-with-netrc/netrcutil/netrcutil.go b/vendor/github.com/bitrise-steplib/steps-authenticate-host-with-netrc/netrcutil/netrcutil.go new file mode 100644 index 00000000..14f7387c --- /dev/null +++ b/vendor/github.com/bitrise-steplib/steps-authenticate-host-with-netrc/netrcutil/netrcutil.go @@ -0,0 +1,60 @@ +package netrcutil + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" +) + +const netrcDefaultFileName = ".netrc" + +// NetRCItemModel ... +type NetRCItemModel struct { + Machine string + Login string + Password string +} + +// NetRCModel ... +type NetRCModel struct { + OutputPth string + ItemModels []NetRCItemModel +} + +// New ... +func New() *NetRCModel { + netRCPth := filepath.Join(pathutil.UserHomeDir(), netrcDefaultFileName) + return &NetRCModel{OutputPth: netRCPth} +} + +// AddItemModel ... +func (netRCModel *NetRCModel) AddItemModel(itemModels ...NetRCItemModel) { + netRCModel.ItemModels = append(netRCModel.ItemModels, itemModels...) +} + +// CreateFile ... +func (netRCModel *NetRCModel) CreateFile() error { + netRCFileContent := generateFileContent(netRCModel) + permission := os.FileMode(0600) // Other tools might fail if the file's permission is not 0600 + return fileutil.WriteStringToFileWithPermission(netRCModel.OutputPth, netRCFileContent, permission) +} + +// Append ... +func (netRCModel *NetRCModel) Append() error { + netRCFileContent := generateFileContent(netRCModel) + return fileutil.AppendStringToFile(netRCModel.OutputPth, fmt.Sprintf("\n\n%s", netRCFileContent)) +} + +func generateFileContent(netRCModel *NetRCModel) string { + netRCFileContent := "" + for i, itemModel := range netRCModel.ItemModels { + netRCFileContent += fmt.Sprintf("machine %s\n\tlogin %s\n\tpassword %s\n", itemModel.Machine, itemModel.Login, itemModel.Password) + if i != len(netRCModel.ItemModels)-1 { + netRCFileContent += "\n\n" + } + } + return netRCFileContent +} diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi/pr_merge_ref.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi/pr_merge_ref.go new file mode 100644 index 00000000..1a066c73 --- /dev/null +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi/pr_merge_ref.go @@ -0,0 +1,137 @@ +package bitriseapi + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-steplib/steps-git-clone/gitclone/tracker" + "github.com/hashicorp/go-retryablehttp" +) + +const maxAttemptCount = 5 +const retryWaitTime = time.Second * 2 + +type MergeRefChecker interface { + // IsMergeRefUpToDate returns true if the ref is safe to use in a checkout, and it reflects the latest state of the PR + IsMergeRefUpToDate(ref string) (bool, error) +} + +func NewMergeRefChecker(buildURL string, apiToken string, client *retryablehttp.Client, logger log.Logger, tracker tracker.StepTracker) MergeRefChecker { + return apiMergeRefChecker{ + buildURL: buildURL, + apiToken: apiToken, + client: client, + logger: logger, + tracker: tracker, + } +} + +type apiMergeRefChecker struct { + buildURL string + apiToken string + client *retryablehttp.Client + logger log.Logger + tracker tracker.StepTracker +} + +type mergeRefResponse struct { + Status string `json:"status"` + Error string `json:"error_msg"` + ShouldRetry bool `json:"should_retry"` +} + +type mergeRefFetcher func(attempt uint) (mergeRefResponse, error) + +func (c apiMergeRefChecker) IsMergeRefUpToDate(ref string) (bool, error) { + if c.buildURL == "" { + return false, fmt.Errorf("Bitrise build URL is not defined") + } + if c.apiToken == "" { + return false, fmt.Errorf("Bitrise API token is not defined") + } + + startTime := time.Now() + isUpToDate, attempts, err := doPoll(c.fetchMergeRef, maxAttemptCount, retryWaitTime, c.logger) + c.tracker.LogMergeRefVerify(time.Since(startTime), isUpToDate, attempts) + return isUpToDate, err +} + +func doPoll(fetcher mergeRefFetcher, maxAttemptCount uint, retryWaitTime time.Duration, logger log.Logger) (bool, uint, error) { + isUpToDate := false + attempts := uint(0) + pollAction := func(attempt uint) (err error, shouldAbort bool) { + resp, err := fetcher(attempt) + attempts = attempt + 1 // attempt is 0-indexed + if err != nil { + logger.Warnf("Error while checking merge ref: %s", err) + logger.Warnf("Retrying request...") + return err, false + } + if resp.Error != "" { + // Soft-error from API + logger.Warnf(resp.Error) + logger.Warnf("Check your connected account in App settings > Integrations > Service credential user") + return fmt.Errorf("response: %s", resp.Error), !resp.ShouldRetry + } + + switch resp.Status { + case "up-to-date": + isUpToDate = true + logger.Donef("Attempt %d: merge ref is up-to-date", attempts) + return nil, true + case "pending": + logger.Warnf("Attempt %d: not up-to-date yet", attempts) + return fmt.Errorf("pending"), false + case "not-mergeable": + // A not-mergeable PR state doesn't trigger a build directly, but there is a time window between + // triggering a build from a mergeable PR and running this code, where the PR can become unmergeable. + // The API responds with `not-mergeable` in this case. + // + // [PR push]-----[trigger]-------------------------------------------------[step run] + // |---------------[PR push: merge conflict]-----[trigger: skip build]--------------| + return fmt.Errorf("PR is not in a mergeable state"), true + default: + logger.Warnf("Attempt %d: unknown status: %s", attempts, resp) + return fmt.Errorf("unknown status: %s", resp.Status), false + } + } + + err := retry.Times(maxAttemptCount).Wait(retryWaitTime).TryWithAbort(pollAction) + if err != nil { + return false, attempts, err + } + + return isUpToDate, attempts, nil +} + +func (c apiMergeRefChecker) fetchMergeRef(attempt uint) (mergeRefResponse, error) { + url := fmt.Sprintf("%s/pull_request_merge_ref_status", c.buildURL) + req, err := retryablehttp.NewRequest(http.MethodGet, url, nil) + if err != nil { + return mergeRefResponse{}, err + } + req.Header.Set("BUILD_API_TOKEN", c.apiToken) + req.Header.Set("Accept", "application/json") + + resp, err := c.client.Do(req) + if err != nil { + return mergeRefResponse{}, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + c.logger.Warnf("Response status: %s", resp.Status) + } + + var response mergeRefResponse + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return mergeRefResponse{}, err + } + + return response, nil +} diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi/pr_patch.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi/pr_patch.go new file mode 100644 index 00000000..20665adf --- /dev/null +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi/pr_patch.go @@ -0,0 +1,50 @@ +package bitriseapi + +import ( + "fmt" + "net/http" + "net/url" + "path/filepath" + + "github.com/bitrise-io/go-steputils/input" + "github.com/bitrise-io/go-utils/filedownloader" +) + +type PatchSource interface { + // GetPRPatch fetches the git patch file of the PR (if available) and returns its local file path + GetPRPatch() (string, error) +} + +func NewPatchSource(buildURL, apiToken string) PatchSource { + return apiPatchSource{ + buildURL: buildURL, + apiToken: apiToken, + } +} + +type apiPatchSource struct { + buildURL string + apiToken string +} + +func (s apiPatchSource) GetPRPatch() (string, error) { + if s.buildURL == "" { + return "", fmt.Errorf("Bitrise build URL is not defined") + } + if s.apiToken == "" { + return "", fmt.Errorf("Bitrise API token is not defined") + } + + u, err := url.Parse(s.buildURL) + if err != nil { + return "", fmt.Errorf("could not parse build URL: %v", err) + } + + if u.Scheme == "file" { + return filepath.Join(u.Path, "diff.txt"), nil + } + + diffURL := fmt.Sprintf("%s/diff.txt?api_token=%s", s.buildURL, s.apiToken) + fileProvider := input.NewFileProvider(filedownloader.New(http.DefaultClient)) + return fileProvider.LocalPath(diffURL) +} diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout.go index eae48b8e..0fbf338e 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout.go @@ -7,6 +7,7 @@ import ( "github.com/bitrise-io/go-utils/command/git" "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi" ) // CheckoutMethod is the checkout method used @@ -47,7 +48,7 @@ const ( const privateForkAuthWarning = `May fail due to missing authentication as Pull Request opened from a private fork. A git hosting provider head branch or a diff file is unavailable.` -// ParameterValidationError is returned when there is missing or malformatted parameter for a given parameter set +// ParameterValidationError is returned when there is missing or malformed parameter for a given parameter set type ParameterValidationError struct { ErrorString string } @@ -88,8 +89,8 @@ type checkoutStrategy interface { // | headBranch | | | | | X | | // |=========================================================================| -func selectCheckoutMethod(cfg Config, patch patchSource) (CheckoutMethod, string) { - isPR := cfg.PRSourceRepositoryURL != "" || cfg.PRDestBranch != "" || cfg.PRMergeBranch != "" +func selectCheckoutMethod(cfg Config, patchSource bitriseapi.PatchSource, mergeRefChecker bitriseapi.MergeRefChecker) (CheckoutMethod, string) { + isPR := cfg.PRSourceRepositoryURL != "" || cfg.PRDestBranch != "" || cfg.PRMergeRef != "" || cfg.PRUnverifiedMergeRef != "" if !isPR { if cfg.Commit != "" { return CheckoutCommitMethod, "" @@ -130,12 +131,13 @@ func selectCheckoutMethod(cfg Config, patch patchSource) (CheckoutMethod, string // Fallback (Bitbucket only): it's a PR from a fork we can't access, so we fetch the PR patch file through // the API and apply the diff manually - if cfg.BuildURL != "" { - patchFile := getPatchFile(patch, cfg.BuildURL, cfg.BuildAPIToken) - if patchFile != "" { - log.Infof("Merging Pull Request despite the option to disable merging, as it is opened from a private fork.") - return CheckoutPRDiffFileMethod, patchFile - } + patchFile, err := patchSource.GetPRPatch() + if err != nil { + log.Warnf("Patch file unavailable for PR: %v", err) + } + if err == nil && patchFile != "" { + log.Infof("Merging Pull Request despite the option to disable merging, as it is opened from a private fork.") + return CheckoutPRDiffFileMethod, patchFile } log.Warnf(privateForkAuthWarning) @@ -143,39 +145,49 @@ func selectCheckoutMethod(cfg Config, patch patchSource) (CheckoutMethod, string } // PR: check out the merge result (merging the PR branch into the destination branch) - if cfg.PRMergeBranch != "" { - // Merge ref (such as refs/pull/2/merge) is available in the destination repo, we can access that - // even if the PR source is a private repo + if cfg.PRMergeRef != "" { + // Merge ref (such as refs/pull/2/merge) is available in the destination repo. + // This field is only defined if the merge ref is known to be up-to-date and is safe to checkout. + // See `PRUnverifiedMergeRef` below for handling a potentially outdated merge ref. + // Note about PRs from private forks: we can access this merge ref of the destination repo even if the source + // repo is private and not accessible to Bitrise return CheckoutPRMergeBranchMethod, "" } - // Fallback (Bitbucket only): fetch the PR patch file through the API and apply the diff manually - if cfg.BuildURL != "" { - patchFile := getPatchFile(patch, cfg.BuildURL, cfg.BuildAPIToken) - if patchFile != "" { - return CheckoutPRDiffFileMethod, patchFile + // Merge ref is available, but it might be outdated, we need to check its status and potentially trigger an update + // before we can use it for checkout + if cfg.PRUnverifiedMergeRef != "" { + log.Printf("\n") + log.Infof("Checking if %s is up to date...", cfg.PRUnverifiedMergeRef) + + upToDate, err := mergeRefChecker.IsMergeRefUpToDate(cfg.PRUnverifiedMergeRef) + if err != nil { + log.Warnf("Failed to check PR merge ref freshness: %s", err) } + if upToDate { + return CheckoutPRMergeBranchMethod, "" + } + } + + // Fallback (Bitbucket only): fetch the PR patch file through the API and apply the diff manually + log.Printf("\n") + log.Infof("Checking if PR patch file is available...") + patchFile, err := patchSource.GetPRPatch() + if err != nil { + log.Warnf("Patch file unavailable for PR: %s", err) + } + if err == nil && patchFile != "" { + return CheckoutPRDiffFileMethod, patchFile } // As a last resort, fetch target + PR branches and do a manual merge // This is not ideal because the merge requires fetched branch histories. If the fetch is too shallow, // the merge is going to fail with "refusing to merge unrelated histories" + log.Printf("\n") + log.Warnf("Fallback strategy: we are going to check out the PR and target branches and do a manual merge") return CheckoutPRManualMergeMethod, "" } -func getPatchFile(patch patchSource, buildURL, buildAPIToken string) string { - if patch != nil { - patchFile, err := patch.getDiffPath(buildURL, buildAPIToken) - if err != nil { - log.Warnf("Diff file unavailable: %v", err) - } else { - return patchFile - } - } - - return "" -} - func createCheckoutStrategy(checkoutMethod CheckoutMethod, cfg Config, patchFile string) (checkoutStrategy, error) { switch checkoutMethod { case CheckoutNoneMethod: @@ -222,7 +234,16 @@ func createCheckoutStrategy(checkoutMethod CheckoutMethod, cfg Config, patchFile } case CheckoutPRMergeBranchMethod: { - params, err := NewPRMergeRefParams(cfg.PRMergeBranch, cfg.PRHeadBranch) + var mergeRef string + if cfg.PRMergeRef != "" { + mergeRef = cfg.PRMergeRef + } else if cfg.PRUnverifiedMergeRef != "" { + // `selectCheckoutMethod()` only selects this method if it verified this merge ref + mergeRef = cfg.PRUnverifiedMergeRef + } else { + return nil, fmt.Errorf("inconsistent checkout strategy and checkout params: PRMergeRef=%s, PRUnverifiedMergeRef=%s", cfg.PRMergeRef, cfg.PRUnverifiedMergeRef) + } + params, err := NewPRMergeRefParams(mergeRef, cfg.PRHeadBranch) if err != nil { return nil, err } diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_helper.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_helper.go index 3d5a0361..0758dcb1 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_helper.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_helper.go @@ -1,6 +1,7 @@ package gitclone import ( + "errors" "fmt" "strings" @@ -72,7 +73,7 @@ func fetch(gitCmd git.Git, remote string, ref string, options fetchOptions) erro return handleCheckoutError( listBranches(gitCmd), fetchFailedTag, - fmt.Errorf("fetch failed: %v", err), + err, "Fetching repository has failed", branch, ) @@ -92,7 +93,7 @@ func checkoutWithCustomRetry(gitCmd git.Git, arg string, retry fallbackRetry) er return runner.Run(gitCmd.Checkout(arg)) } - return fmt.Errorf("checkout failed (%s): %v", arg, cErr) + return fmt.Errorf("checkout failed (%s): %w", arg, cErr) } return nil @@ -101,7 +102,8 @@ func checkoutWithCustomRetry(gitCmd git.Git, arg string, retry fallbackRetry) er func fetchInitialBranch(gitCmd git.Git, remote string, branchRef string, fetchTraits fetchOptions) error { branch := strings.TrimPrefix(branchRef, refsHeadsPrefix) if err := fetch(gitCmd, remote, branchRef, fetchTraits); err != nil { - return err + wErr := fmt.Errorf("failed to fetch branch (%s): %w", branchRef, err) + return fmt.Errorf("%v: %w", wErr, errors.New("please make sure the branch still exists")) } if err := checkoutWithCustomRetry(gitCmd, branch, nil); err != nil { @@ -119,7 +121,7 @@ func fetchInitialBranch(gitCmd git.Git, remote string, branchRef string, fetchTr if err := runner.Run(gitCmd.Merge(remoteBranch)); err != nil { return newStepError( "update_branch_failed", - fmt.Errorf("updating branch (merge) failed %s: %v", branch, err), + fmt.Errorf("updating branch (%s) failed: %w", branch, err), "Updating branch failed", ) } @@ -138,7 +140,8 @@ func mergeWithCustomRetry(gitCmd git.Git, arg string, retry fallbackRetry) error return runner.Run(gitCmd.Merge(arg)) } - return fmt.Errorf("merge failed (%s): %v", arg, mErr) + wErr := fmt.Errorf("merge failed (%s): %w", arg, mErr) + return fmt.Errorf("%v: %w", wErr, errors.New("please try to resolve all conflicts between the base and compare branches")) } return nil @@ -148,7 +151,7 @@ func detachHead(gitCmd git.Git) error { if err := runner.Run(gitCmd.Checkout("--detach")); err != nil { return newStepError( "detach_head_failed", - fmt.Errorf("detaching head failed: %v", err), + fmt.Errorf("detaching head failed: %w", err), "Detaching head failed", ) } diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_diff.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_diff.go index c5e00b46..8cbb3895 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_diff.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_diff.go @@ -2,14 +2,9 @@ package gitclone import ( "fmt" - "net/http" - "net/url" - "path/filepath" "strings" - "github.com/bitrise-io/go-steputils/input" "github.com/bitrise-io/go-utils/command/git" - "github.com/bitrise-io/go-utils/filedownloader" "github.com/bitrise-io/go-utils/log" ) @@ -43,7 +38,7 @@ type checkoutPRDiffFile struct { func (c checkoutPRDiffFile) do(gitCmd git.Git, fetchOptions fetchOptions, fallback fallbackRetry) error { destBranchRef := refsHeadsPrefix + c.params.DestinationBranch if err := fetch(gitCmd, originRemoteName, destBranchRef, fetchOptions); err != nil { - return err + return fmt.Errorf("failed to fetch base branch: %w", err) } if err := checkoutWithCustomRetry(gitCmd, c.params.DestinationBranch, fallback); err != nil { @@ -67,24 +62,3 @@ func (c checkoutPRDiffFile) do(gitCmd git.Git, fetchOptions fetchOptions, fallba func (c checkoutPRDiffFile) getBuildTriggerRef() string { return "" } - -type patchSource interface { - getDiffPath(buildURL, apiToken string) (string, error) -} - -type defaultPatchSource struct{} - -func (defaultPatchSource) getDiffPath(buildURL, apiToken string) (string, error) { - url, err := url.Parse(buildURL) - if err != nil { - return "", fmt.Errorf("could not parse diff file URL: %v", err) - } - - if url.Scheme == "file" { - return filepath.Join(url.Path, "diff.txt"), nil - } - - diffURL := fmt.Sprintf("%s/diff.txt?api_token=%s", buildURL, apiToken) - fileProvider := input.NewFileProvider(filedownloader.New(http.DefaultClient)) - return fileProvider.LocalPath(diffURL) -} diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_merge_branch.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_merge_branch.go index 807a57dd..6b40ee01 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_merge_branch.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_auto_merge_branch.go @@ -43,7 +43,7 @@ func (c checkoutPRMergeRef) do(gitCmd git.Git, fetchOpts fetchOptions, fallback // $ git fetch origin refs/remotes/pull/7/merge:refs/pull/7/merge err := fetch(gitCmd, originRemoteName, refSpec, fetchOpts) if err != nil { - return err + return fmt.Errorf("failed to fetch merge ref: %w", err) } // Also fetch the PR head ref because the step exports outputs based on the PR head commit (see output.go) diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_manual.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_manual.go index 020b656b..66e225b9 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_manual.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_pr_manual.go @@ -40,15 +40,15 @@ func NewPRManualMergeParams(sourceBranch, commit, sourceRepoURL, destBranch stri func validatePRManualMergeParams(sourceBranch, commit, sourceRepoURL, destBranch string) error { if strings.TrimSpace(sourceBranch) == "" { - return NewParameterValidationError("manual PR merge checkout strategy can not be used: no source branch specified") + return NewParameterValidationError("manual PR merge checkout strategy cannot be used: no source branch specified") } if strings.TrimSpace(destBranch) == "" { - return NewParameterValidationError("manual PR merge checkout strategy can not be used: no destination branch specified") + return NewParameterValidationError("manual PR merge checkout strategy cannot be used: no destination branch specified") } if strings.TrimSpace(sourceRepoURL) == "" && strings.TrimSpace(commit) == "" { - return NewParameterValidationError("manual PR merge chekout strategy can not be used: no source repository URL or source branch commit hash specified") + return NewParameterValidationError("manual PR merge checkout strategy cannot be used: no source repository URL or source branch commit hash specified") } return nil @@ -62,7 +62,7 @@ func (c checkoutPRManualMerge) do(gitCmd git.Git, fetchOptions fetchOptions, fal // Fetch and checkout destinations branch destBranchRef := refsHeadsPrefix + c.params.DestinationBranch if err := fetchInitialBranch(gitCmd, originRemoteName, destBranchRef, fetchOptions); err != nil { - return err + return fmt.Errorf("failed to fetch base branch: %w", err) } commitHash, err := runner.RunForOutput(gitCmd.Log("%H")) @@ -77,7 +77,7 @@ func (c checkoutPRManualMerge) do(gitCmd git.Git, fetchOptions fetchOptions, fal // Add fork remote if err := runner.Run(gitCmd.RemoteAdd(forkRemoteName, c.params.SourceRepoURL)); err != nil { - return fmt.Errorf("adding remote fork repository failed (%s): %v", c.params.SourceRepoURL, err) + return fmt.Errorf("adding remote fork repository failed (%s): %w", c.params.SourceRepoURL, err) } } else { @@ -87,7 +87,7 @@ func (c checkoutPRManualMerge) do(gitCmd git.Git, fetchOptions fetchOptions, fal // Fetch and merge sourceBranchRef := refsHeadsPrefix + c.params.SourceBranch if err := fetch(gitCmd, remoteName, sourceBranchRef, fetchOptions); err != nil { - return err + return fmt.Errorf("failed to fetch compare branch: %w", err) } if err := mergeWithCustomRetry(gitCmd, c.params.SourceMergeArg, fallback); err != nil { diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_simple.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_simple.go index 72c4c338..5db492c5 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_simple.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/checkout_method_simple.go @@ -53,11 +53,13 @@ func (c checkoutCommit) do(gitCmd git.Git, fetchOptions fetchOptions, fallback f } if err := fetch(gitCmd, remote, c.params.BranchRef, fetchOptions); err != nil { - return err + return fmt.Errorf("failed to fetch branch ref (%s) while checking out commit: %w", c.params.BranchRef, err) } if err := checkoutWithCustomRetry(gitCmd, c.params.Commit, fallback); err != nil { - return err + err = fmt.Errorf("failed to checkout commit: %w", err) + newErr := fmt.Errorf("please check if the provided commit hash (%s) is valid", c.params.Commit) + return fmt.Errorf("%v: %w", err, newErr) } return nil @@ -128,7 +130,7 @@ type checkoutTag struct { func (c checkoutTag) do(gitCmd git.Git, fetchOptions fetchOptions, fallback fallbackRetry) error { ref := fmt.Sprintf("%s:%s", c.ref(), c.ref()) if err := fetch(gitCmd, originRemoteName, ref, fetchOptions); err != nil { - return err + return fmt.Errorf("failed to fetch tag (%s): %w", c.ref(), err) } if err := checkoutWithCustomRetry(gitCmd, c.params.Tag, fallback); err != nil { diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/command_runner.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/command_runner.go index 5c0ca2fb..a1d6ebb3 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/command_runner.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/command_runner.go @@ -47,7 +47,11 @@ func (r DefaultRunner) Run(c *command.Model) error { err := c.SetStdout(os.Stdout).SetStderr(io.MultiWriter(os.Stderr, &buffer)).Run() if err != nil { if errorutil.IsExitStatusError(err) { - return errors.New(strings.TrimSpace(buffer.String())) + errorStr := buffer.String() + if errorStr == "" { + errorStr = "please check the command output for errors" + } + return errors.New(strings.TrimSpace(errorStr)) } return err } diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/gitclone.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/gitclone.go index ef25c301..12c7b057 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/gitclone.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/gitclone.go @@ -4,10 +4,11 @@ import ( "fmt" "time" + "github.com/bitrise-io/go-utils/command/git" "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/log" - - "github.com/bitrise-io/go-utils/command/git" + "github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi" + "github.com/bitrise-steplib/steps-git-clone/gitclone/tracker" ) const ( @@ -35,25 +36,28 @@ type Config struct { Branch string PRDestBranch string PRSourceRepositoryURL string - PRMergeBranch string + PRMergeRef string + PRUnverifiedMergeRef string PRHeadBranch string ResetRepository bool - BuildURL string - BuildAPIToken string } type GitCloner struct { - logger log.Logger - tracker StepTracker - cmdFactory command.Factory + logger log.Logger + tracker tracker.StepTracker + cmdFactory command.Factory + patchSource bitriseapi.PatchSource + mergeRefChecker bitriseapi.MergeRefChecker } -func NewGitCloner(logger log.Logger, tracker StepTracker, cmdFactory command.Factory) GitCloner { +func NewGitCloner(logger log.Logger, tracker tracker.StepTracker, cmdFactory command.Factory, patchSource bitriseapi.PatchSource, mergeRefChecker bitriseapi.MergeRefChecker) GitCloner { return GitCloner{ - logger: logger, - tracker: tracker, - cmdFactory: cmdFactory, + logger: logger, + tracker: tracker, + cmdFactory: cmdFactory, + patchSource: patchSource, + mergeRefChecker: mergeRefChecker, } } @@ -65,7 +69,7 @@ type CheckoutStateResult struct { // CheckoutState is the entry point of the git clone process func (g GitCloner) CheckoutState(cfg Config) (CheckoutStateResult, error) { - defer g.tracker.wait() + defer g.tracker.Wait() gitCmd, err := git.New(cfg.CloneIntoDir) if err != nil { @@ -127,7 +131,7 @@ func (g GitCloner) CheckoutState(cfg Config) (CheckoutStateResult, error) { return CheckoutStateResult{}, err } - checkoutStrategy, isPR, err := g.checkoutState(gitCmd, cfg, defaultPatchSource{}) + checkoutStrategy, isPR, err := g.checkoutState(gitCmd, cfg) if err != nil { return CheckoutStateResult{}, err } @@ -140,7 +144,7 @@ func (g GitCloner) CheckoutState(cfg Config) (CheckoutStateResult, error) { updateTime := time.Since(startTime).Round(time.Second) g.logger.Println() g.logger.Infof("Updating submodules took %s", updateTime) - g.tracker.logSubmoduleUpdate(updateTime) + g.tracker.LogSubmoduleUpdate(updateTime) } return CheckoutStateResult{ @@ -150,9 +154,9 @@ func (g GitCloner) CheckoutState(cfg Config) (CheckoutStateResult, error) { }, nil } -func (g GitCloner) checkoutState(gitCmd git.Git, cfg Config, patch patchSource) (strategy checkoutStrategy, isPR bool, err error) { +func (g GitCloner) checkoutState(gitCmd git.Git, cfg Config) (strategy checkoutStrategy, isPR bool, err error) { checkoutStartTime := time.Now() - checkoutMethod, diffFile := selectCheckoutMethod(cfg, patch) + checkoutMethod, diffFile := selectCheckoutMethod(cfg, g.patchSource, g.mergeRefChecker) fetchOpts := selectFetchOptions(checkoutMethod, cfg.CloneDepth, cfg.FetchTags, cfg.UpdateSubmodules, len(cfg.SparseDirectories) != 0) @@ -172,7 +176,7 @@ func (g GitCloner) checkoutState(gitCmd git.Git, cfg Config, patch patchSource) checkoutDuration := time.Since(checkoutStartTime).Round(time.Second) g.logger.Println() g.logger.Infof("Fetch and checkout took %s", checkoutDuration) - g.tracker.logCheckout(checkoutDuration, checkoutMethod, cfg.RepositoryURL) + g.tracker.LogCheckout(checkoutDuration, checkoutMethod.String(), cfg.RepositoryURL) return checkoutStrategy, isPRCheckout(checkoutMethod), nil } diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/tracker.go b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/tracker/tracker.go similarity index 68% rename from vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/tracker.go rename to vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/tracker/tracker.go index 0e6809ae..24856c87 100644 --- a/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/tracker.go +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/gitclone/tracker/tracker.go @@ -1,4 +1,4 @@ -package gitclone +package tracker import ( "strings" @@ -25,7 +25,7 @@ func NewStepTracker(envRepo env.Repository, logger log.Logger) StepTracker { } } -func (t *StepTracker) logCheckout(duration time.Duration, method CheckoutMethod, remoteURL string) { +func (t *StepTracker) LogCheckout(duration time.Duration, method string, remoteURL string) { var remoteType = "other" if strings.Contains(remoteURL, "github.com") { remoteType = "github.com" @@ -37,19 +37,28 @@ func (t *StepTracker) logCheckout(duration time.Duration, method CheckoutMethod, p := analytics.Properties{ "duration_s": duration.Truncate(time.Second).Seconds(), - "method": method.String(), + "method": method, "remote_type": remoteType, } t.tracker.Enqueue("step_git_clone_fetch_and_checkout", p) } -func (t *StepTracker) logSubmoduleUpdate(duration time.Duration) { +func (t *StepTracker) LogSubmoduleUpdate(duration time.Duration) { p := analytics.Properties{ "duration_s": duration.Truncate(time.Second).Seconds(), } t.tracker.Enqueue("step_git_clone_submodule_updated", p) } -func (t *StepTracker) wait() { +func (t *StepTracker) LogMergeRefVerify(duration time.Duration, success bool, attemptCount uint) { + p := analytics.Properties{ + "duration_s": duration.Truncate(time.Second).Seconds(), + "is_success": success, + "attempt_count": attemptCount, + } + t.tracker.Enqueue("step_git_clone_merge_ref_verified", p) +} + +func (t *StepTracker) Wait() { t.tracker.Wait() } diff --git a/vendor/github.com/bitrise-steplib/steps-git-clone/transport/transport.go b/vendor/github.com/bitrise-steplib/steps-git-clone/transport/transport.go new file mode 100644 index 00000000..32aedcd7 --- /dev/null +++ b/vendor/github.com/bitrise-steplib/steps-git-clone/transport/transport.go @@ -0,0 +1,82 @@ +package transport + +import ( + "fmt" + "net/url" + "strings" + "time" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-steplib/steps-authenticate-host-with-netrc/netrcutil" +) + +type Config struct { + URL string + HTTPUsername string + HTTPPassword string +} + +func Setup(cfg Config) error { + // We only deal with http URLs for now + if !strings.HasPrefix(cfg.URL, "http") { + return nil + } + + // Setup is a no-op if no password is provided + if cfg.HTTPPassword == "" { + return nil + } + + url, err := url.Parse(cfg.URL) + if err != nil { + return fmt.Errorf("failed to parse URL: %w", err) + } + host := url.Host + username := cfg.HTTPUsername + // Some providers (e.g. GitHub) doesn't care about the username, so we don't ask for it from the user + // But something still needs to be provided when making the network call + if username == "" { + username = "bitrise-git-clone-step" + } + password := cfg.HTTPPassword + + netRC := netrcutil.New() + + // based on https://github.com/bitrise-steplib/steps-authenticate-host-with-netrc/blob/master/main.go + netRC.AddItemModel(netrcutil.NetRCItemModel{Machine: host, Login: username, Password: password}) + + isExists, err := pathutil.IsPathExists(netRC.OutputPth) + if err != nil { + return fmt.Errorf("failed to check path (%s): %w", netRC.OutputPth, err) + } + + if !isExists { + log.Debugf("No .netrc file found at (%s), creating new...", netRC.OutputPth) + + if err := netRC.CreateFile(); err != nil { + return fmt.Errorf("failed to create .netrc file: %w", err) + } + } else { + log.Warnf(".netrc file already exists at (%s)", netRC.OutputPth) + + backupPth := fmt.Sprintf("%s%s", strings.Replace(netRC.OutputPth, ".netrc", ".bk.netrc", -1), time.Now().Format("2006_01_02_15_04_05")) + + if originalContent, err := fileutil.ReadBytesFromFile(netRC.OutputPth); err != nil { + return fmt.Errorf("failed to read file (%s): %w", netRC.OutputPth, err) + } else if err := fileutil.WriteBytesToFile(backupPth, originalContent); err != nil { + return fmt.Errorf("failed to write file (%s): %w", backupPth, err) + } else { + log.Warnf("Backup created at: %s", backupPth) + } + + log.Debugf("Appending config to the existing .netrc file...") + + if err := netRC.Append(); err != nil { + return fmt.Errorf("failed to append to .netrc file: %w", err) + } + } + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 20f3fccd..4377c203 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -38,7 +38,7 @@ github.com/bitrise-io/go-flutter/fluttersdk # github.com/bitrise-io/go-plist v0.0.0-20210301100253-4b1a112ccd10 ## explicit; go 1.15 github.com/bitrise-io/go-plist -# github.com/bitrise-io/go-steputils v1.0.2 +# github.com/bitrise-io/go-steputils v1.0.5 ## explicit; go 1.15 github.com/bitrise-io/go-steputils/input github.com/bitrise-io/go-steputils/step @@ -90,9 +90,15 @@ github.com/bitrise-io/stepman/models # github.com/bitrise-steplib/steps-activate-ssh-key v0.0.0-20210518131750-a0d69ff2d203 ## explicit; go 1.16 github.com/bitrise-steplib/steps-activate-ssh-key/activatesshkey -# github.com/bitrise-steplib/steps-git-clone v0.0.0-20230130163124-2237d7df95a9 +# github.com/bitrise-steplib/steps-authenticate-host-with-netrc v0.0.0-20230216105320-8cb845d52e28 +## explicit; go 1.17 +github.com/bitrise-steplib/steps-authenticate-host-with-netrc/netrcutil +# github.com/bitrise-steplib/steps-git-clone v0.0.0-20230619101604-67b0d018398a ## explicit; go 1.17 github.com/bitrise-steplib/steps-git-clone/gitclone +github.com/bitrise-steplib/steps-git-clone/gitclone/bitriseapi +github.com/bitrise-steplib/steps-git-clone/gitclone/tracker +github.com/bitrise-steplib/steps-git-clone/transport # github.com/gofrs/uuid v4.3.1+incompatible ## explicit github.com/gofrs/uuid