railbow 0.4.0 → 0.5.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.md +54 -0
- data/README.md +79 -1
- data/lib/railbow/config.rb +3 -1
- data/lib/railbow/init.rb +16 -0
- data/lib/railbow/multi_db.rb +87 -0
- data/lib/railbow/params.rb +54 -0
- data/lib/railbow/status/ghosts.rb +144 -0
- data/lib/railbow/status/git_data.rb +327 -0
- data/lib/railbow/status/help.rb +121 -0
- data/lib/railbow/status/printer.rb +324 -0
- data/lib/railbow/status/section.rb +551 -0
- data/lib/railbow/table/column.rb +7 -2
- data/lib/railbow/table/renderer.rb +47 -18
- data/lib/railbow/tasks/migrate_status.rake +30 -756
- data/lib/railbow/version.rb +1 -1
- metadata +7 -1
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "date"
|
|
4
|
+
require_relative "../git_utils"
|
|
5
|
+
require_relative "../multi_db"
|
|
6
|
+
require_relative "../params"
|
|
7
|
+
|
|
8
|
+
module Railbow
|
|
9
|
+
module Status
|
|
10
|
+
# Every git lookup db:migrate:status makes about a migrations directory:
|
|
11
|
+
# who wrote each migration, when it landed on the mainline, which branch
|
|
12
|
+
# introduced it, and what is still uncommitted.
|
|
13
|
+
#
|
|
14
|
+
# One instance per migrations directory. Multi-database runs reuse the
|
|
15
|
+
# instance across databases that share a directory, which is what keeps
|
|
16
|
+
# `git log` from running once per database.
|
|
17
|
+
class GitData
|
|
18
|
+
EMPTY_AUTHORS = {names: {}, emails: {}}.freeze
|
|
19
|
+
|
|
20
|
+
attr_reader :migrate_dir, :author_names, :author_emails, :landed_dates,
|
|
21
|
+
:git_email, :git_name, :branch_origins, :uncommitted_files,
|
|
22
|
+
:incoming_merge_files, :merge_source_label
|
|
23
|
+
|
|
24
|
+
# migrate_dir is nil when no migration file could be located, in which
|
|
25
|
+
# case every file-scoped lookup is skipped and the accessors stay empty.
|
|
26
|
+
def initialize(migrate_dir:, author_enabled:, diff_enabled:, base_override: "", branch_mask: "")
|
|
27
|
+
@migrate_dir = migrate_dir
|
|
28
|
+
@author_names = {}
|
|
29
|
+
@author_emails = {}
|
|
30
|
+
@landed_dates = {}
|
|
31
|
+
@branch_origins = {}
|
|
32
|
+
@uncommitted_files = Set.new
|
|
33
|
+
@incoming_merge_files = Set.new
|
|
34
|
+
@merge_source_label = nil
|
|
35
|
+
|
|
36
|
+
load_authors(author_enabled)
|
|
37
|
+
load_diff(branch_mask, base_override) if diff_enabled && migrate_dir
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Shares one instance per migrations directory across a multi-database
|
|
41
|
+
# run. Databases that share a directory (shards) or a run that renders
|
|
42
|
+
# several databases would otherwise repeat the same full-history
|
|
43
|
+
# `git log` once per database.
|
|
44
|
+
def self.for(migrate_dir:, **opts)
|
|
45
|
+
cache = Railbow::MultiDb.current&.git_cache
|
|
46
|
+
return new(migrate_dir: migrate_dir, **opts) unless cache
|
|
47
|
+
|
|
48
|
+
cache[[migrate_dir, opts]] ||= new(migrate_dir: migrate_dir, **opts)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extracts a display label from a branch name. "auto" uses the built-in
|
|
52
|
+
# ticket pattern; anything else is a regex whose first capture wins.
|
|
53
|
+
def self.apply_branch_mask(branch, branch_mask)
|
|
54
|
+
return branch if branch_mask.empty?
|
|
55
|
+
return Railbow::Params.extract_branch_ticket(branch) if branch_mask == "auto"
|
|
56
|
+
|
|
57
|
+
re = begin
|
|
58
|
+
Regexp.new(branch_mask, Regexp::IGNORECASE)
|
|
59
|
+
rescue RegexpError
|
|
60
|
+
return branch
|
|
61
|
+
end
|
|
62
|
+
m = branch.match(re)
|
|
63
|
+
(m && m[1]) ? m[1] : branch
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def apply_branch_mask(branch, branch_mask)
|
|
69
|
+
self.class.apply_branch_mask(branch, branch_mask)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_authors(author_enabled)
|
|
73
|
+
if migrate_dir
|
|
74
|
+
@landed_dates = migration_landed_dates
|
|
75
|
+
if author_enabled
|
|
76
|
+
result = migration_authors
|
|
77
|
+
@author_names = result[:names]
|
|
78
|
+
@author_emails = result[:emails]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
return unless author_enabled
|
|
82
|
+
|
|
83
|
+
@git_email = current_git_email
|
|
84
|
+
@git_name = current_git_name
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def load_diff(branch_mask, base_override)
|
|
88
|
+
base_branch = detect_default_branch(base_override)
|
|
89
|
+
@branch_origins = branch_migration_origins(base_branch, branch_mask)
|
|
90
|
+
@incoming_merge_files = incoming_merge_files_from_git
|
|
91
|
+
@merge_source_label = detect_merge_source_label(branch_mask) if @incoming_merge_files.any?
|
|
92
|
+
@uncommitted_files = uncommitted_migration_files
|
|
93
|
+
|
|
94
|
+
# Uncommitted files have no commit to attribute, so they belong to
|
|
95
|
+
# whatever branch is checked out right now.
|
|
96
|
+
current_branch = current_branch_name(branch_mask)
|
|
97
|
+
@uncommitted_files.each { |f| @branch_origins[f] ||= current_branch }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def migration_authors
|
|
101
|
+
output, _status = Railbow::GitUtils.capture2(
|
|
102
|
+
"log", "--format=COMMIT:%aN\t%aE", "--diff-filter=AR", "--name-status", "--", migrate_dir
|
|
103
|
+
)
|
|
104
|
+
return EMPTY_AUTHORS.dup if output.empty?
|
|
105
|
+
|
|
106
|
+
names = {}
|
|
107
|
+
emails = {}
|
|
108
|
+
current_name = nil
|
|
109
|
+
current_email = nil
|
|
110
|
+
output.each_line do |line|
|
|
111
|
+
line = line.strip
|
|
112
|
+
if line.start_with?("COMMIT:")
|
|
113
|
+
parts = line.sub("COMMIT:", "").split("\t", 2)
|
|
114
|
+
current_name = parts[0]
|
|
115
|
+
current_email = parts[1]&.downcase
|
|
116
|
+
elsif !line.empty? && current_name
|
|
117
|
+
basename = name_status_basename(line)
|
|
118
|
+
next unless basename
|
|
119
|
+
names[basename] ||= current_name
|
|
120
|
+
emails[basename] ||= current_email
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
{names: names, emails: emails}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# basename => Date the migration first appeared on the mainline (the
|
|
127
|
+
# merge commit date, via --first-parent).
|
|
128
|
+
def migration_landed_dates
|
|
129
|
+
output, _status = Railbow::GitUtils.capture2(
|
|
130
|
+
"log", "--first-parent", "--format=COMMIT:%cI", "--diff-filter=AR", "--name-status", "--", migrate_dir
|
|
131
|
+
)
|
|
132
|
+
return {} if output.empty?
|
|
133
|
+
|
|
134
|
+
dates = {}
|
|
135
|
+
current_date = nil
|
|
136
|
+
output.each_line do |line|
|
|
137
|
+
line = line.strip
|
|
138
|
+
if line.start_with?("COMMIT:")
|
|
139
|
+
current_date = begin
|
|
140
|
+
Date.parse(line.sub("COMMIT:", ""))
|
|
141
|
+
rescue Date::Error
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
elsif !line.empty? && current_date
|
|
145
|
+
basename = name_status_basename(line)
|
|
146
|
+
next unless basename
|
|
147
|
+
dates[basename] ||= current_date
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
dates
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# --name-status lines are "A\tfilepath" or "Rnnn\told\tnew"; a rename
|
|
154
|
+
# attributes to its destination.
|
|
155
|
+
def name_status_basename(line)
|
|
156
|
+
cols = line.split("\t")
|
|
157
|
+
filepath = cols[0]&.start_with?("R") ? cols[2] : cols[1]
|
|
158
|
+
filepath ? File.basename(filepath) : nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def current_git_email
|
|
162
|
+
output, _status = Railbow::GitUtils.capture2("config", "user.email")
|
|
163
|
+
output.strip.downcase
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def current_git_name
|
|
167
|
+
output, _status = Railbow::GitUtils.capture2("config", "user.name")
|
|
168
|
+
output.strip
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def current_branch_name(branch_mask)
|
|
172
|
+
output, status = Railbow::GitUtils.capture2("rev-parse", "--abbrev-ref", "HEAD")
|
|
173
|
+
return "HEAD" unless status.success?
|
|
174
|
+
|
|
175
|
+
apply_branch_mask(output.strip, branch_mask)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def detect_default_branch(override)
|
|
179
|
+
return override if override && !override.empty?
|
|
180
|
+
|
|
181
|
+
output, status = Railbow::GitUtils.capture2("symbolic-ref", "refs/remotes/origin/HEAD")
|
|
182
|
+
if status.success?
|
|
183
|
+
branch = output.strip.sub(%r{^refs/remotes/origin/}, "")
|
|
184
|
+
return branch unless branch.empty?
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
%w[main master].each do |candidate|
|
|
188
|
+
_, st = Railbow::GitUtils.capture2("rev-parse", "--verify", "refs/heads/#{candidate}")
|
|
189
|
+
return candidate if st.success?
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
"main"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def branch_migration_origins(base_branch, branch_mask)
|
|
196
|
+
merge_base_out, mb_status = Railbow::GitUtils.capture2("merge-base", "HEAD", base_branch)
|
|
197
|
+
return {} unless mb_status.success?
|
|
198
|
+
|
|
199
|
+
merge_base = merge_base_out.strip
|
|
200
|
+
diff_out, diff_status = Railbow::GitUtils.capture2(
|
|
201
|
+
"diff", "--name-status", "--diff-filter=AR", merge_base, "HEAD", "--", migrate_dir
|
|
202
|
+
)
|
|
203
|
+
return {} unless diff_status.success?
|
|
204
|
+
|
|
205
|
+
files = diff_out.each_line.map { |l|
|
|
206
|
+
cols = l.strip.split("\t")
|
|
207
|
+
cols[0]&.start_with?("R") ? cols[2] : cols[1]
|
|
208
|
+
}.compact.reject(&:empty?)
|
|
209
|
+
origins = {}
|
|
210
|
+
|
|
211
|
+
files.each do |filepath|
|
|
212
|
+
basename = File.basename(filepath)
|
|
213
|
+
commit = adding_commit(filepath)
|
|
214
|
+
next unless commit
|
|
215
|
+
|
|
216
|
+
branches = branches_containing(commit)
|
|
217
|
+
next if branches.empty?
|
|
218
|
+
|
|
219
|
+
best = pick_origin_branch(branches, commit)
|
|
220
|
+
origins[basename] = best ? apply_branch_mask(best, branch_mask) : best
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
origins
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def adding_commit(filepath)
|
|
227
|
+
commit_out, cs = Railbow::GitUtils.capture2(
|
|
228
|
+
"log", "--diff-filter=AR", "--format=%H", "-1", "--", filepath
|
|
229
|
+
)
|
|
230
|
+
return nil unless cs.success?
|
|
231
|
+
|
|
232
|
+
commit = commit_out.strip
|
|
233
|
+
commit.empty? ? nil : commit
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def branches_containing(commit)
|
|
237
|
+
branches_out, bs = Railbow::GitUtils.capture2(
|
|
238
|
+
"branch", "--contains", commit, "--format=%(refname:short)"
|
|
239
|
+
)
|
|
240
|
+
return [] unless bs.success?
|
|
241
|
+
|
|
242
|
+
branches_out.each_line.map(&:strip).reject(&:empty?)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Picks the branch that originally introduced the commit.
|
|
246
|
+
# 1. Drop child branches: if A is an ancestor of B, the commit came from A.
|
|
247
|
+
# 2. Among the rest, prefer the branch with the most commits after the
|
|
248
|
+
# adding commit - it has been active longest since, so it is the
|
|
249
|
+
# original rather than a newer fork.
|
|
250
|
+
def pick_origin_branch(branches, commit)
|
|
251
|
+
return branches.first if branches.size == 1
|
|
252
|
+
|
|
253
|
+
filtered = branches.reject do |b|
|
|
254
|
+
branches.any? do |other|
|
|
255
|
+
next false if other == b
|
|
256
|
+
_, st = Railbow::GitUtils.capture2("merge-base", "--is-ancestor", other, b)
|
|
257
|
+
st.success?
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
filtered = branches if filtered.empty?
|
|
261
|
+
return filtered.first if filtered.size == 1
|
|
262
|
+
|
|
263
|
+
filtered.max_by do |b|
|
|
264
|
+
count_out, _ = Railbow::GitUtils.capture2("rev-list", "--count", "#{commit}..#{b}")
|
|
265
|
+
count_out.strip.to_i
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def uncommitted_migration_files
|
|
270
|
+
output, status = Railbow::GitUtils.capture2("status", "--porcelain", "--", migrate_dir)
|
|
271
|
+
return Set.new unless status.success?
|
|
272
|
+
|
|
273
|
+
result = Set.new
|
|
274
|
+
output.each_line do |line|
|
|
275
|
+
code = line[0..1]
|
|
276
|
+
|
|
277
|
+
if code[0] == "R"
|
|
278
|
+
# Rename: "R old -> new" or "R100 old -> new". Take the destination.
|
|
279
|
+
parts = line[3..].split(" -> ", 2)
|
|
280
|
+
filepath = (parts[1] || parts[0]).strip
|
|
281
|
+
elsif ["??", "A ", "AM", "M "].include?(code)
|
|
282
|
+
filepath = line[3..].strip
|
|
283
|
+
else
|
|
284
|
+
next
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
result << File.basename(filepath) unless filepath.empty?
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# During an in-progress merge, files from MERGE_HEAD (e.g. main) show up
|
|
291
|
+
# as staged additions. Exclude them so they are not tagged as ours.
|
|
292
|
+
result - incoming_merge_files_from_git
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def detect_merge_source_label(branch_mask)
|
|
296
|
+
merge_head, _, status = Railbow::GitUtils.capture3("rev-parse", "MERGE_HEAD")
|
|
297
|
+
return nil unless status.success?
|
|
298
|
+
|
|
299
|
+
branches_out, _, bs = Railbow::GitUtils.capture3(
|
|
300
|
+
"branch", "--contains", merge_head.strip, "--format=%(refname:short)"
|
|
301
|
+
)
|
|
302
|
+
return nil unless bs.success?
|
|
303
|
+
|
|
304
|
+
branches = branches_out.each_line.map(&:strip).reject(&:empty?)
|
|
305
|
+
return nil if branches.empty?
|
|
306
|
+
|
|
307
|
+
branch = branches.first if branches.size == 1
|
|
308
|
+
branch ||= branches.find { |b| %w[main master develop].include?(b) }
|
|
309
|
+
branch ||= branches.first
|
|
310
|
+
|
|
311
|
+
apply_branch_mask(branch, branch_mask)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def incoming_merge_files_from_git
|
|
315
|
+
_, _, mh_status = Railbow::GitUtils.capture3("rev-parse", "MERGE_HEAD")
|
|
316
|
+
return Set.new unless mh_status.success?
|
|
317
|
+
|
|
318
|
+
output, _, status = Railbow::GitUtils.capture3(
|
|
319
|
+
"diff", "--name-only", "--diff-filter=AR", "HEAD", "MERGE_HEAD", "--", migrate_dir
|
|
320
|
+
)
|
|
321
|
+
return Set.new unless status.success?
|
|
322
|
+
|
|
323
|
+
Set.new(output.each_line.map { |l| File.basename(l.strip) }.reject(&:empty?))
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../logo"
|
|
4
|
+
|
|
5
|
+
module Railbow
|
|
6
|
+
module Status
|
|
7
|
+
module Help
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def print
|
|
11
|
+
Railbow.print_logo
|
|
12
|
+
puts <<~HELP
|
|
13
|
+
|
|
14
|
+
Enhanced db:migrate:status
|
|
15
|
+
|
|
16
|
+
\e[1mUsage:\e[0m
|
|
17
|
+
[RBW_*=value ...] rake db:migrate:status
|
|
18
|
+
|
|
19
|
+
\e[1mOptions:\e[0m
|
|
20
|
+
RBW_SINCE=<period> Filter migrations by age (default: all)
|
|
21
|
+
Values: all, 2mo, 1w, 30d, 1y, etc.
|
|
22
|
+
Units: d (days), w (weeks), mo/m (months), y (years)
|
|
23
|
+
|
|
24
|
+
RBW_SINCE_MIN=<n> Never show fewer than n migrations, however
|
|
25
|
+
old (default: 10). The window above is a
|
|
26
|
+
soft limit: a database with five migrations
|
|
27
|
+
shows all five rather than reporting an
|
|
28
|
+
empty period. 0 disables the floor.
|
|
29
|
+
|
|
30
|
+
RBW_DATE=<mode> Date column format (default: full):
|
|
31
|
+
full - 2026-01-30 12:08:54 (column: Created At)
|
|
32
|
+
rel - ~3d ago
|
|
33
|
+
short - Jan 30 (column: Date)
|
|
34
|
+
custom(…) - user strftime, e.g. custom(%b %d, %Y)
|
|
35
|
+
|
|
36
|
+
RBW_VIEW=<options> Display options (comma-separated):
|
|
37
|
+
calendar - show month/year separator lines + week ticks
|
|
38
|
+
tables - parse migration files, show Tables column
|
|
39
|
+
|
|
40
|
+
RBW_COMPACT=<options> Compact display (comma-separated):
|
|
41
|
+
oneline - truncate instead of wrapping
|
|
42
|
+
dense - remove cell padding
|
|
43
|
+
noheader - hide table header row
|
|
44
|
+
maxw:<n> - cap column widths at n chars
|
|
45
|
+
hide:<col> - hide a column by name (repeatable)
|
|
46
|
+
|
|
47
|
+
RBW_CALENDAR=<options> Calendar sub-options (requires RBW_VIEW=calendar):
|
|
48
|
+
(empty) - month separators only, no week
|
|
49
|
+
markers at all
|
|
50
|
+
wticks - week tick marks on the date column
|
|
51
|
+
wdividers - a separator row per ISO week
|
|
52
|
+
counts - append "· N migrations" to every
|
|
53
|
+
separator row (that section)
|
|
54
|
+
label:<fmt> - strftime for month separators
|
|
55
|
+
(default: %b %Y W%V)
|
|
56
|
+
wlabel:<fmt> - strftime for week separators
|
|
57
|
+
(default: same as label, so the
|
|
58
|
+
week number never shifts)
|
|
59
|
+
|
|
60
|
+
RBW_DB=<options> Multi-database runs (default: focus).
|
|
61
|
+
An empty value expands every database into
|
|
62
|
+
its own section, in database.yml order,
|
|
63
|
+
with column widths shared so the tables
|
|
64
|
+
line up. Databases sharing a migrations
|
|
65
|
+
path (shards) merge into one table with a
|
|
66
|
+
status per database.
|
|
67
|
+
focus - expand only the first
|
|
68
|
+
database, summarizing the
|
|
69
|
+
rest in a line each. A
|
|
70
|
+
database with migrations
|
|
71
|
+
pending is always expanded
|
|
72
|
+
only:<name> - render only this database
|
|
73
|
+
(repeatable)
|
|
74
|
+
skip:<name> - drop this database
|
|
75
|
+
(repeatable)
|
|
76
|
+
full - draw every database in full,
|
|
77
|
+
overriding focus and never
|
|
78
|
+
collapsing a quiet one
|
|
79
|
+
inline - one merged table ordered by
|
|
80
|
+
version, with a Db column
|
|
81
|
+
|
|
82
|
+
RBW_GIT=<options> Git integration (comma-separated):
|
|
83
|
+
author - add an Author column (same as author:all)
|
|
84
|
+
author:all - add an Author column
|
|
85
|
+
author:me - highlight your own migrations
|
|
86
|
+
diff - tag migrations by git origin
|
|
87
|
+
base:<branch> - base branch for diff (default: auto-detected)
|
|
88
|
+
mask:<re> - regex to extract branch label
|
|
89
|
+
e.g. mask:(WS-[^/]+)/
|
|
90
|
+
mask:auto - auto-extract ticket id from branch name
|
|
91
|
+
|
|
92
|
+
RBW_PLAIN=1 Disable Railbow formatting (plain Rails output)
|
|
93
|
+
|
|
94
|
+
RBW_FORCE=1 Force Railbow formatting even when piped, in CI,
|
|
95
|
+
or called by an LLM agent (RBW_PLAIN=1 still wins)
|
|
96
|
+
|
|
97
|
+
RBW_HELP=1 Show this help message
|
|
98
|
+
|
|
99
|
+
\e[2mAuto-disabled when piped, in CI, or when called by an LLM agent.\e[0m
|
|
100
|
+
|
|
101
|
+
\e[1mExamples:\e[0m
|
|
102
|
+
rake db:migrate:status
|
|
103
|
+
RBW_SINCE=2mo RBW_VIEW=calendar rake db:migrate:status
|
|
104
|
+
RBW_CALENDAR=wdividers,counts rake db:migrate:status
|
|
105
|
+
RBW_VIEW=tables RBW_GIT=author rake db:migrate:status
|
|
106
|
+
RBW_GIT=author:me RBW_SINCE=3mo rake db:migrate:status
|
|
107
|
+
RBW_DATE=rel rake db:migrate:status
|
|
108
|
+
RBW_DATE=short rake db:migrate:status
|
|
109
|
+
RBW_DATE='custom(%b %d, %Y)' rake db:migrate:status
|
|
110
|
+
RBW_GIT=diff rake db:migrate:status
|
|
111
|
+
RBW_GIT=diff,base:develop rake db:migrate:status
|
|
112
|
+
RBW_GIT=diff,mask:(WS-[^/]+)/ rake db:migrate:status
|
|
113
|
+
RBW_DB= rake db:migrate:status
|
|
114
|
+
RBW_DB=only:primary rake db:migrate:status
|
|
115
|
+
RBW_DB=inline rake db:migrate:status
|
|
116
|
+
|
|
117
|
+
HELP
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|