From 580d82401a362980cc6e14bf8d866dda79c311f8 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Sat, 28 Jun 2025 03:10:17 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- bin/secpick | 219 +-------------- package.json | 2 +- scripts/backport_fix_to_stable_branch | 371 ++++++++++++++++++++++++++ yarn.lock | 38 +-- 4 files changed, 398 insertions(+), 232 deletions(-) create mode 100755 scripts/backport_fix_to_stable_branch diff --git a/bin/secpick b/bin/secpick index 06695c53214..0b5481ca705 100755 --- a/bin/secpick +++ b/bin/secpick @@ -2,219 +2,14 @@ # frozen_string_literal: true -require 'active_support/core_ext/object/to_query' -require 'optparse' -require 'open3' require 'rainbow/refinement' using Rainbow -module Secpick - BRANCH_PREFIX = 'security' - STABLE_SUFFIX = 'stable' +NEW_SCRIPT = 'scripts/backport_fix_to_stable_branch' - DEFAULT_REMOTE = 'security' - - SECURITY_MR_URL = 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/new' - - class SecurityFix - def initialize - @options = self.class.options - end - - def dry_run? - @options[:try] == true - end - - def source_branch - branch = "#{@options[:branch]}-#{@options[:version]}" - branch = "#{BRANCH_PREFIX}-#{branch}" unless branch.start_with?("#{BRANCH_PREFIX}-") - branch - end - - def stable_branch - "#{@options[:version]}-#{STABLE_SUFFIX}-ee" - end - - def git_pick_commands - [ - fetch_stable_branch, - create_backport_branch, - cherry_pick_commit - ] - end - - def git_push_commands - [ - push_to_remote, - checkout_original_branch - ] - end - - def git_commands - git_pick_commands + git_push_commands - end - - def gitlab_params - { - issuable_template: 'Security Release', - merge_request: { - source_branch: source_branch, - target_branch: stable_branch - } - } - end - - def new_mr_url - SECURITY_MR_URL - end - - def create! - if dry_run? - puts "\nGit commands:".blue - puts git_commands.join("\n") - - if !@options[:merge_request] - puts "\nMerge request URL:".blue - puts new_mr_url - end - - puts "\nMerge request params:".blue - pp gitlab_params - else - cmd = git_pick_commands.join(' && ') - stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) - - puts stdout.read&.green - puts stderr.read&.red - - unless wait_thr.value.success? - puts <<~MSG - It looks like cherry pick failed! - Open a new terminal and fix the conflicts. - Once fixed run `git cherry-pick --continue` - - After you are done, return here and continue. (Press n to cancel) - - Ready to continue? (Y/n) - MSG - - unless ['', 'Y', 'y'].include?(gets.chomp) - puts "\nRemaining git commands:".blue - puts 'git cherry-pick --continue' - puts git_push_commands.join("\n") - exit 1 - end - end - - stdin.close - stdout.close - stderr.close - - cmd = git_push_commands.join(' && ') - stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) - - puts stdout.read&.green - puts stderr.read&.red - - if wait_thr.value.success? && !@options[:merge_request] - puts "#{new_mr_url}?#{gitlab_params.to_query}".blue - end - - stdin.close - stdout.close - stderr.close - end - end - - def self.options - { version: nil, branch: nil, sha: nil, merge_request: false }.tap do |options| - parser = OptionParser.new do |opts| - opts.banner = "Usage: #{$0} [options]" - opts.on('-v', '--version 10.0', 'Version') do |version| - options[:version] = version&.tr('.', '-') - end - - opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch| - options[:branch] = branch - end - - opts.on('-s', '--sha abcd', 'SHA or SHA range to cherry pick (optional, defaults to current)') do |sha| - options[:sha] = sha - end - - opts.on('-r', '--remote dev', "Git remote name of security repo (optional, defaults to `#{DEFAULT_REMOTE}`)") do |remote| - options[:remote] = remote - end - - opts.on('--mr', '--merge-request', 'Create relevant security Merge Request targeting the stable branch') do - options[:merge_request] = true - end - - opts.on('-d', '--dry-run', 'Only show Git commands, without calling them') do - options[:try] = true - end - - opts.on('-h', '--help', 'Displays Help') do - puts opts - - exit - end - end - - parser.parse! - - options[:sha] ||= `git rev-parse HEAD`.strip - options[:branch] ||= `git rev-parse --abbrev-ref HEAD`.strip - options[:remote] ||= DEFAULT_REMOTE - - nil_options = options.select {|_, v| v.nil? } - unless nil_options.empty? - abort("Missing: #{nil_options.keys.join(', ')}. Use #{$0} --help to see the list of options available".red) - end - - abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/ - end - end - - private - - def checkout_original_branch - "git checkout #{@options[:branch]}" - end - - def push_to_remote - [ - "git push #{@options[:remote]} #{source_branch} --no-verify", - *merge_request_push_options - ].join(' ') - end - - def merge_request_push_options - return [] unless @options[:merge_request] - - [ - "-o mr.create", - "-o mr.target='#{stable_branch}'", - "-o mr.description='Please apply Security Release template.\\n/milestone %#{milestone}'" - ] - end - - def cherry_pick_commit - "git cherry-pick #{@options[:sha]}" - end - - def create_backport_branch - "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch} --no-track" - end - - def fetch_stable_branch - "git fetch #{@options[:remote]} #{stable_branch}" - end - - def milestone - @options[:version].gsub('-', '.') - end - end -end - -Secpick::SecurityFix.new.create! +puts "\n⚠️ This tool has moved! ⚠️\n".red +puts "bin/secpick has updated and moved to ".white + NEW_SCRIPT.red.bold +puts "\nThe new script can be used for #{'security fix backports'.bold}".white + " and #{'bug fix backports'.bold}.\n".white +puts "Please run again with:".white +puts "\n#{NEW_SCRIPT} #{ARGV.join(' ')}".blue +exit 1 diff --git a/package.json b/package.json index f5a8aaf1a1a..d0265d686ee 100644 --- a/package.json +++ b/package.json @@ -278,7 +278,7 @@ "crypto": "^1.0.1", "custom-jquery-matchers": "^2.1.0", "dependency-cruiser": "^16.9.0", - "eslint": "9.29.0", + "eslint": "9.30.0", "eslint-formatter-gitlab": "^6.0.1", "eslint-import-resolver-jest": "3.0.2", "eslint-import-resolver-webpack": "0.13.10", diff --git a/scripts/backport_fix_to_stable_branch b/scripts/backport_fix_to_stable_branch new file mode 100755 index 00000000000..7c4076f9067 --- /dev/null +++ b/scripts/backport_fix_to_stable_branch @@ -0,0 +1,371 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require 'active_support/core_ext/object/to_query' +require 'optparse' +require 'open3' +require 'rainbow/refinement' +require 'tty-prompt' +using Rainbow + +class Backport + SECURITY_REPO_URLS = { + ssh: 'git@gitlab.com:gitlab-org/security/gitlab.git', + http: 'https://gitlab.com/gitlab-org/security/gitlab.git' + }.freeze + + DEFAULT_OPTIONS = { + base: { + version: nil, branch: nil, sha: nil, merge_request: false, stable_branch_suffix: 'stable' + }, + security: { + branch_prefix: 'security', + new_merge_request_url: 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/new', + merge_request_template: 'Security Fix' + }, + bugfix: { + branch_prefix: 'backport', + new_merge_request_url: 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/new', + merge_request_template: 'Stable Branch' + } + }.freeze + + BACKPORT_TYPE_CHOICES = [ + { name: 'Security Fix', value: :security }.freeze, + { name: 'Bug Fix', value: :bugfix }.freeze + ].freeze + + def initialize + @prompt = TTY::Prompt.new(help_color: :cyan) + @options = build_options + end + + attr_reader :prompt + + def create! + correct_repository_check! + + return dry_run if dry_run? + + pick_commits + push_commits + end + + private + + def correct_repository_check! + if security_backport? && !pushing_to_security_remote? + abort('⛔️ Can only push security backports to the security repository ⛔️'.red) + elsif !security_backport? && pushing_to_security_remote? + abort('⛔️ Bugfixes should be pushed to the canonical repository ⛔️'.red) + end + end + + def pushing_to_security_remote? + @options[:remote] == security_remote_name + end + + def build_options + options = DEFAULT_OPTIONS[:base].dup + parse_options(options) + + options[:sha] ||= git_head_sha + options[:branch] ||= git_current_branch + options[:remote] ||= select_git_remote + options[:version] ||= select_version + + options.merge(DEFAULT_OPTIONS[confirmed_backport_type!]) + end + + def confirmed_backport_type! + backport_type = prompt.select('⚠️ What type of fix are you backporting? ⚠️'.red, BACKPORT_TYPE_CHOICES) + + @security_backport = backport_type == :security + + backport_type + end + + def security_backport? + @security_backport + end + + def add_security_remote + puts "⚠️ You do not have the security remote configured ⚠️".red + + return attempt_to_add_security_remote if prompt.yes?('Would you like me to add the remote for you now?') + + print_security_remote_config_instructions_and_exit + end + + def attempt_to_add_security_remote + security_repo_url = prompt.select('Which remote format do you normally use?', git_remote_style_choices) + stdin, stdout, stderr, wait_thr = Open3.popen3("git remote add security #{security_repo_url}") + + if wait_thr.value.success? # rubocop:disable Cop/LineBreakAroundConditionalBlock -- https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/merge_requests/258 + @security_remote_name = 'security' + puts '✅ Successfully created `security` remote'.white + else + puts "⛔️ Could not create `security` remote ⛔️".red + puts ('-' * 80).red + puts "\n#{stderr.read}".red + puts ('-' * 80).red + + print_security_remote_config_instructions_and_exit + end + ensure + stdin.close + stdout.close + stderr.close + end + + def print_security_remote_config_instructions_and_exit + puts "Please add the gitlab security remote to git with:".white + puts "git remote add security #{SECURITY_REPO_URLS[:ssh]}".cyan + puts "or".white + puts "git remote add security #{SECURITY_REPO_URLS[:http]}".cyan + puts "and then try again".white + exit 1 + end + + def parse_options(options) + parser = OptionParser.new do |opts| + opts.banner = <<~BANNER + Usage: #{$PROGRAM_NAME} [options] + + This tool requires confirmation for the backport type and will prompt + for the remote and version unless specified. + + BANNER + + opts.on('-v', '--version 10.0', 'Version to target (opens prompt if not passed)') do |version| + options[:version] = version&.tr('.', '-') + end + + opts.on('-r', '--remote dev', "Git remote name of repository (opens prompt if not passed)") do |remote| + options[:remote] = remote + end + + opts.on('-b', '--branch branch-name', 'Original branch name (optional, defaults to current branch)') do |branch| + options[:branch] = branch + end + + opts.on('-s', '--sha abcd', 'SHA or SHA range to cherry pick (optional, defaults to HEAD SHA)') do |sha| + options[:sha] = sha + end + + opts.on('--mr', '--merge-request', 'Create a Merge Request targeting the stable branch') do + options[:merge_request] = true + end + + opts.on('-d', '--dry-run', 'Display the Git commands this script will run without calling them') do + options[:try] = true + end + + opts.on('-h', '--help', 'Displays Help') do + puts opts + + exit + end + end + + parser.parse! + end + + def git_head_sha + `git rev-parse HEAD`.strip + end + + def git_current_branch + `git rev-parse --abbrev-ref HEAD`.strip + end + + def select_git_remote + prompt.select("Which remote do you want to push to?", remote_choices) + end + + def git_remotes + @git_remotes ||= `git remote -v`.strip.split("\n").each_with_object({}) do |line, output| + name, url, _type = line.split(/\s+/) + output[name] = url + end + end + + def security_remote_name + return @security_remote_name if defined?(@security_remote_name) + + @security_remote_name = git_remotes.find { |_k, url| SECURITY_REPO_URLS.value?(url) }&.first + add_security_remote if @security_remote_name.nil? + @security_remote_name + end + + # Sorts git remotes so the security remote is first in the list + def remote_choices + [security_remote_name] + (git_remotes.keys - [security_remote_name]) + end + + def git_remote_style_choices + SECURITY_REPO_URLS.map { |style, url| { name: "#{style}: #{url}", value: url } } + end + + def select_version + prompt.select('Which version are you targeting?', version_choices) + end + + def current_version + version_filepath = File.join(File.dirname($PROGRAM_NAME), '../VERSION') + full_version = File.read(version_filepath) + major, minor, _rest = full_version.split('.') + [major.to_i, minor.to_i] + end + + def version_choices + major, minor = current_version + + Array.new(3) do + minor -= 1 + + if minor < 0 + major -= 1 + minor = 11 + end + + { name: "#{major}.#{minor}", value: "#{major}-#{minor}" } + end + end + + def dry_run? + @options[:try] == true + end + + def dry_run + puts "\nGit commands:".blue + puts git_commands.join("\n") + puts "\nMerge request URL:".blue + puts new_merge_request_url + end + + def pick_commits + cmd = git_pick_commands.join(' && ') + stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) + + puts stdout.read.green + puts stderr.read.red + + unless wait_thr.value.success? # rubocop:disable Cop/LineBreakAroundConditionalBlock -- https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/merge_requests/258 + puts <<~MSG + It looks like cherry pick failed! + Open a new terminal and fix the conflicts. + Once fixed run `git cherry-pick --continue` + + After you are done, return here and continue. (Press n to cancel) + + Ready to continue? (Y/n) + MSG + + unless ['', 'Y', 'y'].include?(gets.chomp) + puts "\nRemaining git commands:".blue + puts 'git cherry-pick --continue' + puts git_push_commands.join("\n") + exit 1 + end + end + ensure + stdin.close + stdout.close + stderr.close + end + + def push_commits + cmd = git_push_commands.join(' && ') + stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) + + puts stdout.read.green + puts stderr.read.red + + puts new_merge_request_url.blue if wait_thr.value.success? && !@options[:merge_request] + ensure + stdin.close + stdout.close + stderr.close + end + + def git_pick_commands + [ + fetch_stable_branch, + create_backport_branch, + cherry_pick_commit + ] + end + + def git_push_commands + [ + push_to_remote, + checkout_original_branch + ] + end + + def git_commands + git_pick_commands + git_push_commands + end + + def fetch_stable_branch + "git fetch #{@options[:remote]} #{stable_branch}" + end + + def create_backport_branch + "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch} --no-track" + end + + def cherry_pick_commit + "git cherry-pick #{@options[:sha]}" + end + + def push_to_remote + [ + "git push #{@options[:remote]} #{source_branch} --no-verify", + *merge_request_push_options + ].join(' ') + end + + def checkout_original_branch + "git checkout #{@options[:branch]}" + end + + def gitlab_params + { + issuable_template: @options[:merge_request_template], + merge_request: { + source_branch: source_branch, + target_branch: stable_branch + } + } + end + + def merge_request_push_options + return [] unless @options[:merge_request] + + [ + "-o mr.create", + "-o mr.target='#{stable_branch}'", + "-o mr.description='Please apply `#{@options[:merge_request_template]}` template.'", + "-o mr.milestone='#{@options[:version].tr('-', '.')}'" + ] + end + + def source_branch + branch = "#{@options[:branch]}-#{@options[:version]}" + branch = "#{@options[:branch_prefix]}-#{branch}" unless branch.start_with?("#{@options[:branch_prefix]}-") + branch + end + + def stable_branch + "#{@options[:version]}-#{@options[:stable_branch_suffix]}-ee" + end + + def new_merge_request_url + "#{@options[:new_merge_request_url]}?#{gitlab_params.to_query}" + end +end + +Backport.new.create! diff --git a/yarn.lock b/yarn.lock index 73f53455390..18ebd2f1f0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1272,19 +1272,19 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.20.1": - version "0.20.1" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.20.1.tgz#454f89be82b0e5b1ae872c154c7e2f3dd42c3979" - integrity sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw== +"@eslint/config-array@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" + integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== dependencies: "@eslint/object-schema" "^2.1.6" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/config-helpers@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.2.1.tgz#26042c028d1beee5ce2235a7929b91c52651646d" - integrity sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw== +"@eslint/config-helpers@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.0.tgz#3e09a90dfb87e0005c7694791e58e97077271286" + integrity sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw== "@eslint/core@^0.14.0": version "0.14.0" @@ -1315,10 +1315,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.29.0", "@eslint/js@^9.15.0": - version "9.29.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.29.0.tgz#dc6fd117c19825f8430867a662531da36320fe56" - integrity sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ== +"@eslint/js@9.30.0", "@eslint/js@^9.15.0": + version "9.30.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.30.0.tgz#c396fa450d5505dd9b7b8846b33f0491aebd9a2d" + integrity sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww== "@eslint/object-schema@^2.1.6": version "2.1.6" @@ -7511,18 +7511,18 @@ eslint-visitor-keys@^4.2.1: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -eslint@9.29.0: - version "9.29.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.29.0.tgz#65e3db3b7e5a5b04a8af541741a0f3648d0a81a6" - integrity sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ== +eslint@9.30.0: + version "9.30.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.30.0.tgz#fb0c655f5e28fc1b2f4050c28efa1876d78034fc" + integrity sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.20.1" - "@eslint/config-helpers" "^0.2.1" + "@eslint/config-array" "^0.21.0" + "@eslint/config-helpers" "^0.3.0" "@eslint/core" "^0.14.0" "@eslint/eslintrc" "^3.3.1" - "@eslint/js" "9.29.0" + "@eslint/js" "9.30.0" "@eslint/plugin-kit" "^0.3.1" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1"