fastlane-plugin-wpmreleasetoolkit 12.2.0 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5efb1f6f88d1bb1617313112768ef93497cabe3897bb4834c5026a4e9a68ced0
4
- data.tar.gz: 8eaef85d47b2ec7be3e2da0693856923c02e6f67b65707e4143d7241147a7435
3
+ metadata.gz: bd7622da309b11a1d3f03e67692147361bc620cd3b9ecb0fa6d9b923cd5e8c15
4
+ data.tar.gz: 662c4964c041947dd71506cee2aeb6c01bfaf2ae35281ff41192ab1231c82201
5
5
  SHA512:
6
- metadata.gz: 7c05b2af7ca941a5afb52b703f3f0ee315e0ab48ffa87de88c48c5f22bbc1db82f0a819c7e4f2b10e754b6558569d801431e64b00f491e282eadd9885c4f75ac
7
- data.tar.gz: fc832aaf5f31e2e6b3f51c268f38529f056dc40a0971fb67e962da313634e2c56580eeac23f8d2e94f2f3dcc74ad3d69b0a465724afb7c178501103a5c01d309
6
+ metadata.gz: 891584b03637c76ea99ef7843db8639a8154565efd90e9339167f8edf40d7e24fc53c371c42f8e0de06f32fb6050010288b367920b326d9c34beec60d78fb9b4
7
+ data.tar.gz: 3e8d0bc1db3db2e76c64ed79285113d63fdad4ffcd08cc7444c741aedf62611a194c47f73ea0c35246320855e5cc3e77e302fbf789c86f3de0378adb4682261e
data/README.md CHANGED
@@ -24,15 +24,17 @@ This guide also includes some tips about configuring your environment and IDE (e
24
24
 
25
25
  When you need to do a new release of the `release-toolkit`, simply run `rake new_release` and follow the instructions.
26
26
 
27
+ > [!NOTE]
27
28
  > This task will:
28
29
  > - Show you the CHANGELOG/release notes it's about to use for that version
29
30
  > - Deduce which version number to use according to [SemVer](https://semver.org/) rules, and ask you to confirm that version number
30
31
  > - Create a `release/<x.y>` branch, update the version number in all the right places, and create a PR for those changes
31
32
 
32
- Submit the PR, adding the `Releases` label to it and adding the `owl-team` as reviewers.
33
+ Submit the PR, adding the `Releases` label to it and adding the `@wordpress-mobile/apps-infrastructure` team as reviewers.
33
34
 
34
35
  Once that PR is approved and merged, create a new GitHub Release, copy/pasting the CHANGELOG entries for that GH release's description.
35
36
 
37
+ > [!IMPORTANT]
36
38
  > Publishing the GitHub Release will create the associated tag as well, which will trigger the CI job that will ultimately `gem push` the gem on RubyGems.
37
39
 
38
40
  ## Security
@@ -50,4 +52,4 @@ If you have questions about getting setup or just want to say hi, join the [Word
50
52
 
51
53
  ## License
52
54
 
53
- Mobile Release Toolkit is an Open Source project covered by the [GNU General Public License version 2](LICENSE).
55
+ Mobile Release Toolkit is an Open Source project covered by the [GNU General Public License version 2](LICENSE).
@@ -1,17 +1,20 @@
1
1
  module Fastlane
2
2
  module Actions
3
3
  class BuildkitePipelineUploadAction < Action
4
- DEFAULT_ENV_FILE = File.join('.buildkite', 'shared-pipeline-vars').freeze
4
+ DEFAULT_BUILDKITE_PIPELINE_FOLDER = '.buildkite'.freeze
5
+ DEFAULT_ENV_FILE = File.join(DEFAULT_BUILDKITE_PIPELINE_FOLDER, 'shared-pipeline-vars').freeze
5
6
 
6
7
  def self.run(params)
7
8
  pipeline_file = params[:pipeline_file]
9
+ pipeline_file = File.join(DEFAULT_BUILDKITE_PIPELINE_FOLDER, pipeline_file) unless File.absolute_path?(pipeline_file)
8
10
  env_file = params[:env_file]
9
- environment = params[:environment]
11
+ # Both keys and values need to be passed as strings otherwise Fastlane.sh will fail to parse the command.
12
+ environment = params[:environment].to_h { |k, v| [k.to_s, v.to_s] }
10
13
 
11
14
  UI.user_error!("Pipeline file not found: #{pipeline_file}") unless File.exist?(pipeline_file)
12
15
  UI.user_error!('This action can only be called from a Buildkite CI build') unless ENV['BUILDKITE'] == 'true'
13
16
 
14
- UI.message "Adding steps from `#{pipeline_file}` to the current build"
17
+ UI.message("Adding steps from `#{pipeline_file}` to the current build")
15
18
 
16
19
  if env_file && File.exist?(env_file)
17
20
  UI.message(" - Sourcing environment file beforehand: #{env_file}")
@@ -31,7 +34,7 @@ module Fastlane
31
34
  [
32
35
  FastlaneCore::ConfigItem.new(
33
36
  key: :pipeline_file,
34
- description: 'The path to the YAML pipeline file to upload',
37
+ description: 'The path to the YAML pipeline file to upload. If a relative path is provided, it will be prefixed with the `.buildkite/` folder path. Absolute paths are used as-is',
35
38
  optional: false,
36
39
  type: String
37
40
  ),
@@ -89,23 +89,33 @@ module Fastlane
89
89
  # @return [String] The URL of the created Pull Request, or `nil` if no PR was created.
90
90
  #
91
91
  def self.create_backmerge_pr(token:, repository:, title:, head_branch:, base_branch:, labels:, milestone:, reviewers:, team_reviewers:, intermediate_branch_created_callback:)
92
- intermediate_branch = "merge/#{head_branch.gsub('/', '-')}-into-#{base_branch.gsub('/', '-')}"
93
-
94
- if Fastlane::Helper::GitHelper.branch_exists_on_remote?(branch_name: intermediate_branch)
95
- UI.user_error!("The intermediate branch `#{intermediate_branch}` already exists. Please check if there is an existing Pull Request that needs to be merged or closed first, or delete the branch.")
92
+ # Do an early pre-check to see if the PR would be valid, but only if no callback (as a callback might add new commits on intermediate branch)
93
+ if intermediate_branch_created_callback.nil? && !can_merge?(head_branch, into: base_branch)
94
+ UI.error("Nothing to merge from #{head_branch} into #{base_branch}. Skipping PR creation.")
96
95
  return nil
97
96
  end
98
97
 
98
+ # Create the intermediate branch
99
+ intermediate_branch = "merge/#{head_branch.gsub('/', '-')}-into-#{base_branch.gsub('/', '-')}"
100
+ if Fastlane::Helper::GitHelper.branch_exists_on_remote?(branch_name: intermediate_branch)
101
+ UI.important("An intermediate branch `#{intermediate_branch}` already exists on the remote. It will be deleted and GitHub will close any associated existing PR.")
102
+ Fastlane::Helper::GitHelper.delete_remote_branch_if_exists!(intermediate_branch)
103
+ end
104
+ Fastlane::Helper::GitHelper.delete_local_branch_if_exists!(intermediate_branch)
99
105
  Fastlane::Helper::GitHelper.create_branch(intermediate_branch)
100
106
 
101
- intermediate_branch_created_callback&.call(base_branch, intermediate_branch)
102
-
103
- # if there's a callback, make sure it didn't switch branches
104
- other_action.ensure_git_branch(branch: "^#{intermediate_branch}/") unless intermediate_branch_created_callback.nil?
105
-
106
- if Fastlane::Helper::GitHelper.point_to_same_commit?(base_branch, head_branch)
107
- UI.error("No differences between #{head_branch} and #{base_branch}. Skipping PR creation.")
108
- return nil
107
+ # Call the callback if one was provided to allow the use to add commits on the intermediate branch (e.g. solve conflicts)
108
+ unless intermediate_branch_created_callback.nil?
109
+ intermediate_branch_created_callback.call(base_branch, intermediate_branch)
110
+ # Make sure the callback block didn't switch branches
111
+ other_action.ensure_git_branch(branch: "^#{intermediate_branch}$")
112
+
113
+ # When a callback was provided, do the pre-check about valid PR _only_ at that point, in case the callback added new commits
114
+ unless can_merge?(intermediate_branch, into: base_branch)
115
+ UI.error("Nothing to merge from #{intermediate_branch} into #{base_branch}. Skipping PR creation.")
116
+ Fastlane::Helper::GitHelper.delete_local_branch_if_exists!(intermediate_branch)
117
+ return nil
118
+ end
109
119
  end
110
120
 
111
121
  other_action.push_to_git_remote(tags: false)
@@ -137,6 +147,23 @@ module Fastlane
137
147
  )
138
148
  end
139
149
 
150
+ # Determine if a `head->base` PR would be considered valid by GitHub.
151
+ #
152
+ # Note that a PR with an empty diff can still be valid (e.g. if you merge a commit and its revert)
153
+ #
154
+ # This method returns false mostly when all commits from `head` has already been merged into `base`
155
+ # and that there are no new commits to merge (in which case GitHub would refuse creating the PR)
156
+ #
157
+ # @param head [String] the head reference (commit sha or branch name) we want to merge
158
+ # @param into [String] the base reference (commit sha or branch name) we want to merge into
159
+ # @return [Boolean] true if there are commits in `head` that are not yet in `base` and a merge can happen;
160
+ # false if all commits from `head` are already in `base` and a merge would be rejected
161
+ #
162
+ def self.can_merge?(head, into:)
163
+ merge_base = Fastlane::Helper::GitHelper.find_merge_base(into, head)
164
+ !Fastlane::Helper::GitHelper.point_to_same_commit?(merge_base, head)
165
+ end
166
+
140
167
  def self.description
141
168
  'Creates backmerge PRs for a release branch into target branches'
142
169
  end
@@ -200,7 +227,9 @@ module Fastlane
200
227
  optional: true,
201
228
  type: Array),
202
229
  FastlaneCore::ConfigItem.new(key: :intermediate_branch_created_callback,
203
- description: 'Callback to allow for the caller to perform operations on the intermediate branch before pushing. The call back receives two parameters: the base (target) branch for the PR and the intermediate branch name',
230
+ description: 'Callback to allow for the caller to perform operations on the intermediate branch (e.g. pushing new commits to pre-solve conflicts) before creating the PR. ' \
231
+ + 'The callback receives two parameters: the base (target) branch for the PR and the intermediate branch name that has been created.' \
232
+ + 'Note that if you use the callback to add new commits to the intermediate branch, you are responsible for git-pushing them too',
204
233
  optional: true,
205
234
  type: Proc),
206
235
  Fastlane::Helper::GithubHelper.github_token_config_item,
@@ -113,11 +113,19 @@ module Fastlane
113
113
 
114
114
  # Get the SHA of a given git ref. Typically useful to get the SHA of the current HEAD commit.
115
115
  #
116
- # @param [String] ref The git ref (commit, branch name, 'HEAD', …) to resolve as a SHA
116
+ # @param ref [String]
117
+ # The git ref (commit, branch name, 'HEAD', …) to resolve as a SHA
118
+ # @param prepend_origin_if_needed [Boolean]
119
+ # If true, will retry the rev-parse by prefixing `origin/` to the ref it it failed without it
117
120
  # @return [String] The commit SHA of the ref
118
121
  #
119
- def self.get_commit_sha(ref: 'HEAD')
120
- Git.open(Dir.pwd).revparse(ref)
122
+ def self.get_commit_sha(ref: 'HEAD', prepend_origin_if_needed: false)
123
+ repo = Git.open(Dir.pwd)
124
+ repo.revparse(ref)
125
+ rescue Git::FailedError
126
+ raise unless prepend_origin_if_needed
127
+
128
+ repo.revparse("origin/#{ref}")
121
129
  end
122
130
 
123
131
  # Creates a tag for the given version, and optionally push it to the remote.
@@ -170,28 +178,36 @@ module Fastlane
170
178
  Action.sh('git', 'fetch', '--tags')
171
179
  end
172
180
 
181
+ # Use `git merge-base` to find as good a common ancestors as possible for a merge
182
+ #
183
+ # @param ref1 [String] The first git reference (sha1, ref name…)to find the common ancestor of
184
+ # @param ref2 [String] The second git reference (sha1, ref name…)to find the common ancestor of
185
+ # @return [String] The merge-base aka common ancestor for the 2 commits provided
186
+ # @note If a reference (e.g. branch name) can't be found locally, it will try with the same ref prefixed with `origin/`
187
+ #
188
+ def self.find_merge_base(ref1, ref2)
189
+ git_repo = Git.open(Dir.pwd)
190
+ # Resolve to shas, mostly so that we can support cases with and without `origin/` explicit prefix on branch names
191
+ ref1_sha, ref2_sha = [ref1, ref2].map { |ref| get_commit_sha(ref: ref, prepend_origin_if_needed: true) }
192
+
193
+ git_repo.merge_base(ref1_sha, ref2_sha)&.first&.sha
194
+ end
195
+
173
196
  # Checks if two git references point to the same commit.
174
197
  #
175
198
  # @param ref1 [String] the first git reference to check.
176
199
  # @param ref2 [String] the second git reference to check.
177
- # @param remote_name [String] the name of the remote repository to use (default is 'origin').
178
- # If nil or empty, no remote prefix will be used.
179
200
  #
180
201
  # @return [Boolean] true if the two references point to the same commit, false otherwise.
181
202
  #
182
- def self.point_to_same_commit?(ref1, ref2, remote_name: 'origin')
183
- git_repo = Git.open(Dir.pwd)
184
-
185
- ref1_full = remote_name.to_s.empty? ? ref1 : "#{remote_name}/#{ref1}"
186
- ref2_full = remote_name.to_s.empty? ? ref2 : "#{remote_name}/#{ref2}"
203
+ def self.point_to_same_commit?(ref1, ref2)
187
204
  begin
188
- ref1_commit = git_repo.gcommit(ref1_full)
189
- ref2_commit = git_repo.gcommit(ref2_full)
205
+ ref1_sha = get_commit_sha(ref: ref1, prepend_origin_if_needed: true)
206
+ ref2_sha = get_commit_sha(ref: ref2, prepend_origin_if_needed: true)
190
207
  rescue StandardError => e
191
- UI.error "Error fetching commits for #{ref1_full} and #{ref2_full}: #{e.message}"
192
- return false
208
+ UI.user_error! "Error fetching commits for #{ref1} and/or #{ref2}: #{e.message}"
193
209
  end
194
- ref1_commit.sha == ref2_commit.sha
210
+ ref1_sha == ref2_sha
195
211
  end
196
212
 
197
213
  # Returns the current git branch, or "HEAD" if it's not checked out to any branch
@@ -232,6 +248,32 @@ module Fastlane
232
248
  !Action.sh('git', 'ls-remote', '--heads', remote_name, branch_name).empty?
233
249
  end
234
250
 
251
+ # Delete a local branch if it exists.
252
+ #
253
+ # @param [String] branch_name The name of the local branch to delete.
254
+ # @return [Boolean] true if the branch was deleted, false if not (e.g. no such local branch existed in the first place)
255
+ #
256
+ def self.delete_local_branch_if_exists!(branch_name)
257
+ git_repo = Git.open(Dir.pwd)
258
+ return false unless git_repo.is_local_branch?(branch_name)
259
+
260
+ git_repo.branch(branch_name).delete
261
+ true
262
+ end
263
+
264
+ # Delete a remote branch if it exists.
265
+ #
266
+ # @param [String] branch_name The name of the remote branch to delete.
267
+ # @param [String] remote_name The name of the remote to delete the branch from. Defaults to 'origin'
268
+ # @return [Boolean] true if the branch was deleted, false if not (e.g. no such local branch existed in the first place)
269
+ #
270
+ def self.delete_remote_branch_if_exists!(branch_name, remote_name: 'origin')
271
+ git_repo = Git.open(Dir.pwd)
272
+ return false unless git_repo.branches.any? { |b| b.remote&.name == remote_name && b.name == branch_name }
273
+
274
+ git_repo.push(remote_name, branch_name, delete: true)
275
+ end
276
+
235
277
  # Checks whether a given path is ignored by Git, relying on Git's `check-ignore` under the hood.
236
278
  #
237
279
  # @param [String] path The path to check against `.gitignore`
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fastlane
4
4
  module Wpmreleasetoolkit
5
- VERSION = '12.2.0'
5
+ VERSION = '12.3.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-wpmreleasetoolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.2.0
4
+ version: 12.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Automattic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-30 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport