summaryrefslogtreecommitdiff
path: root/bin/secpick
blob: be120a304c922181a9cd13094ba3a832d0336064 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env ruby

# frozen_string_literal: false

require 'active_support/core_ext/object/to_query'
require 'optparse'
require 'open3'
require 'rainbow/refinement'
using Rainbow

module Secpick
  BRANCH_PREFIX = 'security'.freeze
  DEFAULT_REMOTE = 'dev'.freeze
  NEW_MR_URL = 'https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/new'.freeze

  class SecurityFix
    def initialize
      @options = self.class.options
    end

    def ee?
      File.exist?('./CHANGELOG-EE.md')
    end

    def dry_run?
      @options[:try] == true
    end

    def original_branch
      @options[:branch].strip
    end

    def source_branch
      branch = "#{original_branch}-#{@options[:version]}"
      branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
      branch.freeze
    end

    def security_branch
      "#{BRANCH_PREFIX}-#{@options[:version]}".tap do |name|
        name << "-ee" if ee?
      end.freeze
    end

    def git_commands
      ["git fetch #{@options[:remote]} #{security_branch}",
       "git checkout #{security_branch}",
       "git pull #{@options[:remote]} #{security_branch}",
       "git checkout -B #{source_branch}",
       "git cherry-pick #{@options[:sha]}",
       "git push #{@options[:remote]} #{source_branch}",
       "git checkout #{original_branch}"]
    end

    def gitlab_params
      {
        merge_request: {
          source_branch: source_branch,
          target_branch: security_branch,
          title: "[#{@options[:version].tr('-', '.')}] ",
          description: '/label ~security ~"Merge into Security"'
        }
      }
    end

    def new_mr_url
      if ee?
        NEW_MR_URL.sub('gitlabhq', 'gitlab-ee')
      else
        NEW_MR_URL
      end
    end

    def create!
      if dry_run?
        puts git_commands.join("\n").green
        puts "\nMerge request params: ".blue
        pp gitlab_params
      else
        cmd = git_commands.join(' && ')
        stdin, stdout, stderr, wait_thr = Open3.popen3(cmd)

        puts stdout.read&.green
        puts stderr.read&.red

        if wait_thr.value.success?
          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 }.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 to cherry pick') do |sha|
            options[:sha] = sha
          end

          opts.on('-r', '--remote abcd', 'Git remote name of dev.gitlab.org (optional, defaults to `dev`)') do |remote|
            options[:remote] = remote
          end

          opts.on('-d', '--dry-run', 'Only show Git commands, without calling them') do |remote|
            options[:try] = true
          end

          opts.on('-h', '--help', 'Displays Help') do
            puts opts

            exit
          end
        end

        parser.parse!

        options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
        options[:remote] ||= DEFAULT_REMOTE

        abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
        abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
      end
    end
  end
end

Secpick::SecurityFix.new.create!