gitlab-housekeeper 0.1.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 +7 -0
- data/bin/gitlab-housekeeper +32 -0
- data/lib/gitlab/housekeeper/change.rb +50 -0
- data/lib/gitlab/housekeeper/git.rb +70 -0
- data/lib/gitlab/housekeeper/gitlab_client.rb +202 -0
- data/lib/gitlab/housekeeper/keep.rb +29 -0
- data/lib/gitlab/housekeeper/runner.rb +151 -0
- data/lib/gitlab/housekeeper/shell.rb +27 -0
- data/lib/gitlab/housekeeper/version.rb +7 -0
- data/lib/gitlab/housekeeper.rb +11 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8b911737b1ef300020b8cd648c514329d2eee7dfd8f86610727d8f4c4c47bd20
|
4
|
+
data.tar.gz: 47ef5bec137ec296a9bc61032aaee4c3be6e26fd8d404a1b4483f1797a8ad2bf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 767b5444231e67c439b7bd659c2e974445041e3e8140ed2d7c3336aecf3f7a5628505122cf32a83be68f5277578afe04b0ea74cfa6402108f466850a9d827a74
|
7
|
+
data.tar.gz: 6f50658e7fc7d37694af6f504a046bd2e1510d85852a37c3e4a84ac54391b8cea985c729f34be24f49f2ec7e53202de7bcc9b8f309c6fa61de445394e3c55641
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require 'gitlab/housekeeper'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = 'Creates merge requests that can be inferred from the current state of the codebase'
|
10
|
+
|
11
|
+
opts.on('-m=M', '--max-mrs=M', Integer, 'Limit of MRs to create. Defaults to 1.') do |m|
|
12
|
+
options[:max_mrs] = m
|
13
|
+
end
|
14
|
+
|
15
|
+
opts.on('-d', '--dry-run', 'Dry-run only. Print the MR titles, descriptions and diffs') do
|
16
|
+
options[:dry_run] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on('-k OverdueFinalizeBackgroundMigration,AnotherKeep', '--keeps OverdueFinalizeBackgroundMigration,AnotherKeep', Array, 'Require keeps specified') do |k|
|
20
|
+
options[:keeps] = k
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on('--filter-identifiers some-identifier-regex,another-regex', Array, 'Skip any changes where none of the identifiers match these regexes. The identifiers is an array, so at least one element must match at least one regex.') do |filters|
|
24
|
+
options[:filter_identifiers] = filters.map { |f| Regexp.new(f) }
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-h', '--help', 'Prints this help') do
|
28
|
+
abort opts.to_s
|
29
|
+
end
|
30
|
+
end.parse!
|
31
|
+
|
32
|
+
Gitlab::Housekeeper::Runner.new(**options).run
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Housekeeper
|
5
|
+
class Change
|
6
|
+
attr_accessor :identifiers,
|
7
|
+
:title,
|
8
|
+
:description,
|
9
|
+
:changed_files,
|
10
|
+
:labels,
|
11
|
+
:reviewers
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@labels = []
|
15
|
+
@reviewers = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def mr_description
|
19
|
+
<<~MARKDOWN
|
20
|
+
#{description}
|
21
|
+
|
22
|
+
This change was generated by
|
23
|
+
[gitlab-housekeeper](https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-housekeeper)
|
24
|
+
MARKDOWN
|
25
|
+
end
|
26
|
+
|
27
|
+
def commit_message
|
28
|
+
<<~MARKDOWN
|
29
|
+
#{title}
|
30
|
+
|
31
|
+
#{mr_description}
|
32
|
+
|
33
|
+
Changelog: other
|
34
|
+
MARKDOWN
|
35
|
+
end
|
36
|
+
|
37
|
+
def matches_filters?(filters)
|
38
|
+
filters.all? do |filter|
|
39
|
+
identifiers.any? do |identifier|
|
40
|
+
identifier.match?(filter)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid?
|
46
|
+
@identifiers && @title && @description && @changed_files
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
require 'gitlab/housekeeper/shell'
|
5
|
+
|
6
|
+
module Gitlab
|
7
|
+
module Housekeeper
|
8
|
+
class Git
|
9
|
+
def initialize(logger:, branch_from: 'master')
|
10
|
+
@logger = logger
|
11
|
+
@branch_from = branch_from
|
12
|
+
end
|
13
|
+
|
14
|
+
def commit_in_branch(change)
|
15
|
+
branch_name = branch_name(change.identifiers)
|
16
|
+
|
17
|
+
create_commit(branch_name, change)
|
18
|
+
|
19
|
+
branch_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_branch_from_branch
|
23
|
+
stashed = false
|
24
|
+
current_branch = Shell.execute('git', 'branch', '--show-current').chomp
|
25
|
+
|
26
|
+
result = Shell.execute('git', 'stash')
|
27
|
+
stashed = !result.include?('No local changes to save')
|
28
|
+
|
29
|
+
Shell.execute("git", "checkout", @branch_from)
|
30
|
+
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
# The `current_branch` won't be set in CI due to how the repo is cloned. Therefore we should only checkout
|
34
|
+
# `current_branch` if we actually have one.
|
35
|
+
Shell.execute("git", "checkout", current_branch) if current_branch.present?
|
36
|
+
Shell.execute('git', 'stash', 'pop') if stashed
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_commit(branch_name, change)
|
40
|
+
current_branch = Shell.execute('git', 'branch', '--show-current').chomp
|
41
|
+
|
42
|
+
begin
|
43
|
+
Shell.execute("git", "branch", '-D', branch_name)
|
44
|
+
rescue Shell::Error # Might not exist yet
|
45
|
+
end
|
46
|
+
|
47
|
+
Shell.execute("git", "checkout", "-b", branch_name)
|
48
|
+
Shell.execute("git", "add", *change.changed_files)
|
49
|
+
Shell.execute("git", "commit", "-m", change.commit_message)
|
50
|
+
ensure
|
51
|
+
Shell.execute("git", "checkout", current_branch)
|
52
|
+
end
|
53
|
+
|
54
|
+
def branch_name(identifiers)
|
55
|
+
# Hyphen-case each identifier then join together with hyphens.
|
56
|
+
branch_name = identifiers
|
57
|
+
.map { |i| i.gsub(/[[:upper:]]/) { |w| "-#{w.downcase}" } }
|
58
|
+
.join('-')
|
59
|
+
.delete_prefix("-")
|
60
|
+
|
61
|
+
# Truncate if it's too long and add a digest
|
62
|
+
if branch_name.length > 240
|
63
|
+
branch_name = branch_name[0...200] + OpenSSL::Digest::SHA256.hexdigest(branch_name)[0...15]
|
64
|
+
end
|
65
|
+
|
66
|
+
branch_name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'httparty'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Gitlab
|
7
|
+
module Housekeeper
|
8
|
+
class GitlabClient
|
9
|
+
Error = Class.new(StandardError)
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@token = ENV.fetch("HOUSEKEEPER_GITLAB_API_TOKEN")
|
13
|
+
@base_uri = 'https://gitlab.com/api/v4'
|
14
|
+
end
|
15
|
+
|
16
|
+
# This looks at the system notes of the merge request to detect if it has been updated by anyone other than the
|
17
|
+
# current housekeeper user. If it has then it assumes that they did this for a reason and we can skip updating
|
18
|
+
# this detail of the merge request. Otherwise we assume we should generate it again using the latest output.
|
19
|
+
def non_housekeeper_changes(
|
20
|
+
source_project_id:,
|
21
|
+
source_branch:,
|
22
|
+
target_branch:,
|
23
|
+
target_project_id:
|
24
|
+
)
|
25
|
+
|
26
|
+
iid = get_existing_merge_request(
|
27
|
+
source_project_id: source_project_id,
|
28
|
+
source_branch: source_branch,
|
29
|
+
target_branch: target_branch,
|
30
|
+
target_project_id: target_project_id
|
31
|
+
)
|
32
|
+
|
33
|
+
return [] if iid.nil?
|
34
|
+
|
35
|
+
merge_request_notes = get_merge_request_notes(target_project_id: target_project_id, iid: iid)
|
36
|
+
|
37
|
+
changes = Set.new
|
38
|
+
|
39
|
+
merge_request_notes.each do |note|
|
40
|
+
next false unless note["system"]
|
41
|
+
next false if note["author"]["id"] == current_user_id
|
42
|
+
|
43
|
+
changes << :title if note['body'].start_with?("changed title from")
|
44
|
+
changes << :description if note['body'] == "changed the description"
|
45
|
+
changes << :code if note['body'].match?(/added \d+ commit/)
|
46
|
+
|
47
|
+
changes << :reviewers if note['body'].include?('requested review from')
|
48
|
+
changes << :reviewers if note['body'].include?('removed review request for')
|
49
|
+
end
|
50
|
+
|
51
|
+
resource_label_events = get_merge_request_resource_label_events(target_project_id: target_project_id, iid: iid)
|
52
|
+
|
53
|
+
resource_label_events.each do |event|
|
54
|
+
next if event["user"]["id"] == current_user_id
|
55
|
+
|
56
|
+
# Labels are routinely added by both humans and bots, so addition events aren't cause for concern.
|
57
|
+
# However, if labels have been removed it may mean housekeeper added an incorrect label, and we shouldn't
|
58
|
+
# re-add them.
|
59
|
+
#
|
60
|
+
# TODO: Inspect the actual labels housekeeper wants to add, and add if they haven't previously been removed.
|
61
|
+
changes << :labels if event["action"] == "remove"
|
62
|
+
end
|
63
|
+
|
64
|
+
changes.to_a
|
65
|
+
end
|
66
|
+
|
67
|
+
# rubocop:disable Metrics/ParameterLists
|
68
|
+
def create_or_update_merge_request(
|
69
|
+
change:,
|
70
|
+
source_project_id:,
|
71
|
+
source_branch:,
|
72
|
+
target_branch:,
|
73
|
+
target_project_id:,
|
74
|
+
update_title:,
|
75
|
+
update_description:,
|
76
|
+
update_labels:,
|
77
|
+
update_reviewers:
|
78
|
+
)
|
79
|
+
existing_iid = get_existing_merge_request(
|
80
|
+
source_project_id: source_project_id,
|
81
|
+
source_branch: source_branch,
|
82
|
+
target_branch: target_branch,
|
83
|
+
target_project_id: target_project_id
|
84
|
+
)
|
85
|
+
|
86
|
+
if existing_iid
|
87
|
+
update_existing_merge_request(
|
88
|
+
change: change,
|
89
|
+
existing_iid: existing_iid,
|
90
|
+
target_project_id: target_project_id,
|
91
|
+
update_title: update_title,
|
92
|
+
update_description: update_description,
|
93
|
+
update_labels: update_labels,
|
94
|
+
update_reviewers: update_reviewers
|
95
|
+
)
|
96
|
+
else
|
97
|
+
create_merge_request(
|
98
|
+
change: change,
|
99
|
+
source_project_id: source_project_id,
|
100
|
+
source_branch: source_branch,
|
101
|
+
target_branch: target_branch,
|
102
|
+
target_project_id: target_project_id
|
103
|
+
)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# rubocop:enable Metrics/ParameterLists
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def get_merge_request_notes(target_project_id:, iid:)
|
111
|
+
request(:get, "/projects/#{target_project_id}/merge_requests/#{iid}/notes", query: { per_page: 100 })
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_merge_request_resource_label_events(target_project_id:, iid:)
|
115
|
+
request(:get, "/projects/#{target_project_id}/merge_requests/#{iid}/resource_label_events",
|
116
|
+
query: { per_page: 100 })
|
117
|
+
end
|
118
|
+
|
119
|
+
def current_user_id
|
120
|
+
@current_user_id ||= request(:get, "/user")['id']
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_existing_merge_request(source_project_id:, source_branch:, target_branch:, target_project_id:)
|
124
|
+
data = request(:get, "/projects/#{target_project_id}/merge_requests", query: {
|
125
|
+
state: :opened,
|
126
|
+
source_branch: source_branch,
|
127
|
+
target_branch: target_branch,
|
128
|
+
source_project_id: source_project_id
|
129
|
+
})
|
130
|
+
|
131
|
+
return nil if data.empty?
|
132
|
+
|
133
|
+
iids = data.pluck('iid')
|
134
|
+
|
135
|
+
raise Error, "More than one matching MR exists: iids: #{iids.join(',')}" unless data.size == 1
|
136
|
+
|
137
|
+
iids.first
|
138
|
+
end
|
139
|
+
|
140
|
+
def create_merge_request(
|
141
|
+
change:,
|
142
|
+
source_project_id:,
|
143
|
+
source_branch:,
|
144
|
+
target_branch:,
|
145
|
+
target_project_id:
|
146
|
+
)
|
147
|
+
request(:post, "/projects/#{source_project_id}/merge_requests", body: {
|
148
|
+
title: change.title,
|
149
|
+
description: change.mr_description,
|
150
|
+
labels: Array(change.labels).join(','),
|
151
|
+
source_branch: source_branch,
|
152
|
+
target_branch: target_branch,
|
153
|
+
target_project_id: target_project_id,
|
154
|
+
remove_source_branch: true,
|
155
|
+
reviewer_ids: usernames_to_ids(change.reviewers)
|
156
|
+
})
|
157
|
+
end
|
158
|
+
|
159
|
+
def update_existing_merge_request(
|
160
|
+
change:,
|
161
|
+
existing_iid:,
|
162
|
+
target_project_id:,
|
163
|
+
update_title:,
|
164
|
+
update_description:,
|
165
|
+
update_labels:,
|
166
|
+
update_reviewers:
|
167
|
+
)
|
168
|
+
body = {}
|
169
|
+
|
170
|
+
body[:title] = change.title if update_title
|
171
|
+
body[:description] = change.mr_description if update_description
|
172
|
+
body[:add_labels] = Array(change.labels).join(',') if update_labels
|
173
|
+
body[:reviewer_ids] = usernames_to_ids(change.reviewers) if update_reviewers
|
174
|
+
|
175
|
+
return if body.empty?
|
176
|
+
|
177
|
+
request(:put, "/projects/#{target_project_id}/merge_requests/#{existing_iid}", body: body)
|
178
|
+
end
|
179
|
+
|
180
|
+
def usernames_to_ids(usernames)
|
181
|
+
usernames.map do |username|
|
182
|
+
data = request(:get, "/users", query: { username: username })
|
183
|
+
data[0]['id']
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def request(method, path, query: {}, body: {})
|
188
|
+
response = HTTParty.public_send(method, "#{@base_uri}#{path}", query: query, body: body.to_json, headers: { # rubocop:disable GitlabSecurity/PublicSend
|
189
|
+
'Private-Token' => @token,
|
190
|
+
'Content-Type' => 'application/json'
|
191
|
+
})
|
192
|
+
|
193
|
+
unless (200..299).cover?(response.code)
|
194
|
+
raise Error,
|
195
|
+
"Failed with response code: #{response.code} and body:\n#{response.body}"
|
196
|
+
end
|
197
|
+
|
198
|
+
JSON.parse(response.body)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Housekeeper
|
5
|
+
# A Keep is analogous to a Cop in RuboCop. The Keep is responsible for:
|
6
|
+
# - Detecting a specific code change that should be made (eg. removing an old feature flag)
|
7
|
+
# - Making the code change (eg. delete the feature flag YML file)
|
8
|
+
# - Yielding a Change object that describes this change, For example:
|
9
|
+
# ```
|
10
|
+
# yield Gitlab::Housekeeper::Change.new(
|
11
|
+
# identifiers: ['remove-old-ff', 'old_ff_name'], # Unique and stable identifier for branch name
|
12
|
+
# title: "Remove old feature flag old_ff_name as it has been enabled since %15.0",
|
13
|
+
# description: "This feature flag was enabled in 15.0 and is not needed anymore ...",
|
14
|
+
# changed_files: ["config/feature_flags/ops/old_ff_name.yml", "app/models/user.rb"]
|
15
|
+
# )
|
16
|
+
# ```
|
17
|
+
class Keep
|
18
|
+
# The each_change method must update local working copy files and yield a Change object which describes the
|
19
|
+
# specific changed files and other data that will be used to generate a merge request. This is the core
|
20
|
+
# implementation details for a specific housekeeper keep. This does not need to commit the changes or create the
|
21
|
+
# merge request as that is handled by the gitlab-housekeeper gem.
|
22
|
+
#
|
23
|
+
# @yieldparam [Gitlab::Housekeeper::Change]
|
24
|
+
def each_change
|
25
|
+
raise NotImplementedError, "A Keep must implement each_change method"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
require 'gitlab/housekeeper/keep'
|
5
|
+
require 'gitlab/housekeeper/gitlab_client'
|
6
|
+
require 'gitlab/housekeeper/git'
|
7
|
+
require 'gitlab/housekeeper/change'
|
8
|
+
require 'awesome_print'
|
9
|
+
require 'digest'
|
10
|
+
|
11
|
+
module Gitlab
|
12
|
+
module Housekeeper
|
13
|
+
class Runner
|
14
|
+
def initialize(max_mrs: 1, dry_run: false, keeps: nil, filter_identifiers: [])
|
15
|
+
@max_mrs = max_mrs
|
16
|
+
@dry_run = dry_run
|
17
|
+
@logger = Logger.new($stdout)
|
18
|
+
require_keeps
|
19
|
+
|
20
|
+
@keeps = if keeps
|
21
|
+
keeps.map { |k| k.is_a?(String) ? k.constantize : k }
|
22
|
+
else
|
23
|
+
all_keeps
|
24
|
+
end
|
25
|
+
|
26
|
+
@filter_identifiers = filter_identifiers
|
27
|
+
end
|
28
|
+
|
29
|
+
def run
|
30
|
+
created = 0
|
31
|
+
|
32
|
+
git.with_branch_from_branch do
|
33
|
+
@keeps.each do |keep_class|
|
34
|
+
keep = keep_class.new
|
35
|
+
keep.each_change do |change|
|
36
|
+
unless change.valid?
|
37
|
+
puts "Ignoring invalid change from: #{keep_class}"
|
38
|
+
next
|
39
|
+
end
|
40
|
+
|
41
|
+
branch_name = git.commit_in_branch(change)
|
42
|
+
add_standard_change_data(change)
|
43
|
+
|
44
|
+
# Must be done after we commit so that we don't keep around changed files. We could checkout those files
|
45
|
+
# but then it might be riskier in local development in case we lose unrelated changes.
|
46
|
+
unless change.matches_filters?(@filter_identifiers)
|
47
|
+
puts "Skipping change: #{change.identifiers} due to not matching filter"
|
48
|
+
next
|
49
|
+
end
|
50
|
+
|
51
|
+
if @dry_run
|
52
|
+
dry_run(change, branch_name)
|
53
|
+
else
|
54
|
+
create(change, branch_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
created += 1
|
58
|
+
break if created >= @max_mrs
|
59
|
+
end
|
60
|
+
break if created >= @max_mrs
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "Housekeeper created #{created} MRs"
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_standard_change_data(change)
|
68
|
+
change.labels ||= []
|
69
|
+
change.labels << 'automation:gitlab-housekeeper-authored'
|
70
|
+
end
|
71
|
+
|
72
|
+
def git
|
73
|
+
@git ||= ::Gitlab::Housekeeper::Git.new(logger: @logger)
|
74
|
+
end
|
75
|
+
|
76
|
+
def require_keeps
|
77
|
+
Dir.glob("keeps/*.rb").each do |f|
|
78
|
+
require(Pathname(f).expand_path.to_s)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def dry_run(change, branch_name)
|
83
|
+
puts "=> #{change.identifiers.join(': ')}".purple
|
84
|
+
|
85
|
+
puts '=> Title:'.purple
|
86
|
+
puts change.title.purple
|
87
|
+
puts
|
88
|
+
|
89
|
+
puts '=> Description:'
|
90
|
+
puts change.description
|
91
|
+
puts
|
92
|
+
|
93
|
+
if change.labels.present?
|
94
|
+
puts '=> Attributes:'
|
95
|
+
puts "Labels: #{change.labels.join(', ')}"
|
96
|
+
puts
|
97
|
+
end
|
98
|
+
|
99
|
+
puts '=> Diff:'
|
100
|
+
puts Shell.execute('git', '--no-pager', 'diff', '--color=always', 'master', branch_name, '--',
|
101
|
+
*change.changed_files)
|
102
|
+
puts
|
103
|
+
end
|
104
|
+
|
105
|
+
def create(change, branch_name)
|
106
|
+
dry_run(change, branch_name)
|
107
|
+
|
108
|
+
non_housekeeper_changes = gitlab_client.non_housekeeper_changes(
|
109
|
+
source_project_id: housekeeper_fork_project_id,
|
110
|
+
source_branch: branch_name,
|
111
|
+
target_branch: 'master',
|
112
|
+
target_project_id: housekeeper_target_project_id
|
113
|
+
)
|
114
|
+
|
115
|
+
unless non_housekeeper_changes.include?(:code)
|
116
|
+
Shell.execute('git', 'push', '-f', 'housekeeper', "#{branch_name}:#{branch_name}")
|
117
|
+
end
|
118
|
+
|
119
|
+
mr = gitlab_client.create_or_update_merge_request(
|
120
|
+
change: change,
|
121
|
+
source_project_id: housekeeper_fork_project_id,
|
122
|
+
source_branch: branch_name,
|
123
|
+
target_branch: 'master',
|
124
|
+
target_project_id: housekeeper_target_project_id,
|
125
|
+
update_title: !non_housekeeper_changes.include?(:title),
|
126
|
+
update_description: !non_housekeeper_changes.include?(:description),
|
127
|
+
update_labels: !non_housekeeper_changes.include?(:labels),
|
128
|
+
update_reviewers: !non_housekeeper_changes.include?(:reviewers)
|
129
|
+
)
|
130
|
+
|
131
|
+
puts "Merge request URL: #{mr['web_url'].yellowish}"
|
132
|
+
end
|
133
|
+
|
134
|
+
def housekeeper_fork_project_id
|
135
|
+
ENV.fetch('HOUSEKEEPER_FORK_PROJECT_ID')
|
136
|
+
end
|
137
|
+
|
138
|
+
def housekeeper_target_project_id
|
139
|
+
ENV.fetch('HOUSEKEEPER_TARGET_PROJECT_ID')
|
140
|
+
end
|
141
|
+
|
142
|
+
def gitlab_client
|
143
|
+
@gitlab_client ||= GitlabClient.new
|
144
|
+
end
|
145
|
+
|
146
|
+
def all_keeps
|
147
|
+
@all_keeps ||= ObjectSpace.each_object(Class).select { |klass| klass < Keep }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'open3'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Housekeeper
|
7
|
+
class Shell
|
8
|
+
Error = Class.new(StandardError)
|
9
|
+
|
10
|
+
def self.execute(*cmd)
|
11
|
+
stdin, stdout, stderr, wait_thr = Open3.popen3(*cmd)
|
12
|
+
|
13
|
+
stdin.close
|
14
|
+
out = stdout.read
|
15
|
+
stdout.close
|
16
|
+
err = stderr.read
|
17
|
+
stderr.close
|
18
|
+
|
19
|
+
exit_status = wait_thr.value
|
20
|
+
|
21
|
+
raise Error, "Failed with #{exit_status}\n#{out}\n#{err}\n" unless exit_status.success?
|
22
|
+
|
23
|
+
out + err
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-housekeeper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- group::tenant-scale
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: awesome_print
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: gitlab-styles
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: webmock
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Housekeeping following https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134487
|
126
|
+
email:
|
127
|
+
- engineering@gitlab.com
|
128
|
+
executables:
|
129
|
+
- gitlab-housekeeper
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- bin/gitlab-housekeeper
|
134
|
+
- lib/gitlab/housekeeper.rb
|
135
|
+
- lib/gitlab/housekeeper/change.rb
|
136
|
+
- lib/gitlab/housekeeper/git.rb
|
137
|
+
- lib/gitlab/housekeeper/gitlab_client.rb
|
138
|
+
- lib/gitlab/housekeeper/keep.rb
|
139
|
+
- lib/gitlab/housekeeper/runner.rb
|
140
|
+
- lib/gitlab/housekeeper/shell.rb
|
141
|
+
- lib/gitlab/housekeeper/version.rb
|
142
|
+
homepage: https://gitlab.com/gitlab-org/gitlab/-/tree/master/gems/gitlab-housekeeper
|
143
|
+
licenses:
|
144
|
+
- MIT
|
145
|
+
metadata:
|
146
|
+
rubygems_mfa_required: 'true'
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '3.0'
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
requirements: []
|
162
|
+
rubygems_version: 3.5.6
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: Gem summary
|
166
|
+
test_files: []
|