git-maintain 0.11.0 → 0.13.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 +4 -4
- data/CHANGELOG +21 -0
- data/README.md +14 -14
- data/bin/git-maintain +7 -55
- data/git-maintain-completion.sh +3 -3
- data/lib/{addons → git-maintain/addons}/RDMACore.rb +91 -33
- data/lib/{addons → git-maintain/addons}/git-maintain.rb +48 -15
- data/lib/git-maintain/addons/healthd.rb +73 -0
- data/lib/git-maintain/addons/hpc-testing.rb +102 -0
- data/lib/git-maintain/azure.rb +200 -0
- data/lib/git-maintain/branch.rb +944 -0
- data/lib/git-maintain/branch_iterator.rb +148 -0
- data/lib/git-maintain/ci.rb +163 -0
- data/lib/git-maintain/common.rb +117 -0
- data/lib/git-maintain/error.rb +81 -0
- data/lib/git-maintain/repo.rb +573 -0
- data/lib/git-maintain/travis.rb +179 -0
- data/lib/git-maintain.rb +2 -0
- metadata +30 -14
- data/lib/azure.rb +0 -98
- data/lib/branch.rb +0 -759
- data/lib/ci.rb +0 -88
- data/lib/common.rb +0 -253
- data/lib/repo.rb +0 -447
- data/lib/travis.rb +0 -80
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
# Main module for git-maintain repository maintenance tool.
|
|
2
|
+
module GitMaintain
|
|
3
|
+
|
|
4
|
+
# Class representing a single stable branch and actions that can be performed on it.
|
|
5
|
+
class Branch < Common
|
|
6
|
+
# List of all available maintenance actions.
|
|
7
|
+
ACTION_LIST = [
|
|
8
|
+
:cp, :steal, :list,
|
|
9
|
+
:merge, :pull, :push, :monitor,
|
|
10
|
+
:release, :reset, :create, :delete
|
|
11
|
+
]
|
|
12
|
+
# Actions that do not require updating the remote repository first.
|
|
13
|
+
NO_FETCH_ACTIONS = [
|
|
14
|
+
:cp, :merge, :monitor, :release, :delete
|
|
15
|
+
]
|
|
16
|
+
# Actions that do not require checking out the local branch before running.
|
|
17
|
+
NO_CHECKOUT_ACTIONS = [
|
|
18
|
+
:create, :delete, :list, :push, :monitor
|
|
19
|
+
]
|
|
20
|
+
# Actions that run on all branches, regardless of target versions.
|
|
21
|
+
ALL_BRANCHES_ACTIONS = [
|
|
22
|
+
:create
|
|
23
|
+
]
|
|
24
|
+
# Description map of actions for CLI help output.
|
|
25
|
+
ACTION_HELP = {
|
|
26
|
+
:cp => "Backport commits and eventually push them to github",
|
|
27
|
+
:create => "Create missing local branches from all the stable branches",
|
|
28
|
+
:delete => "Delete all local branches using the suffix",
|
|
29
|
+
:steal => "Steal commit from upstream that fixes commit in the branch or were tagged as stable",
|
|
30
|
+
:list => "List commit present in the branch but not in the stable branch",
|
|
31
|
+
:merge => "Merge branch with suffix specified in -m <suff> into the main branch",
|
|
32
|
+
:push => "Push branches to github for validation",
|
|
33
|
+
:pull => "Rebase branches on top of the upstream one",
|
|
34
|
+
:monitor => "Check the CI state of all branches",
|
|
35
|
+
:release => "Create new release on all concerned branches",
|
|
36
|
+
:reset => "Reset branch against upstream",
|
|
37
|
+
}
|
|
38
|
+
# Configure action-specific command line options.
|
|
39
|
+
#
|
|
40
|
+
# @param action [Symbol] Selected action name
|
|
41
|
+
# @param optsParser [OptionParser] The OptionParser instance to configure
|
|
42
|
+
# @param opts [Hash] The options hash to populate
|
|
43
|
+
def self.set_opts(action, optsParser, opts)
|
|
44
|
+
opts[:base_ver] = 0
|
|
45
|
+
opts[:version] = []
|
|
46
|
+
opts[:commits] = []
|
|
47
|
+
opts[:breaker] = nil
|
|
48
|
+
opts[:do_merge] = false
|
|
49
|
+
opts[:push_force] = false
|
|
50
|
+
opts[:no_ci] = false
|
|
51
|
+
opts[:steal_base] = nil
|
|
52
|
+
opts[:check_only] = false
|
|
53
|
+
opts[:fetch] = nil
|
|
54
|
+
opts[:watch] = false
|
|
55
|
+
opts[:delete_remote] = false
|
|
56
|
+
opts[:no_edit] = false
|
|
57
|
+
opts[:stable] = false
|
|
58
|
+
|
|
59
|
+
optsParser.on("-v", "--base-version [MIN_VER]", Integer, "Older release to consider.") {
|
|
60
|
+
|val| opts[:base_ver] = val}
|
|
61
|
+
optsParser.on("-V", "--version [regexp]", Regexp, "Regexp to filter versions.") {
|
|
62
|
+
|val| opts[:version] << val}
|
|
63
|
+
|
|
64
|
+
if ALL_BRANCHES_ACTIONS.index(action) == nil &&
|
|
65
|
+
action != :merge &&
|
|
66
|
+
action != :delete then
|
|
67
|
+
optsParser.on("-B", "--manual-branch <branch name>", "Work on a specific (non-stable) branch.") {
|
|
68
|
+
|val| opts[:manual_branch] = val}
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if NO_FETCH_ACTIONS.index(action) == nil
|
|
72
|
+
optsParser.on("--[no-]fetch", "Enable/Disable fetch of stable repo.") {
|
|
73
|
+
|val| opts[:fetch] = val}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
case action
|
|
77
|
+
when :cp
|
|
78
|
+
optsParser.banner += "-c <sha1> [-c <sha1> ...]"
|
|
79
|
+
optsParser.on("-c", "--sha1 [SHA1]", String, "Commit to cherry-pick. Can be used multiple time.") {
|
|
80
|
+
|val| opts[:commits] << val}
|
|
81
|
+
optsParser.on("--breaker SHA", String, "Only apply patch if the breaker (or a cherry-pick of it) is contained within the branch.") {
|
|
82
|
+
|val| opts[:breaker] = val}
|
|
83
|
+
when :delete
|
|
84
|
+
optsParser.on("--remote", "Delete the remote staging branch instead of the local ones.") {
|
|
85
|
+
|val| opts[:delete_remote] = true}
|
|
86
|
+
when :list
|
|
87
|
+
optsParser.on("--stable", "List unreleased commits in the upstream stable branch.") {
|
|
88
|
+
opts[:stable] = true }
|
|
89
|
+
when :merge
|
|
90
|
+
optsParser.banner += "-m <suffix>"
|
|
91
|
+
optsParser.on("-m", "--merge [SUFFIX]", "Merge branch with suffix.") {
|
|
92
|
+
|val| opts[:do_merge] = val}
|
|
93
|
+
when :monitor
|
|
94
|
+
optsParser.on("-w", "--watch <PERIOD>", Integer,
|
|
95
|
+
"Watch and refresh CI status every <PERIOD>.") {
|
|
96
|
+
|val| opts[:watch] = val}
|
|
97
|
+
optsParser.on("--stable", "Check CI status on stable repo.") {
|
|
98
|
+
opts[:stable] = true }
|
|
99
|
+
when :pull
|
|
100
|
+
optsParser.on("--stable", "List unreleased commits in the upstream stable branch.") {
|
|
101
|
+
opts[:stable] = true }
|
|
102
|
+
when :push
|
|
103
|
+
optsParser.banner += "[-f]"
|
|
104
|
+
optsParser.on("-f", "--force", "Add --force to git push (for 'push' action).") {
|
|
105
|
+
opts[:push_force] = true}
|
|
106
|
+
optsParser.on("--stable", "Push to stable repo.") {
|
|
107
|
+
opts[:stable] = true }
|
|
108
|
+
optsParser.banner += "[-T]"
|
|
109
|
+
optsParser.on("-T", "--no-ci", "Ignore CI build status and push anyway.") {
|
|
110
|
+
opts[:no_ci] = true}
|
|
111
|
+
optsParser.on("-c", "--check", "Check if there is something to be pushed.") {
|
|
112
|
+
opts[:check_only] = true}
|
|
113
|
+
when :release
|
|
114
|
+
optsParser.on("--no-edit", "Do not edit release commit nor tag.") {
|
|
115
|
+
opts[:no_edit] = true }
|
|
116
|
+
when :steal
|
|
117
|
+
optsParser.banner += "[-a][-b <HEAD>]"
|
|
118
|
+
optsParser.on("-a", "--all", "Check all commits from master. "+
|
|
119
|
+
"By default only new commits (since last successful run) are considered.") {
|
|
120
|
+
|val| opts[:steal_base] = :all}
|
|
121
|
+
optsParser.on("-b", "--base <HEAD>", "Check all commits from this commit. "+
|
|
122
|
+
"By default only new commits (since last successful run) are considered.") {
|
|
123
|
+
|val| opts[:steal_base] = val}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Sanity check and normalize the parsed options for the given action.
|
|
128
|
+
#
|
|
129
|
+
# @param opts [Hash] Options hash to validate and configure
|
|
130
|
+
# @raise [InvalidArgumentError] If options are invalid or conflicting
|
|
131
|
+
def self.check_opts(opts)
|
|
132
|
+
if opts[:action] == :release then
|
|
133
|
+
if opts[:br_suff] != "master" then
|
|
134
|
+
raise InvalidArgumentError.new("Action #{opts[:action]} can only be done on 'master' suffixed branches")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
if opts[:action] == :delete && opts[:delete_remote] != true then
|
|
138
|
+
if opts[:br_suff] == "master" then
|
|
139
|
+
raise InvalidArgumentError.new("Action #{opts[:action]} can NOT be done on 'master' suffixed branches")
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
if opts[:action] == :push
|
|
143
|
+
if opts[:stable] == true && opts[:push_force] == true then
|
|
144
|
+
raise InvalidArgumentError.new("Action push can NOT be use both --stable and --force")
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
opts[:version] = [ /.*/ ] if opts[:version].length == 0
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Factory method to load an instance of the Branch class or its repository-specific subclass.
|
|
151
|
+
#
|
|
152
|
+
# @param repo [Repo] The Repo instance
|
|
153
|
+
# @param version [String] The branch version (e.g., '1.0')
|
|
154
|
+
# @param ci [CI] The CI instance
|
|
155
|
+
# @param branch_suff [String] Suffix of the branch (e.g., 'master')
|
|
156
|
+
# @return [Branch] The loaded Branch instance (or subclass)
|
|
157
|
+
# @raise [GitMaintainError] If class loading fails
|
|
158
|
+
def self.load(repo, version, ci, branch_suff)
|
|
159
|
+
repo_name = File.basename(repo.path)
|
|
160
|
+
return GitMaintain::loadClass(Branch, repo_name, repo, version, ci, branch_suff)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
protected
|
|
164
|
+
# Print the diff between two branches and prompt for confirmation.
|
|
165
|
+
#
|
|
166
|
+
# @param opts [Hash] Options hash
|
|
167
|
+
# @param br1 [String] First branch/ref
|
|
168
|
+
# @param br2 [String] Second branch/ref
|
|
169
|
+
# @param action_msg [String] Action message to prompt (e.g. 'submit')
|
|
170
|
+
# @return [String] User prompt response ('y' or 'n')
|
|
171
|
+
# @raise [RunError] If running git log fails
|
|
172
|
+
def checkLog(opts, br1, br2, action_msg)
|
|
173
|
+
puts "Diff between #{br1} and #{br2}"
|
|
174
|
+
puts `git log --format=oneline #{br1} ^#{br2}`
|
|
175
|
+
return "n" if action_msg.to_s() == ""
|
|
176
|
+
rep = confirm(opts, "#{action_msg} this branch")
|
|
177
|
+
return rep
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Print the diff/log between two branches.
|
|
181
|
+
#
|
|
182
|
+
# @param opts [Hash] Options hash
|
|
183
|
+
# @param br1 [String] First branch/ref
|
|
184
|
+
# @param br2 [String] Second branch/ref
|
|
185
|
+
# @raise [RunError] If running git log fails
|
|
186
|
+
def showLog(opts, br1, br2)
|
|
187
|
+
log(:INFO, "Diff between #{br1} and #{br2}")
|
|
188
|
+
puts `git log --format=oneline #{br1} ^#{br2}`
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
public
|
|
192
|
+
# Initialize a new Branch instance, resolving local/remote branches, head references, and stable base.
|
|
193
|
+
#
|
|
194
|
+
# @param repo [Repo] The Repo instance
|
|
195
|
+
# @param version [String] Suffix version (e.g. '1.0') or branch name
|
|
196
|
+
# @param ci [CI] The CI instance
|
|
197
|
+
# @param branch_suff [String] Branch suffix (e.g. 'master')
|
|
198
|
+
# @raise [NoRefError] If resolving git references fails
|
|
199
|
+
def initialize(repo, version, ci, branch_suff)
|
|
200
|
+
GitMaintain::checkDirectConstructor(self.class)
|
|
201
|
+
|
|
202
|
+
@path = repo.path
|
|
203
|
+
@repo = repo
|
|
204
|
+
@ci = ci
|
|
205
|
+
@version = version
|
|
206
|
+
@branch_suff = branch_suff
|
|
207
|
+
|
|
208
|
+
if version =~ /^[0-9]+$/
|
|
209
|
+
@local_branch = @repo.versionToLocalBranch(@version, @branch_suff)
|
|
210
|
+
@remote_branch = @repo.versionToStableBranch(@version)
|
|
211
|
+
@branch_type = :std
|
|
212
|
+
@verbose_name = "v"+version
|
|
213
|
+
else
|
|
214
|
+
@remote_branch = @local_branch = version
|
|
215
|
+
@branch_type = :user_specified
|
|
216
|
+
@verbose_name = version
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
@head = @repo.ref_exist?(@local_branch)
|
|
220
|
+
@valid_ref = "#{@repo.valid_repo}/#{@local_branch}"
|
|
221
|
+
@remote_ref = "#{@repo.stable_repo}/#{@remote_branch}"
|
|
222
|
+
@stable_head =
|
|
223
|
+
begin
|
|
224
|
+
@repo.ref_exist?(@remote_ref)
|
|
225
|
+
rescue
|
|
226
|
+
nil
|
|
227
|
+
end
|
|
228
|
+
case @branch_type
|
|
229
|
+
when :std
|
|
230
|
+
@stable_base = @repo.findStableBase(@local_branch)
|
|
231
|
+
when :user_specified
|
|
232
|
+
@stable_base = @remote_ref
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
attr_reader :version, :local_branch, :head, :remote_branch, :valid_ref, :remote_ref, :stable_head,
|
|
236
|
+
:verbose_name, :exists, :stable_base
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Check if the branch matches specified target version options.
|
|
240
|
+
#
|
|
241
|
+
# @param opts [Hash] Options hash with version/base filters
|
|
242
|
+
# @return [Boolean, Symbol] `true` if targeted, `:too_old` or `:no_match` otherwise
|
|
243
|
+
def is_targetted?(opts)
|
|
244
|
+
return true if @branch_type == :user_specified
|
|
245
|
+
if @version.to_i < opts[:base_ver] then
|
|
246
|
+
return :too_old
|
|
247
|
+
end
|
|
248
|
+
opts[:version].each() {|regexp|
|
|
249
|
+
return true if @version =~ regexp
|
|
250
|
+
}
|
|
251
|
+
return :no_match
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Checkout the git repository to this branch's local branch.
|
|
255
|
+
#
|
|
256
|
+
# @raise [RunError] If git checkout execution fails
|
|
257
|
+
def checkout()
|
|
258
|
+
runGitInteractive("checkout -q #{@local_branch}")
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Backport/cherry-pick the given array of commits into this branch.
|
|
262
|
+
#
|
|
263
|
+
# @param opts [Hash] Options hash containing `:commits` to pick
|
|
264
|
+
# @raise [CPAbort] If cherry-pick is aborted by the user
|
|
265
|
+
def cp(opts)
|
|
266
|
+
if opts[:breaker] != nil then
|
|
267
|
+
if !is_in_tree?(opts[:breaker]) then
|
|
268
|
+
log(:INFO, "Skipping #{verbose_name}: breaker #{opts[:breaker]} not in tree")
|
|
269
|
+
return
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
opts[:commits].each(){|commit|
|
|
273
|
+
if is_in_tree?(commit) then
|
|
274
|
+
log(:INFO, "Commit #{commit} is already in tree, skipping")
|
|
275
|
+
next
|
|
276
|
+
end
|
|
277
|
+
prev_head=runGit("rev-parse HEAD")
|
|
278
|
+
log(:INFO, "Applying #{@repo.getCommitHeadline(commit)}")
|
|
279
|
+
begin
|
|
280
|
+
runGitInteractive("cherry-pick #{commit}")
|
|
281
|
+
rescue RunError
|
|
282
|
+
begin
|
|
283
|
+
cp_fix(opts, commit)
|
|
284
|
+
rescue CPSkip => e
|
|
285
|
+
log(:INFO, e.message)
|
|
286
|
+
rescue CPAbort => e
|
|
287
|
+
log(:INFO, "Cherry-pick aborted by user.")
|
|
288
|
+
raise e
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
new_head=runGit("rev-parse HEAD")
|
|
292
|
+
# Do not make commit pretty if it was not applied
|
|
293
|
+
if new_head != prev_head
|
|
294
|
+
make_pretty(commit)
|
|
295
|
+
end
|
|
296
|
+
}
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Steal upstream commits that are not present in this branch.
|
|
300
|
+
# Uses `steal_all` and marks the last successful run with a git tag if successful.
|
|
301
|
+
#
|
|
302
|
+
# @param opts [Hash] Options hash
|
|
303
|
+
# @raise [RunError] If git tags/execution fails
|
|
304
|
+
def steal(opts)
|
|
305
|
+
base_ref=@stable_base
|
|
306
|
+
|
|
307
|
+
# If we are not force checking everything,
|
|
308
|
+
# try to start from the last tag we steal upto
|
|
309
|
+
case opts[:steal_base]
|
|
310
|
+
when nil
|
|
311
|
+
begin
|
|
312
|
+
sha = runGit("rev-parse 'git-maintain/steal/last/#{@stable_base}' 2>&1")
|
|
313
|
+
base_ref=sha
|
|
314
|
+
log(:VERBOSE, "Starting from last successfull run:")
|
|
315
|
+
log(:VERBOSE, @repo.getCommitHeadline(base_ref))
|
|
316
|
+
rescue RunError
|
|
317
|
+
# No matching tag found. Not an issue
|
|
318
|
+
end
|
|
319
|
+
when :all
|
|
320
|
+
base_ref=@stable_base
|
|
321
|
+
else
|
|
322
|
+
begin
|
|
323
|
+
sha = runGit("rev-parse #{opts[:steal_base]} 2>&1")
|
|
324
|
+
base_ref=sha
|
|
325
|
+
log(:VERBOSE, "Starting from base:")
|
|
326
|
+
log(:VERBOSE, @repo.getCommitHeadline(base_ref))
|
|
327
|
+
rescue RunError
|
|
328
|
+
crit("Could not find specified base '#{opts[:steal_base]}'")
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
master_sha=runGit("rev-parse origin/master")
|
|
333
|
+
|
|
334
|
+
begin
|
|
335
|
+
steal_all(opts, "#{base_ref}..#{master_sha}", true)
|
|
336
|
+
|
|
337
|
+
# We picked all the commits (or nothing happened)
|
|
338
|
+
# Mark the current master as the last checked point so we
|
|
339
|
+
# can just steal from this point on the next run
|
|
340
|
+
runGit("tag -f 'git-maintain/steal/last/#{@stable_base}' origin/master")
|
|
341
|
+
log(:VERBOSE, "Marking new last successfull run at:")
|
|
342
|
+
log(:VERBOSE, @repo.getCommitHeadline(master_sha))
|
|
343
|
+
rescue CPSkip
|
|
344
|
+
# Ignore the error
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# List all unreleased stable commits or commits in this branch but not in stable.
|
|
349
|
+
#
|
|
350
|
+
# @param opts [Hash] Options hash
|
|
351
|
+
# @raise [RunError] If running git commands fails
|
|
352
|
+
def list(opts)
|
|
353
|
+
log(:INFO, "Working on #{@verbose_name}")
|
|
354
|
+
if opts[:stable] == true then
|
|
355
|
+
# List commits in the stable_branch that are no in the latest release
|
|
356
|
+
showLog(opts, @remote_ref, runGit("describe --abbrev=0 #{@local_branch}"))
|
|
357
|
+
else
|
|
358
|
+
# List commits in the branch that are no in the stable branch
|
|
359
|
+
showLog(opts, @local_branch, @remote_ref)
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
# Merge the specified merge branch into this branch.
|
|
364
|
+
#
|
|
365
|
+
# @param opts [Hash] Options hash containing `:do_merge` branch name
|
|
366
|
+
# @raise [RunError] If running git merge or system commands fails
|
|
367
|
+
def merge(opts)
|
|
368
|
+
merge_branch = @repo.versionToLocalBranch(@version, opts[:do_merge])
|
|
369
|
+
|
|
370
|
+
# Make sure branch exists
|
|
371
|
+
begin
|
|
372
|
+
hash_to_merge = @repo.ref_exist?(merge_branch)
|
|
373
|
+
rescue NoRefError
|
|
374
|
+
log(:INFO, "Branch #{merge_branch} does not exists. Skipping...")
|
|
375
|
+
return
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# See if there is anything worth merging
|
|
379
|
+
merge_base_hash = runGit("merge-base #{merge_branch} #{@local_branch}")
|
|
380
|
+
if merge_base_hash == hash_to_merge then
|
|
381
|
+
log(:INFO, "Branch #{merge_branch} has no commit that needs to be merged")
|
|
382
|
+
return
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
rep = checkLog(opts, merge_branch, @local_branch, "merge")
|
|
386
|
+
if rep == "y" then
|
|
387
|
+
begin
|
|
388
|
+
runGitInteractive("merge #{merge_branch}")
|
|
389
|
+
rescue RunError
|
|
390
|
+
log(:WARNING, "Merge failure. Starting bash for manual fixes. Exit shell to continue")
|
|
391
|
+
runBash("PS1_WARNING='MERGING'")
|
|
392
|
+
end
|
|
393
|
+
else
|
|
394
|
+
log(:INFO, "Skipping merge")
|
|
395
|
+
return
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Pull/rebase the current branch against the upstream stable or validation reference.
|
|
400
|
+
#
|
|
401
|
+
# @param opts [Hash] Options hash
|
|
402
|
+
# @raise [NoRefError] If checking remote ref existence fails with unexpected ref errors
|
|
403
|
+
# @raise [RunError] If rebasing fails
|
|
404
|
+
def pull(opts)
|
|
405
|
+
remoteRef = opts[:stable] == true ? @remote_ref : @valid_ref
|
|
406
|
+
|
|
407
|
+
# Make sure branch exists
|
|
408
|
+
begin
|
|
409
|
+
@repo.ref_exist?(remoteRef)
|
|
410
|
+
rescue NoRefError
|
|
411
|
+
log(:INFO, "Branch #{remoteRef} does not exists. Skipping...")
|
|
412
|
+
return
|
|
413
|
+
end
|
|
414
|
+
runGitInteractive("rebase #{remoteRef}")
|
|
415
|
+
|
|
416
|
+
end
|
|
417
|
+
# Push the branch to the validation or stable repository.
|
|
418
|
+
# Saves the push specs to `opts[:push_branches]` accumulator array.
|
|
419
|
+
#
|
|
420
|
+
# @param opts [Hash] Options hash containing configuration and the accumulator array
|
|
421
|
+
# @raise [GitMaintainError] If CI checks fail or other git/CI errors occur
|
|
422
|
+
def push(opts)
|
|
423
|
+
remoteRef = opts[:stable] == true ? @remote_ref : @valid_ref
|
|
424
|
+
|
|
425
|
+
# Check both where we want to push and the final remote_ref
|
|
426
|
+
# We may have destroyed the validation branch but if we already merged
|
|
427
|
+
# in the final repo, no need to worry about it.
|
|
428
|
+
if same_sha?(@local_branch, remoteRef) ||
|
|
429
|
+
same_sha?(@local_branch, @remote_ref) then
|
|
430
|
+
log(:INFO, "Nothing to push on #{@local_branch}")
|
|
431
|
+
return
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# For stable branches, we need to check for CI
|
|
435
|
+
if opts[:stable] == true &&
|
|
436
|
+
(opts[:no_ci] != true && @NO_CI != true) &&
|
|
437
|
+
@ci.checkValidState(self, @head) != true then
|
|
438
|
+
log(:WARNING, "Build is not passed on CI. Skipping push to stable")
|
|
439
|
+
return
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
if opts[:check_only] == true then
|
|
443
|
+
checkLog(opts, @local_branch, @remote_ref, "")
|
|
444
|
+
return
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# For validation/CI push, let's go and push already
|
|
448
|
+
if opts[:stable] != true
|
|
449
|
+
opts[:push_branches] ||= []
|
|
450
|
+
opts[:push_branches] << "#{@local_branch}:#{@local_branch}"
|
|
451
|
+
return
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
# For stable, we need to confirm with the user that he really wants to push
|
|
455
|
+
rep = checkLog(opts, @local_branch, @remote_ref, "submit")
|
|
456
|
+
if rep == "y" then
|
|
457
|
+
opts[:push_branches] ||= []
|
|
458
|
+
opts[:push_branches] << "#{@local_branch}:#{@remote_branch}"
|
|
459
|
+
else
|
|
460
|
+
log(:INFO, "Skipping push to stable")
|
|
461
|
+
return
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# Run the epilogue for the push action, executing the accumulated pushes.
|
|
466
|
+
#
|
|
467
|
+
# @param opts [Hash] Options hash containing configuration and accumulated branch specs in `opts[:push_branches]`
|
|
468
|
+
# @param branches [Array] Ignored branch list from map send (we use accumulator in `opts[:push_branches]`)
|
|
469
|
+
# @raise [RunError] If git push execution fails
|
|
470
|
+
def push_epilogue(opts, branches)
|
|
471
|
+
push_list = opts[:push_branches] || []
|
|
472
|
+
return if push_list.length == 0
|
|
473
|
+
|
|
474
|
+
repo = (opts[:stable] == true) ? opts[:repo].stable_repo : opts[:repo].valid_repo
|
|
475
|
+
opts[:repo].runGit("push #{opts[:push_force] == true ? "-f" : ""} "+
|
|
476
|
+
"#{repo} #{push_list.join(" ")}")
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Monitor the build status on CI for the branch.
|
|
480
|
+
# Displays the current status (e.g. success, started, errored) and handles prompt to show logs.
|
|
481
|
+
#
|
|
482
|
+
# @param opts [Hash] Options hash
|
|
483
|
+
# @raise [GitMaintainError] If querying CI state or fetching logs fails
|
|
484
|
+
def monitor(opts)
|
|
485
|
+
ts = st = head = nil
|
|
486
|
+
suff=""
|
|
487
|
+
if opts[:stable] == true then
|
|
488
|
+
st = @ci.getStableState(self, @stable_head)
|
|
489
|
+
ts = @ci.getStableTS(self, @stable_head) if st == "started"
|
|
490
|
+
else
|
|
491
|
+
st = @ci.getValidState(self, @head)
|
|
492
|
+
ts = @ci.getValidTS(self, @head) if st == "started"
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
case st
|
|
496
|
+
when "started"
|
|
497
|
+
suff= " at #{ts}"
|
|
498
|
+
end
|
|
499
|
+
log(:INFO, "Status for v#{@version}: " + st + suff)
|
|
500
|
+
if @ci.isErrored(self, st) && opts[:watch] == false
|
|
501
|
+
rep = "y"
|
|
502
|
+
suff=""
|
|
503
|
+
while rep == "y"
|
|
504
|
+
rep = confirm(opts, "see the build log#{suff}")
|
|
505
|
+
if rep == "y" then
|
|
506
|
+
log = @ci.getValidLog(self, @head)
|
|
507
|
+
tmp = `mktemp`.chomp()
|
|
508
|
+
tmpfile = File.open(tmp, "w+")
|
|
509
|
+
tmpfile.puts(log)
|
|
510
|
+
tmpfile.close()
|
|
511
|
+
system("less -r #{tmp}")
|
|
512
|
+
`rm -f #{tmp}`
|
|
513
|
+
end
|
|
514
|
+
suff=" again"
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
# Reset the branch to the upstream stable reference (warning: hard reset).
|
|
520
|
+
#
|
|
521
|
+
# @param opts [Hash] Options hash
|
|
522
|
+
# @raise [RunError] If running git commands fails
|
|
523
|
+
def reset(opts)
|
|
524
|
+
if same_sha?(@local_branch, @remote_ref) then
|
|
525
|
+
log(:INFO, "Nothing to reset")
|
|
526
|
+
return
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
rep = checkLog(opts, @local_branch, @remote_ref, "reset")
|
|
530
|
+
if rep == "y" then
|
|
531
|
+
runGit("reset --hard #{@remote_ref}")
|
|
532
|
+
else
|
|
533
|
+
log(:INFO, "Skipping reset")
|
|
534
|
+
return
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Create a release on the current branch (dummy/unsupported method for the base Branch class).
|
|
539
|
+
#
|
|
540
|
+
# @param opts [Hash] Options hash
|
|
541
|
+
def release(opts)
|
|
542
|
+
log(:ERROR,"#No release command available for this repo")
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Create the missing local branch tracking the upstream remote stable reference.
|
|
546
|
+
#
|
|
547
|
+
# @param opts [Hash] Options hash
|
|
548
|
+
# @raise [RunError] If git branch creation fails
|
|
549
|
+
def create(opts)
|
|
550
|
+
return if @head != ""
|
|
551
|
+
log(:INFO, "Creating missing #{@local_branch} from #{@remote_ref}")
|
|
552
|
+
runGit("branch #{@local_branch} #{@remote_ref}")
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Delete the branch (locally or remotely).
|
|
556
|
+
# Saves the deletion specs to `opts[:delete_branches]` accumulator array.
|
|
557
|
+
#
|
|
558
|
+
# @param opts [Hash] Options hash containing configuration and the accumulator array
|
|
559
|
+
# @raise [NoRefError] If checking remote ref existence fails with unexpected ref errors
|
|
560
|
+
def delete(opts)
|
|
561
|
+
if opts[:delete_remote] == true then
|
|
562
|
+
begin
|
|
563
|
+
@repo.ref_exist?("#{@repo.valid_repo}/#{@local_branch}")
|
|
564
|
+
rescue NoRefError
|
|
565
|
+
log(:DEBUG, "Skipping non existing remote branch #{@local_branch}.")
|
|
566
|
+
return
|
|
567
|
+
end
|
|
568
|
+
msg = "delete remote branch #{@repo.valid_repo}/#{@local_branch}"
|
|
569
|
+
else
|
|
570
|
+
msg = "delete branch #{@local_branch}"
|
|
571
|
+
end
|
|
572
|
+
rep = confirm(opts, msg)
|
|
573
|
+
if rep == "y" then
|
|
574
|
+
opts[:delete_branches] ||= []
|
|
575
|
+
opts[:delete_branches] << @local_branch
|
|
576
|
+
else
|
|
577
|
+
log(:INFO, "Skipping deletion")
|
|
578
|
+
return
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# Run the epilogue for the delete action, executing the accumulated deletions.
|
|
583
|
+
#
|
|
584
|
+
# @param opts [Hash] Options hash containing configuration and accumulated branch specs in `opts[:delete_branches]`
|
|
585
|
+
# @param branches [Array] Ignored branch list from map send (we use accumulator in `opts[:delete_branches]`)
|
|
586
|
+
# @raise [RunError] If git branch deletion fails
|
|
587
|
+
def delete_epilogue(opts, branches)
|
|
588
|
+
delete_list = opts[:delete_branches] || []
|
|
589
|
+
return if delete_list.length == 0
|
|
590
|
+
puts "Deleting #{opts[:delete_remote] == true ? "remote" : "local"} branches: #{delete_list.join(" ")}"
|
|
591
|
+
rep = confirm(opts, "continue", true)
|
|
592
|
+
if rep != "y" then
|
|
593
|
+
log(:INFO, "Cancelling")
|
|
594
|
+
return
|
|
595
|
+
end
|
|
596
|
+
if opts[:delete_remote] == true then
|
|
597
|
+
opts[:repo].runGit("push #{opts[:repo].valid_repo} #{delete_list.map(){|x| ":" + x}.join(" ")}")
|
|
598
|
+
else
|
|
599
|
+
opts[:repo].runGit("branch -D #{delete_list.join(" ")}")
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
private
|
|
604
|
+
# Add the given commit to the git notes blacklist for the current branch.
|
|
605
|
+
#
|
|
606
|
+
# @param commit [String] The commit SHA to blacklist
|
|
607
|
+
# @raise [RunError] If running git notes fails
|
|
608
|
+
def add_blacklist(commit)
|
|
609
|
+
runGit("notes append -m \"#{@local_branch}\" #{commit}")
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def is_blacklisted?(commit)
|
|
613
|
+
begin
|
|
614
|
+
runGit("notes show #{commit} 2> /dev/null").split("\n").each(){|br|
|
|
615
|
+
return true if br == @local_branch
|
|
616
|
+
}
|
|
617
|
+
rescue
|
|
618
|
+
end
|
|
619
|
+
return false
|
|
620
|
+
end
|
|
621
|
+
|
|
622
|
+
# Rewrite the commit message of the HEAD commit to reference the upstream commit.
|
|
623
|
+
# Adds an "[ Upstream commit <SHA> ]" label.
|
|
624
|
+
#
|
|
625
|
+
# @param orig_commit [String] Original upstream commit SHA or reference
|
|
626
|
+
# @param commit [String] Override commit SHA to display in the label
|
|
627
|
+
# @raise [RunError] If running git commands fails
|
|
628
|
+
def make_pretty(orig_commit, commit="")
|
|
629
|
+
orig_sha=runGit("rev-parse #{orig_commit}")
|
|
630
|
+
msg_commit = (commit.to_s() == "") ? orig_sha : commit
|
|
631
|
+
|
|
632
|
+
msg_path=`mktemp`.chomp()
|
|
633
|
+
msg_file = File.open(msg_path, "w+")
|
|
634
|
+
msg_file.puts runGit("log -1 --format=\"%s%n%n[ Upstream commit #{msg_commit} ]%n%n%b\" #{orig_commit}")
|
|
635
|
+
msg_file.close()
|
|
636
|
+
runGit("commit -s --amend -F #{msg_path}")
|
|
637
|
+
`rm -f #{msg_path}`
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
def is_in_tree?(commit, src_commit=commit)
|
|
641
|
+
fullhash=nil
|
|
642
|
+
begin
|
|
643
|
+
fullhash=@repo.ref_exist?(commit)
|
|
644
|
+
rescue NoRefError
|
|
645
|
+
# This might happen if someone pointed to a commit that doesn't exist in our
|
|
646
|
+
# tree.
|
|
647
|
+
log(:WARNING, "Commit #{src_commit} points to a SHA #{commit} not in tree")
|
|
648
|
+
return false
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
# Hope for the best, same commit is/isn't in the current branch
|
|
652
|
+
if runGit("merge-base #{fullhash} HEAD") == fullhash then
|
|
653
|
+
return true
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
# Grab the subject, since commit sha1 is different between branches we
|
|
657
|
+
# have to look it up based on subject.
|
|
658
|
+
subj=@repo.getCommitSubj(commit)
|
|
659
|
+
|
|
660
|
+
# Try and find if there's a commit with given subject the hard way
|
|
661
|
+
runGit("log --pretty=\"%H\" -F --grep \"#{subj.gsub("\"", '\\"')}\" "+
|
|
662
|
+
"#{@stable_base}..HEAD").split("\n").each(){|cmt|
|
|
663
|
+
cursubj=runGit("log -1 --format=\"%s\" #{cmt}")
|
|
664
|
+
if cursubj == subj then
|
|
665
|
+
return true
|
|
666
|
+
end
|
|
667
|
+
}
|
|
668
|
+
return false
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def is_relevant?(commit)
|
|
672
|
+
# Let's grab the commit that this commit fixes (if exists (based on the "Fixes:" tag)).
|
|
673
|
+
fixescmt=runGit("log -1 #{commit} | grep -i \"fixes:\" | head -n 1 | "+
|
|
674
|
+
"sed -e 's/^[ \\t]*//' | cut -f 2 -d ':' | "+
|
|
675
|
+
"sed -e 's/^[ \\t]*//' -e 's/\\([0-9a-f]\\+\\)(/\\1 (/' | cut -f 1 -d ' '")
|
|
676
|
+
|
|
677
|
+
# If this commit fixes anything, but the broken commit isn't in our branch we don't
|
|
678
|
+
# need this commit either.
|
|
679
|
+
if fixescmt != "" then
|
|
680
|
+
if is_in_tree?(fixescmt, commit) then
|
|
681
|
+
return true
|
|
682
|
+
else
|
|
683
|
+
return false
|
|
684
|
+
end
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
if runGit("show #{commit} | grep -i 'stable@' | wc -l") == "0" then
|
|
688
|
+
return false
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Let's see if there's a version tag in this commit
|
|
692
|
+
full=runGit("show #{commit} | grep -i 'stable@'").gsub(/.* #?/, "")
|
|
693
|
+
|
|
694
|
+
# Sanity check our extraction
|
|
695
|
+
if full =~ /stable/ then
|
|
696
|
+
return false
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
full = runGit("rev-parse #{full}^{commit}")
|
|
700
|
+
|
|
701
|
+
# Make sure our branch contains this version
|
|
702
|
+
if runGit("merge-base #{@head} #{full}") == full then
|
|
703
|
+
return true
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
# Tag is not in history, ignore
|
|
707
|
+
return false
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Cherry-pick a single commit, trying other stable alternatives if it fails.
|
|
711
|
+
#
|
|
712
|
+
# @param commit [String] Commit SHA to cherry-pick
|
|
713
|
+
# @raise [CherryPickErrorException] If cherry-picking the commit (and its alternatives) fails
|
|
714
|
+
def pick_one(commit)
|
|
715
|
+
cpCmd="cherry-pick --strategy=recursive -Xpatience -x"
|
|
716
|
+
runGitInteractive("#{cpCmd} #{commit} &> /dev/null", {}, false)
|
|
717
|
+
return if $? == 0
|
|
718
|
+
|
|
719
|
+
if runGit("status -uno --porcelain | wc -l") == "0" then
|
|
720
|
+
runGit("reset --hard")
|
|
721
|
+
raise CherryPickErrorException.new("Failed to cherry pick commit #{commit}", commit)
|
|
722
|
+
end
|
|
723
|
+
runGit("reset --hard")
|
|
724
|
+
|
|
725
|
+
# That didn't work? Let's try that with every variation of the commit
|
|
726
|
+
# in other stable trees.
|
|
727
|
+
@repo.find_alts(commit).each(){|alt_commit|
|
|
728
|
+
runGitInteractive("#{cpCmd} #{alt_commit} &> /dev/null", {}, false)
|
|
729
|
+
if $? == 0 then
|
|
730
|
+
return
|
|
731
|
+
end
|
|
732
|
+
runGit("reset --hard")
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
# Still no? Let's go back to the original commit and hand it off to
|
|
736
|
+
# the user.
|
|
737
|
+
runGitInteractive("#{cpCmd} #{commit} &> /dev/null", {}, false)
|
|
738
|
+
raise CherryPickErrorException.new("Failed to cherry pick commit #{commit}", commit)
|
|
739
|
+
end
|
|
740
|
+
|
|
741
|
+
# Handle user interaction to fix cherry-pick conflicts via an interactive shell.
|
|
742
|
+
#
|
|
743
|
+
# @param opts [Hash] Options hash
|
|
744
|
+
# @param commit [String] Commit SHA with conflicts
|
|
745
|
+
# @raise [CPAbort] If cherry-pick is aborted by the user
|
|
746
|
+
# @raise [CPSkip] If the user skips this commit
|
|
747
|
+
def cp_fix(opts, commit)
|
|
748
|
+
runGitInteractive("diff")
|
|
749
|
+
log( :INFO, "Entering subshell to fix conflicts. Exit when done")
|
|
750
|
+
runSystem("PS1_WARNING='CP FIX' bash", false)
|
|
751
|
+
rep = confirm(opts, "continue with scp [y(es), n(o), s(kip)]?", true, ["y", "n", "s"])
|
|
752
|
+
case rep
|
|
753
|
+
when "n"
|
|
754
|
+
runGitInteractive("cherry-pick --abort")
|
|
755
|
+
raise(CPAbort)
|
|
756
|
+
when "s"
|
|
757
|
+
runGitInteractive("cherry-pick --abort")
|
|
758
|
+
e = CPSkip.new(commit.to_s())
|
|
759
|
+
log(:INFO, e.to_s())
|
|
760
|
+
raise(e)
|
|
761
|
+
end
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
# Steal/cherry-pick a single commit from upstream, asking for confirmation if needed.
|
|
765
|
+
#
|
|
766
|
+
# @param opts [Hash] Options hash
|
|
767
|
+
# @param commit [String] Commit SHA to cherry-pick
|
|
768
|
+
# @param mainline [Boolean] Whether to treat this as a mainline cherry-pick (skips mapping checks)
|
|
769
|
+
# @raise [CPSkip] If the user chooses to skip this commit
|
|
770
|
+
# @raise [CPAbort] If the user chooses to abort cherry-picking
|
|
771
|
+
def steal_one(opts, commit, mainline=false)
|
|
772
|
+
msg=''
|
|
773
|
+
orig_cmt=commit
|
|
774
|
+
|
|
775
|
+
if mainline == false then
|
|
776
|
+
subj=@repo.getCommitSubj(commit)
|
|
777
|
+
subj.gsub!(/"/, '\"')
|
|
778
|
+
# Let's grab the mainline commit id, this is useful if the version tag
|
|
779
|
+
# doesn't exist in the commit we're looking at but exists upstream.
|
|
780
|
+
orig_cmt=runGit("log --no-merges --format=\"%H\" -F --grep \"#{subj}\" " +
|
|
781
|
+
"#{@stable_base}..origin/master | tail -n1")
|
|
782
|
+
|
|
783
|
+
if orig_cmt == "" then
|
|
784
|
+
log(:WARNING, "Could not find commit #{commit} in mainline")
|
|
785
|
+
end
|
|
786
|
+
end
|
|
787
|
+
# If the commit doesn't apply for us, skip it
|
|
788
|
+
if is_relevant?(orig_cmt) != true
|
|
789
|
+
return
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
log(:VERBOSE, "Found relevant commit #{@repo.getCommitHeadline(commit)}")
|
|
793
|
+
if is_in_tree?(orig_cmt) == true
|
|
794
|
+
# Commit is already in the stable branch, skip
|
|
795
|
+
log(:VERBOSE, "Commit is already in tree")
|
|
796
|
+
return
|
|
797
|
+
end
|
|
798
|
+
|
|
799
|
+
# Check if it's not blacklisted by a git-notes
|
|
800
|
+
if is_blacklisted?(orig_cmt) == true then
|
|
801
|
+
# Commit is blacklisted
|
|
802
|
+
log(:INFO, "Skipping 'blacklisted' commit " +
|
|
803
|
+
@repo.getCommitHeadline(orig_cmt))
|
|
804
|
+
return
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
commit_desc = @repo.getCommitHeadline(commit)
|
|
808
|
+
rep = "t"
|
|
809
|
+
while rep != "y"
|
|
810
|
+
rep = confirm(opts, "pick commit '#{commit_desc}' up ([y]es, [n]o, [b]lacklist)",
|
|
811
|
+
false, ["y", "n", "?", "b"])
|
|
812
|
+
|
|
813
|
+
case rep
|
|
814
|
+
when "y"
|
|
815
|
+
break
|
|
816
|
+
when "n"
|
|
817
|
+
raise CPSkip.new(commit)
|
|
818
|
+
when "b"
|
|
819
|
+
log(:INFO, "Blacklisting this commit for the current branch")
|
|
820
|
+
add_blacklist(commit)
|
|
821
|
+
raise CPSkip.new(commit)
|
|
822
|
+
when "?"
|
|
823
|
+
runGitInteractive("show #{commit}", {}, false)
|
|
824
|
+
end
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
prev_head=runGit("rev-parse HEAD")
|
|
828
|
+
begin
|
|
829
|
+
pick_one(commit)
|
|
830
|
+
rescue CherryPickErrorException
|
|
831
|
+
cp_fix(opts, commit)
|
|
832
|
+
end
|
|
833
|
+
new_head=runGit("rev-parse HEAD")
|
|
834
|
+
|
|
835
|
+
# If we didn't find the commit upstream then this must be a custom commit
|
|
836
|
+
# in the given tree - make sure the user checks this commit.
|
|
837
|
+
if orig_cmt == "" then
|
|
838
|
+
msg="Custom"
|
|
839
|
+
orig_cmt=runGit("rev-parse HEAD")
|
|
840
|
+
log(:WARNING, "Custom commit, please double-check!")
|
|
841
|
+
runBash("PS1_WARNING='CHECK'")
|
|
842
|
+
end
|
|
843
|
+
if new_head != prev_head
|
|
844
|
+
make_pretty(orig_cmt, msg)
|
|
845
|
+
end
|
|
846
|
+
return
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
# Steal/cherry-pick all upstream commits in the given revision range.
|
|
850
|
+
#
|
|
851
|
+
# @param opts [Hash] Options hash
|
|
852
|
+
# @param range [String] Git revision range (e.g., 'base..HEAD')
|
|
853
|
+
# @param mainline [Boolean] Whether to treat commits as mainline cherry-picks
|
|
854
|
+
# @raise [CPSkip] If any cherry-pick of a patch is skipped by the user
|
|
855
|
+
# @raise [CPAbort] If cherry-pick is aborted by the user
|
|
856
|
+
def steal_all(opts, range, mainline = false)
|
|
857
|
+
skipped = []
|
|
858
|
+
runGit("log --no-merges --format=\"%H\" #{range} | tac").split("\n").each(){|commit|
|
|
859
|
+
begin
|
|
860
|
+
steal_one(opts, commit, mainline)
|
|
861
|
+
rescue CPSkip => e
|
|
862
|
+
log(:INFO, e.message)
|
|
863
|
+
skipped << commit
|
|
864
|
+
rescue CPAbort => e
|
|
865
|
+
log(:INFO, "Cherry-pick aborted by user.")
|
|
866
|
+
raise e
|
|
867
|
+
end
|
|
868
|
+
}
|
|
869
|
+
if skipped.length > 0
|
|
870
|
+
raise CPSkip.new(skipped.join(" "))
|
|
871
|
+
end
|
|
872
|
+
return
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
def same_sha?(ref1, ref2)
|
|
876
|
+
begin
|
|
877
|
+
c1=@repo.ref_exist?(ref1)
|
|
878
|
+
c2=@repo.ref_exist?(ref2)
|
|
879
|
+
return c1 == c2
|
|
880
|
+
rescue
|
|
881
|
+
return false
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
# Add files and commit them to the release branch.
|
|
887
|
+
#
|
|
888
|
+
# @param opts [Hash] Options hash
|
|
889
|
+
# @param filelist [Array<String>] List of file paths to add
|
|
890
|
+
# @param commit_path [String, nil] Path to a file containing the commit message
|
|
891
|
+
# @param commit_msg [String, nil] Direct commit message text
|
|
892
|
+
# @raise [MissingArgumentError] If both commit_msg and commit_path are nil
|
|
893
|
+
# @raise [GitMaintainError] If the git commit fails
|
|
894
|
+
def release_do_add_commit(opts, filelist, commit_path, commit_msg=nil)
|
|
895
|
+
edit_flag = ""
|
|
896
|
+
edit_flag = "--edit" if opts[:no_edit] == false
|
|
897
|
+
|
|
898
|
+
raise MissingArgumentError.new("commit message/path") if commit_path == nil && commit_msg == nil
|
|
899
|
+
commit_flag=""
|
|
900
|
+
if commit_msg != nil
|
|
901
|
+
commit_flag = "-m '#{commit_msg}'"
|
|
902
|
+
else
|
|
903
|
+
commit_flag = "-F '#{commit_path}'"
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
# Add and commit
|
|
907
|
+
begin
|
|
908
|
+
runGit("add " + filelist.join(" "))
|
|
909
|
+
runGitInteractive("commit #{commit_flag} --verbose #{edit_flag} --signoff")
|
|
910
|
+
rescue RunError
|
|
911
|
+
raise GitMaintainError.new("Failed to commit on branch #{@local_branch}")
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
# Create a signed annotated release tag.
|
|
916
|
+
#
|
|
917
|
+
# @param opts [Hash] Options hash
|
|
918
|
+
# @param version [String] The version string/tag name
|
|
919
|
+
# @param tag_path [String] Path to file containing the tag message
|
|
920
|
+
# @raise [GitMaintainError] If tagging fails
|
|
921
|
+
def release_do_tag(opts, version, tag_path)
|
|
922
|
+
edit_flag = ""
|
|
923
|
+
edit_flag = "--edit" if opts[:no_edit] == false
|
|
924
|
+
begin
|
|
925
|
+
runGitInteractive("tag -a -s #{version} #{edit_flag} -F #{tag_path}")
|
|
926
|
+
rescue RunError
|
|
927
|
+
raise GitMaintainError.new("Failed to tag branch #{@local_branch}")
|
|
928
|
+
end
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
# Add, commit, and tag files for a release.
|
|
932
|
+
#
|
|
933
|
+
# @param opts [Hash] Options hash
|
|
934
|
+
# @param filelist [Array<String>] List of file paths to add and commit
|
|
935
|
+
# @param version [String] The version string/tag name
|
|
936
|
+
# @param message_path [String] Path to file containing the commit/tag message
|
|
937
|
+
# @raise [GitMaintainError] If committing or tagging fails
|
|
938
|
+
def release_do_add_commit_tag(opts, filelist, version, message_path)
|
|
939
|
+
release_do_add_commit(opts, filelist, message_path)
|
|
940
|
+
release_do_tag(opts, version, message_path)
|
|
941
|
+
end
|
|
942
|
+
|
|
943
|
+
end
|
|
944
|
+
end
|