git-maintain 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +14 -0
- data/LICENSE +674 -22
- data/README.md +26 -5
- data/lib/addons/RDMACore.rb +54 -3
- data/lib/branch.rb +84 -49
- data/lib/ci.rb +88 -0
- data/lib/common.rb +16 -11
- data/lib/repo.rb +143 -68
- data/lib/travis.rb +18 -61
- metadata +16 -8
data/README.md
CHANGED
@@ -16,9 +16,9 @@ The idea is to script most of the maintenance tasks so the maintainer can focus
|
|
16
16
|
- **list_stable**: List commit present in the stable branch but not in the latest associated relase
|
17
17
|
- **merge**: Merge branch with suffix specified in -m <suff> into the main branch
|
18
18
|
- **push**: Push branches to github for validation
|
19
|
-
- **monitor**: Check the
|
19
|
+
- **monitor**: Check the CI state of all branches
|
20
20
|
- **push_stable**: Push to stable repo
|
21
|
-
- **monitor_stable**: Check the
|
21
|
+
- **monitor_stable**: Check the CI state of all stable branches
|
22
22
|
- **release**: Create new release on all concerned branches
|
23
23
|
- **reset**: Reset branch against upstream
|
24
24
|
- **submit_release**: Push the to stable and create the release packages
|
@@ -157,7 +157,7 @@ Apply them to the appropriate branches
|
|
157
157
|
|
158
158
|
```git maintain cp -s deadbeef --version '1[789]'```
|
159
159
|
|
160
|
-
And push them to my own github repo so that
|
160
|
+
And push them to my own github repo so that CI will check everything out
|
161
161
|
|
162
162
|
```git maintain push --version '1[789]'```
|
163
163
|
|
@@ -179,7 +179,7 @@ Push it to my own github too.
|
|
179
179
|
|
180
180
|
```git maintain push --version '1[789]' -b pending```
|
181
181
|
|
182
|
-
Once this gets accepted (and
|
182
|
+
Once this gets accepted (and CI is OK too), I merge this branch back to my 'master'
|
183
183
|
|
184
184
|
```git maintain merge --version '1[789]' -m pending```
|
185
185
|
|
@@ -239,7 +239,7 @@ The tag will not have been propagated anywhere else and can be deleted manually.
|
|
239
239
|
|
240
240
|
```git maintain push_stable --version '1[789]'```
|
241
241
|
|
242
|
-
You can then monitor the status on
|
242
|
+
You can then monitor the status on CI
|
243
243
|
|
244
244
|
```git maintain monitor_stable --version '1[789]'```
|
245
245
|
|
@@ -258,6 +258,8 @@ Enjoy, and feel free to report bugs, missing features and/or send patches
|
|
258
258
|
|
259
259
|
This is a summary of all the settings that can be set in the git config:
|
260
260
|
|
261
|
+
- `maintain.valid-repo`: Remote github repo to test out branches before submitting. Default = `github`
|
262
|
+
- `maintain.stable-repo`: Remote stable github repository t submit validated branches and new releases. Default = `stable`
|
261
263
|
- `maintain.autofetch`: Enable/Disable auto fetching.
|
262
264
|
Can be overriden by the --[no-]fetch option on the CLI.
|
263
265
|
If unset, autofetch is enabled
|
@@ -274,3 +276,22 @@ This is a summary of all the settings that can be set in the git config:
|
|
274
276
|
- `maintain.mail-format`: Specify how release annoucement emails are sent. Can be:
|
275
277
|
- `imap_send`: Store prepared email in an IMAP folder. See `main git-imap-send` for more infos. This is the default value.
|
276
278
|
- `send_email`: Generates a file which is compatible with git send-email
|
279
|
+
|
280
|
+
# License
|
281
|
+
|
282
|
+
Unless otherwise stated, everything in this repo is covered by the following
|
283
|
+
copyright notice:
|
284
|
+
|
285
|
+
Copyright (c) 2018 SUSE
|
286
|
+
|
287
|
+
This program is free software: you can redistribute it and/or modify it
|
288
|
+
under the terms of the GNU General Public License version 3, as
|
289
|
+
published by the Free Software Foundation.
|
290
|
+
|
291
|
+
This program is distributed in the hope that it will be useful,
|
292
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
293
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
294
|
+
GNU General Public License for more details.
|
295
|
+
|
296
|
+
You should have received a copy of the GNU General Public License
|
297
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/lib/addons/RDMACore.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module GitMaintain
|
2
2
|
class RDMACoreBranch < Branch
|
3
3
|
REPO_NAME = "rdma-core"
|
4
|
+
AZURE_MIN_VERSION = 25
|
4
5
|
|
5
6
|
def self.set_opts(action, optsParser, opts)
|
6
7
|
opts[:rel_type] = nil
|
@@ -73,7 +74,7 @@ module GitMaintain
|
|
73
74
|
end
|
74
75
|
|
75
76
|
# Update version number in relevant files
|
76
|
-
@repo.run("sed -i -e 's/\\(Version:[[:space:]]*\\)[0-9.]\\+/\\1#{new_ver}/g'
|
77
|
+
@repo.run("sed -i -e 's/\\(Version:[[:space:]]*\\)[0-9.]\\+/\\1#{new_ver}/g' */*.spec")
|
77
78
|
@repo.run("sed -i -e 's/\\([sS][eE][tT](PACKAGE_VERSION[[:space:]]*\"\\)[0-9.]*\"/\\1#{new_ver}\"/g' CMakeLists.txt")
|
78
79
|
|
79
80
|
case opts[:rel_type]
|
@@ -93,7 +94,7 @@ mv debian/changelog.new debian/changelog")
|
|
93
94
|
end
|
94
95
|
|
95
96
|
# Add and commit
|
96
|
-
@repo.runGit("add
|
97
|
+
@repo.runGit("add */*.spec CMakeLists.txt debian/changelog")
|
97
98
|
@repo.runGitInteractive("commit -m '#{commit_msg} #{new_ver}' --verbose --edit --signoff")
|
98
99
|
if $? != 0 then
|
99
100
|
raise("Failed to commit on branch #{local_branch}")
|
@@ -108,7 +109,57 @@ mv debian/changelog.new debian/changelog")
|
|
108
109
|
`rm -f #{tag_path}`
|
109
110
|
end
|
110
111
|
end
|
112
|
+
class RDMACoreRepo < Repo
|
113
|
+
AZURE_MIN_VERSION = 25
|
114
|
+
def submitReleases(opts, new_tags)
|
115
|
+
new_tags.each(){|tag|
|
116
|
+
next if tag !~ /v([0-9]*)\.[0-9]*/
|
117
|
+
major=$1.to_i
|
118
|
+
# Starting from v27, do not create the github release ourself as this is done by Azure
|
119
|
+
createRelease(opts, tag, major < AZURE_MIN_VERSION)
|
120
|
+
}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
class RDMACoreCI < CI
|
125
|
+
AZURE_MIN_VERSION = 25
|
126
|
+
def initialize(repo)
|
127
|
+
super(repo)
|
128
|
+
@travis = GitMaintain::TravisCI.new(repo)
|
129
|
+
@azure = GitMaintain::AzureCI.new(repo, 'ucfconsort', 'ucfconsort')
|
130
|
+
|
131
|
+
# Auto generate all CI required methods
|
132
|
+
# Wicked ruby tricker to find all the public methods of CI but not of inherited classes
|
133
|
+
# to dynamically define these method in the object being created
|
134
|
+
(GitMaintain::CI.new(repo).public_methods() - Object.new.public_methods()).each(){|method|
|
135
|
+
# Skip specific emptyCache method
|
136
|
+
next if method == :emptyCache
|
111
137
|
|
138
|
+
self.define_singleton_method(method) { |br, *args|
|
139
|
+
if br.version =~ /([0-9]+)/
|
140
|
+
major=$1.to_i
|
141
|
+
elsif br.version == "master"
|
142
|
+
major=99999
|
143
|
+
else
|
144
|
+
raise("Unable to monitor branch #{br} on a CI")
|
145
|
+
end
|
146
|
+
if major < AZURE_MIN_VERSION
|
147
|
+
@travis.send(method, br, *args)
|
148
|
+
else
|
149
|
+
@azure.send(method, br, *args)
|
150
|
+
end
|
151
|
+
}
|
152
|
+
}
|
153
|
+
end
|
154
|
+
def emptyCache()
|
155
|
+
@travis.emptyCache()
|
156
|
+
@azure.emptyCache()
|
157
|
+
end
|
158
|
+
end
|
112
159
|
GitMaintain::registerCustom(RDMACoreBranch::REPO_NAME,
|
113
|
-
{
|
160
|
+
{
|
161
|
+
GitMaintain::Branch => RDMACoreBranch,
|
162
|
+
GitMaintain::Repo => RDMACoreRepo,
|
163
|
+
GitMaintain::CI => RDMACoreCI,
|
164
|
+
})
|
114
165
|
end
|
data/lib/branch.rb
CHANGED
@@ -33,34 +33,35 @@ module GitMaintain
|
|
33
33
|
"* list_stable: List commit present in the stable branch but not in the latest associated relase",
|
34
34
|
"* merge: Merge branch with suffix specified in -m <suff> into the main branch",
|
35
35
|
"* push: Push branches to github for validation",
|
36
|
-
"* monitor: Check the
|
36
|
+
"* monitor: Check the CI state of all branches",
|
37
37
|
"* push_stable: Push to stable repo",
|
38
|
-
"* monitor_stable: Check the
|
38
|
+
"* monitor_stable: Check the CI state of all stable branches",
|
39
39
|
"* release: Create new release on all concerned branches",
|
40
40
|
"* reset: Reset branch against upstream",
|
41
41
|
]
|
42
42
|
|
43
|
-
def self.load(repo, version,
|
43
|
+
def self.load(repo, version, ci, branch_suff)
|
44
44
|
repo_name = File.basename(repo.path)
|
45
|
-
return GitMaintain::loadClass(Branch, repo_name, repo, version,
|
45
|
+
return GitMaintain::loadClass(Branch, repo_name, repo, version, ci, branch_suff)
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.set_opts(action, optsParser, opts)
|
49
49
|
opts[:base_ver] = 0
|
50
|
-
opts[:version] =
|
50
|
+
opts[:version] = []
|
51
51
|
opts[:commits] = []
|
52
52
|
opts[:do_merge] = false
|
53
53
|
opts[:push_force] = false
|
54
|
-
opts[:
|
54
|
+
opts[:no_ci] = false
|
55
55
|
opts[:all] = false
|
56
56
|
opts[:check_only] = false
|
57
57
|
opts[:fetch] = nil
|
58
58
|
opts[:watch] = false
|
59
|
+
opts[:delete_remote] = false
|
59
60
|
|
60
61
|
optsParser.on("-v", "--base-version [MIN_VER]", Integer, "Older release to consider.") {
|
61
62
|
|val| opts[:base_ver] = val}
|
62
63
|
optsParser.on("-V", "--version [regexp]", Regexp, "Regexp to filter versions.") {
|
63
|
-
|val| opts[:version]
|
64
|
+
|val| opts[:version] << val}
|
64
65
|
|
65
66
|
if ALL_BRANCHES_ACTIONS.index(action) == nil &&
|
66
67
|
action != :merge &&
|
@@ -79,13 +80,16 @@ module GitMaintain
|
|
79
80
|
optsParser.banner += "-c <sha1> [-c <sha1> ...]"
|
80
81
|
optsParser.on("-c", "--sha1 [SHA1]", String, "Commit to cherry-pick. Can be used multiple time.") {
|
81
82
|
|val| opts[:commits] << val}
|
83
|
+
when :delete
|
84
|
+
optsParser.on("--remote", "Delete the remote staging branch instead of the local ones.") {
|
85
|
+
|val| opts[:delete_remote] = true}
|
82
86
|
when :merge
|
83
87
|
optsParser.banner += "-m <suffix>"
|
84
88
|
optsParser.on("-m", "--merge [SUFFIX]", "Merge branch with suffix.") {
|
85
89
|
|val| opts[:do_merge] = val}
|
86
90
|
when :monitor, :monitor_stable
|
87
91
|
optsParser.on("-w", "--watch <PERIOD>", Integer,
|
88
|
-
"Watch and refresh
|
92
|
+
"Watch and refresh CI status every <PERIOD>.") {
|
89
93
|
|val| opts[:watch] = val}
|
90
94
|
when :push
|
91
95
|
optsParser.banner += "[-f]"
|
@@ -93,8 +97,8 @@ module GitMaintain
|
|
93
97
|
|val| opts[:push_force] = val}
|
94
98
|
when :push_stable
|
95
99
|
optsParser.banner += "[-T]"
|
96
|
-
optsParser.on("-T", "--no-
|
97
|
-
|val| opts[:
|
100
|
+
optsParser.on("-T", "--no-ci", "Ignore CI build status and push anyway.") {
|
101
|
+
|val| opts[:no_ci] = true}
|
98
102
|
optsParser.on("-c", "--check", "Check if there is something to be pushed.") {
|
99
103
|
|val| opts[:check_only] = true}
|
100
104
|
when :steal
|
@@ -112,18 +116,19 @@ module GitMaintain
|
|
112
116
|
raise "Action #{opts[:action]} can only be done on 'master' suffixed branches"
|
113
117
|
end
|
114
118
|
end
|
115
|
-
if opts[:action] == :delete then
|
119
|
+
if opts[:action] == :delete && opts[:delete_remote] != true then
|
116
120
|
if opts[:br_suff] == "master" then
|
117
121
|
raise "Action #{opts[:action]} can NOT be done on 'master' suffixed branches"
|
118
122
|
end
|
119
123
|
end
|
124
|
+
opts[:version] = [ /.*/ ] if opts[:version].length == 0
|
120
125
|
end
|
121
126
|
|
122
127
|
def self.execAction(opts, action)
|
123
128
|
repo = Repo::load()
|
124
|
-
|
129
|
+
ci = CI::load(repo)
|
125
130
|
opts[:repo] = repo
|
126
|
-
opts[:
|
131
|
+
opts[:ci] = ci
|
127
132
|
brClass = GitMaintain::getClass(self, repo.name)
|
128
133
|
|
129
134
|
if NO_FETCH_ACTIONS.index(action) == nil && opts[:fetch] != false then
|
@@ -140,7 +145,7 @@ module GitMaintain
|
|
140
145
|
unfilteredList = repo.getBranchList(opts[:br_suff])
|
141
146
|
end
|
142
147
|
branchList = unfilteredList.map(){|br|
|
143
|
-
branch = Branch::load(repo, br,
|
148
|
+
branch = Branch::load(repo, br, ci, opts[:br_suff])
|
144
149
|
case branch.is_targetted?(opts)
|
145
150
|
when :too_old
|
146
151
|
GitMaintain::log(:VERBOSE, "Skipping older v#{branch.version}")
|
@@ -153,7 +158,7 @@ module GitMaintain
|
|
153
158
|
branch
|
154
159
|
}.compact()
|
155
160
|
else
|
156
|
-
branchList = [ Branch::load(repo, opts[:manual_branch],
|
161
|
+
branchList = [ Branch::load(repo, opts[:manual_branch], ci, opts[:br_suff]) ]
|
157
162
|
end
|
158
163
|
|
159
164
|
loop do
|
@@ -178,15 +183,15 @@ module GitMaintain
|
|
178
183
|
|
179
184
|
break if opts[:watch] == false
|
180
185
|
sleep(opts[:watch])
|
181
|
-
|
186
|
+
ci.emptyCache()
|
182
187
|
end
|
183
188
|
end
|
184
189
|
|
185
|
-
def initialize(repo, version,
|
190
|
+
def initialize(repo, version, ci, branch_suff)
|
186
191
|
GitMaintain::checkDirectConstructor(self.class)
|
187
192
|
|
188
193
|
@repo = repo
|
189
|
-
@
|
194
|
+
@ci = ci
|
190
195
|
@version = version
|
191
196
|
@branch_suff = branch_suff
|
192
197
|
|
@@ -223,10 +228,10 @@ module GitMaintain
|
|
223
228
|
if @version.to_i < opts[:base_ver] then
|
224
229
|
return :too_old
|
225
230
|
end
|
226
|
-
|
227
|
-
return
|
228
|
-
|
229
|
-
return
|
231
|
+
opts[:version].each() {|regexp|
|
232
|
+
return true if @version =~ regexp
|
233
|
+
}
|
234
|
+
return :no_match
|
230
235
|
end
|
231
236
|
|
232
237
|
# Checkout the repo to the given branch
|
@@ -271,7 +276,7 @@ module GitMaintain
|
|
271
276
|
end
|
272
277
|
|
273
278
|
master_sha=@repo.runGit("rev-parse origin/master")
|
274
|
-
res = steal_all(opts, "#{base_ref}..#{master_sha}")
|
279
|
+
res = steal_all(opts, "#{base_ref}..#{master_sha}", true)
|
275
280
|
|
276
281
|
# If we picked all the commits (or nothing happened)
|
277
282
|
# Mark the current master as the last checked point so we
|
@@ -345,22 +350,22 @@ module GitMaintain
|
|
345
350
|
"#{opts[:repo].valid_repo} #{branches.join(" ")}")
|
346
351
|
end
|
347
352
|
|
348
|
-
# Monitor the build status on
|
353
|
+
# Monitor the build status on CI
|
349
354
|
def monitor(opts)
|
350
|
-
st = @
|
355
|
+
st = @ci.getValidState(self, @head)
|
351
356
|
suff=""
|
352
357
|
case st
|
353
358
|
when "started"
|
354
|
-
suff= " started at #{@
|
359
|
+
suff= " started at #{@ci.getValidTS(self, @head)}"
|
355
360
|
end
|
356
361
|
log(:INFO, "Status for v#{@version}: " + st + suff)
|
357
|
-
if (
|
362
|
+
if @ci.isErrored(self, st) && opts[:watch] == false
|
358
363
|
rep = "y"
|
359
364
|
suff=""
|
360
365
|
while rep == "y"
|
361
366
|
rep = GitMaintain::confirm(opts, "see the build log#{suff}")
|
362
367
|
if rep == "y" then
|
363
|
-
log = @
|
368
|
+
log = @ci.getValidLog(self, @head)
|
364
369
|
tmp = `mktemp`.chomp()
|
365
370
|
tmpfile = File.open(tmp, "w+")
|
366
371
|
tmpfile.puts(log)
|
@@ -375,9 +380,9 @@ module GitMaintain
|
|
375
380
|
|
376
381
|
# Push branch to the stable repo
|
377
382
|
def push_stable(opts)
|
378
|
-
if (opts[:
|
379
|
-
@
|
380
|
-
log(:WARNING, "Build is not passed on
|
383
|
+
if (opts[:no_ci] != true && @NO_CI != true) &&
|
384
|
+
@ci.checkValidState(self, @head) != true then
|
385
|
+
log(:WARNING, "Build is not passed on CI. Skipping push to stable")
|
381
386
|
return
|
382
387
|
end
|
383
388
|
|
@@ -408,13 +413,13 @@ module GitMaintain
|
|
408
413
|
return if branches.length == 0
|
409
414
|
opts[:repo].runGit("push #{opts[:repo].stable_repo} #{branches.join(" ")}")
|
410
415
|
end
|
411
|
-
# Monitor the build status of the stable branch on
|
416
|
+
# Monitor the build status of the stable branch on CI
|
412
417
|
def monitor_stable(opts)
|
413
|
-
st = @
|
418
|
+
st = @ci.getStableState(self, @stable_head)
|
414
419
|
suff=""
|
415
420
|
case st
|
416
421
|
when "started"
|
417
|
-
suff= " started at #{@
|
422
|
+
suff= " started at #{@ci.getStableTS(self, @stable_head)}"
|
418
423
|
end
|
419
424
|
log(:INFO, "Status for v#{@version}: " + st + suff)
|
420
425
|
end
|
@@ -446,14 +451,36 @@ module GitMaintain
|
|
446
451
|
end
|
447
452
|
|
448
453
|
def delete(opts)
|
449
|
-
|
454
|
+
if opts[:delete_remote] == true then
|
455
|
+
msg = "delete remote branch #{@repo.valid_repo}/#{@local_branch}"
|
456
|
+
else
|
457
|
+
msg = "delete branch #{@local_branch}"
|
458
|
+
end
|
459
|
+
rep = GitMaintain::confirm(opts, msg)
|
450
460
|
if rep == "y" then
|
451
|
-
|
461
|
+
return @local_branch
|
452
462
|
else
|
453
463
|
log(:INFO, "Skipping deletion")
|
454
464
|
return
|
455
465
|
end
|
456
466
|
end
|
467
|
+
def self.delete_epilogue(opts, branches)
|
468
|
+
# Compact to remove empty entries
|
469
|
+
branches.compact!()
|
470
|
+
|
471
|
+
return if branches.length == 0
|
472
|
+
puts "Deleting #{opts[:delete_remote] == true ? "remote" : "local"} branches: #{branches.join(" ")}"
|
473
|
+
rep = GitMaintain::confirm(opts, "continue")
|
474
|
+
if rep != "y" then
|
475
|
+
log(:INFO, "Cancelling")
|
476
|
+
return
|
477
|
+
end
|
478
|
+
if opts[:delete_remote] == true then
|
479
|
+
opts[:repo].runGit("push #{opts[:repo].valid_repo} #{branches.map(){|x| ":" + x}.join(" ")}")
|
480
|
+
else
|
481
|
+
opts[:repo].runGit("branch -D #{branches.join(" ")}")
|
482
|
+
end
|
483
|
+
end
|
457
484
|
|
458
485
|
private
|
459
486
|
def add_blacklist(commit)
|
@@ -532,13 +559,15 @@ module GitMaintain
|
|
532
559
|
end
|
533
560
|
|
534
561
|
# Let's see if there's a version tag in this commit
|
535
|
-
full=@repo.runGit("show #{commit} | grep -i 'stable@'").gsub(/.*
|
562
|
+
full=@repo.runGit("show #{commit} | grep -i 'stable@'").gsub(/.* #?/, "")
|
536
563
|
|
537
564
|
# Sanity check our extraction
|
538
565
|
if full =~ /stable/ then
|
539
566
|
return false
|
540
567
|
end
|
541
568
|
|
569
|
+
full = @repo.runGit("rev-parse #{full}^{commit}")
|
570
|
+
|
542
571
|
# Make sure our branch contains this version
|
543
572
|
if @repo.runGit("merge-base #{@head} #{full}") == full then
|
544
573
|
return true
|
@@ -607,17 +636,23 @@ module GitMaintain
|
|
607
636
|
return do_cp
|
608
637
|
end
|
609
638
|
|
610
|
-
def steal_one(opts, commit)
|
611
|
-
subj=@repo.getCommitSubj(commit)
|
612
|
-
subj.gsub!(/"/, '\"')
|
639
|
+
def steal_one(opts, commit, mainline=false)
|
613
640
|
msg=''
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
641
|
+
orig_cmt=commit
|
642
|
+
|
643
|
+
if mainline == false then
|
644
|
+
subj=@repo.getCommitSubj(commit)
|
645
|
+
subj.gsub!(/"/, '\"')
|
646
|
+
# Let's grab the mainline commit id, this is useful if the version tag
|
647
|
+
# doesn't exist in the commit we're looking at but exists upstream.
|
648
|
+
orig_cmt=@repo.runGit("log --no-merges --format=\"%H\" -F --grep \"#{subj}\" " +
|
649
|
+
"#{@stable_base}..origin/master | tail -n1")
|
650
|
+
|
651
|
+
if orig_cmt == "" then
|
652
|
+
log(:WARNING, "Could not find commit #{commit} in mainline")
|
653
|
+
end
|
654
|
+
end
|
655
|
+
# If the commit doesn't apply for us, skip it
|
621
656
|
if is_relevant?(orig_cmt) != true
|
622
657
|
return true
|
623
658
|
end
|
@@ -663,10 +698,10 @@ module GitMaintain
|
|
663
698
|
end
|
664
699
|
end
|
665
700
|
|
666
|
-
def steal_all(opts, range)
|
701
|
+
def steal_all(opts, range, mainline = false)
|
667
702
|
res = true
|
668
703
|
@repo.runGit("log --no-merges --format=\"%H\" #{range} | tac").split("\n").each(){|commit|
|
669
|
-
res &= steal_one(opts, commit)
|
704
|
+
res &= steal_one(opts, commit, mainline)
|
670
705
|
}
|
671
706
|
return res
|
672
707
|
end
|