railbow 0.1.0 โ 0.3.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 +51 -0
- data/README.md +17 -12
- data/lib/railbow/init.rb +13 -13
- data/lib/railbow/migration_parser.rb +21 -1
- data/lib/railbow/notes_formatter.rb +5 -5
- data/lib/railbow/params.rb +4 -0
- data/lib/railbow/tasks/migrate_status.rake +120 -23
- data/lib/railbow/version.rb +1 -1
- data/lib/railbow.rb +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c42f0f6d8162662344a6ef8fb12388e8dd6a2a579384cab3829a85d806cb1571
|
|
4
|
+
data.tar.gz: 064fb908f8cd7567e9d8be53800e6972aa48faf65348aff7338c6088aa7427dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f7af392359a26db27919f6bac5d31ce22d6736615bbbe8d514c794e5114a2a71ff61081160340a3177fd4b172207b8c07ae2948fcfdaeaccbc0498b28370fb1c
|
|
7
|
+
data.tar.gz: e5a9364c62c93fa530cbde3222c2249b3f251043514fda4d4cf51a33cfaf27ad54bffed5d5abae43befe065634faee0d96ec60581adc22004cd45790e7f87534
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,57 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-07-23
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Ghost rows in `db:migrate:status` now surface mighost 0.5's classification:
|
|
13
|
+
a superseded ghost (re-timestamped migration) shows a calmer ๐ชฆ status with
|
|
14
|
+
a `โก <version>` badge pointing at its successor, and a ghost deleted with no
|
|
15
|
+
surviving branch shows `โ deleted in:<sha>`. Branch badges are unchanged and
|
|
16
|
+
take precedence right after supersession.
|
|
17
|
+
- `RBW_FORCE=1` forces Railbow formatting past every auto-detection
|
|
18
|
+
(`NO_COLOR`, CI, piped output, LLM agent detection). An explicit
|
|
19
|
+
`RBW_PLAIN=1` still wins.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Ghost data now comes from `Mighost::API.orphaned_migrations`, so
|
|
24
|
+
`db:migrate:status` honors mighost dismissals and `hide_superseded` for the
|
|
25
|
+
first time: a dismissed or hidden ghost renders as plain `NO FILE` instead
|
|
26
|
+
of ๐ป. Live git/worktree recovery still kicks in for versions without a
|
|
27
|
+
stored snapshot, so fresh clones keep working with zero setup.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- A ghost row without a branch no longer inherits the previous ghost row's
|
|
32
|
+
branch badge (a stale local leaked across loop iterations; unreachable
|
|
33
|
+
before mighost 0.5 made branchless ghosts the normal case).
|
|
34
|
+
|
|
35
|
+
## [0.2.0] - 2026-07-23
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Affected-table detection now recognizes tables passed via keyword
|
|
40
|
+
arguments (`table:`, `to_table:`, `from_table:`), covering project-level
|
|
41
|
+
migration helpers and DSLs that the ActiveRecord method allow-list missed.
|
|
42
|
+
- `change_column_default` and `change_column_null` are recognized, and
|
|
43
|
+
foreign-key methods contribute their first table even when the target is
|
|
44
|
+
given as `to_table:`.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Ruby comments no longer produce phantom tables: content is stripped of
|
|
49
|
+
`#` comments (preserving `#{}` interpolation) before scanning, so a
|
|
50
|
+
comment mentioning `ALTER TABLE` or a commented-out `create_table` does
|
|
51
|
+
not count.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
|
|
55
|
+
- Ghost recovery integration targets [mighost](https://github.com/amberpixels/mighost)
|
|
56
|
+
0.5, which classifies superseded ghosts and reports accurate branch
|
|
57
|
+
attribution.
|
|
58
|
+
|
|
8
59
|
## [0.1.0] - 2026-07-08
|
|
9
60
|
|
|
10
61
|
First public release.
|
data/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
- **About** - polished `rails about` output
|
|
23
23
|
- **Git Integration** - authors, diffs, branch origin, landing dates, uncommitted file indicators
|
|
24
24
|
- **Calendar View** - month separators and week tick markers for migration timelines
|
|
25
|
-
- **Ghost Recovery** -
|
|
25
|
+
- **Ghost Recovery** - uses the [mighost](https://github.com/amberpixels/mighost) gem (optional) to recover names, authors, and branch badges for `NO FILE` migrations
|
|
26
26
|
- **Smart Defaults** - auto-disables in CI, piped output, `NO_COLOR`, and LLM agents
|
|
27
27
|
|
|
28
28
|
## Installation
|
|
@@ -97,16 +97,16 @@ becomes a rich, information-dense dashboard (with default config):
|
|
|
97
97
|
|
|
98
98
|
Out of the box you get:
|
|
99
99
|
|
|
100
|
-
- **Calendar separators**
|
|
101
|
-
- **Status aliases**
|
|
102
|
-
- **Created At**
|
|
103
|
-
- **Landing dates**
|
|
104
|
-
- **Branch badges**
|
|
105
|
-
- **Affected tables**
|
|
106
|
-
- **Time filtering**
|
|
107
|
-
- **Your migrations highlighted**
|
|
100
|
+
- **Calendar separators** - month + ISO week headers to orient you in time
|
|
101
|
+
- **Status aliases** - `โโ` / `โโ` instead of `up` / `down` (customizable)
|
|
102
|
+
- **Created At** - timestamp parsed from the migration ID
|
|
103
|
+
- **Landing dates** - `โคป Mar 04` badge when a migration was merged to main after its creation
|
|
104
|
+
- **Branch badges** - `โ PS-142` showing the source branch/ticket
|
|
105
|
+
- **Affected tables** - color-coded table names extracted from migration files
|
|
106
|
+
- **Time filtering** - only the last 70 days shown by default (`since: 70d`)
|
|
107
|
+
- **Your migrations highlighted** - rows authored by you are visually distinct
|
|
108
108
|
|
|
109
|
-
If the [mighost](https://github.com/amberpixels/mighost) gem is installed, migrations whose files were deleted (e.g. after switching branches) show up with a ๐ป status and their recovered name
|
|
109
|
+
If the [mighost](https://github.com/amberpixels/mighost) gem is installed (`gem "mighost", group: [:development, :test]`), migrations whose files were deleted (e.g. after switching branches) show up with a ๐ป status and their recovered name instead of a bare `********** NO FILE **********` row - Railbow talks to it through the stable `Mighost::API`. Each ghost row carries the most informative badge mighost can provide: `โก <version>` when the migration was re-timestamped and lives on under another version (shown with a calmer ๐ชฆ status), `โฅ <branch>` when a branch still holds the file, or `โ deleted in:<sha>` pointing at the commit that removed it. Ghosts dismissed via `mighost:dismiss` (or hidden by `hide_superseded`) render as plain `NO FILE`.
|
|
110
110
|
|
|
111
111
|
### `rails db:migrate:down`
|
|
112
112
|
|
|
@@ -126,7 +126,7 @@ Code statistics rendered as a colorful table with highlighted totals and code-to
|
|
|
126
126
|
|
|
127
127
|
### `rails notes`
|
|
128
128
|
|
|
129
|
-
Annotations enriched with git blame data
|
|
129
|
+
Annotations enriched with git blame data - author names, commit dates, and color-coded tags (TODO=yellow, FIXME=red, OPTIMIZE=cyan, HACK=red, NOTE=green).
|
|
130
130
|
|
|
131
131
|
## Configuration
|
|
132
132
|
|
|
@@ -176,6 +176,7 @@ Every option can also be set via `RBW_*` environment variables, which override c
|
|
|
176
176
|
| Variable | Example | Description |
|
|
177
177
|
|---|---|---|
|
|
178
178
|
| `RBW_PLAIN` | `1` | Disable all formatting |
|
|
179
|
+
| `RBW_FORCE` | `1` | Force formatting even when piped, in CI, or run by an LLM agent (`RBW_PLAIN=1` still wins) |
|
|
179
180
|
| `RBW_SINCE` | `2mo`, `70d`, `1y`, `all` | Filter migrations by time period |
|
|
180
181
|
| `RBW_DATE` | `full`, `rel`, `short`, `custom(%b %d)` | Date display format |
|
|
181
182
|
| `RBW_GIT` | `author:me,diff,mask:auto` | Git integration options |
|
|
@@ -207,7 +208,7 @@ RBW_SORT=date rails notes
|
|
|
207
208
|
|
|
208
209
|
## How It Works
|
|
209
210
|
|
|
210
|
-
Railbow integrates through a Rails Railtie
|
|
211
|
+
Railbow integrates through a Rails Railtie - it prepends formatter modules onto existing Rails classes without modifying your code:
|
|
211
212
|
|
|
212
213
|
- `ActiveRecord::Migration` - migration output
|
|
213
214
|
- `ActiveRecord::Tasks::DatabaseTasks` - migration status
|
|
@@ -221,6 +222,10 @@ Formatting auto-disables when:
|
|
|
221
222
|
- Output is piped or redirected (non-TTY)
|
|
222
223
|
- Running inside an LLM agent (`CLAUDECODE` env var)
|
|
223
224
|
|
|
225
|
+
`RBW_FORCE=1` overrides all of the auto-detection above (useful for capturing
|
|
226
|
+
formatted output to a file, or letting an agent inspect the real rendering).
|
|
227
|
+
An explicit `RBW_PLAIN=1` always wins over `RBW_FORCE`.
|
|
228
|
+
|
|
224
229
|
## Requirements
|
|
225
230
|
|
|
226
231
|
- Ruby >= 3.1.0
|
data/lib/railbow/init.rb
CHANGED
|
@@ -26,16 +26,16 @@ module Railbow
|
|
|
26
26
|
# sort: "file"
|
|
27
27
|
|
|
28
28
|
# Git integration (comma-separated compound value):
|
|
29
|
-
# author
|
|
30
|
-
# author:me
|
|
31
|
-
# author:all
|
|
32
|
-
# diff
|
|
33
|
-
# base:<branch>
|
|
34
|
-
# mask:auto
|
|
35
|
-
# mask:<re>
|
|
29
|
+
# author - add Author column (same as author:all)
|
|
30
|
+
# author:me - highlight your own migrations
|
|
31
|
+
# author:all - show all authors
|
|
32
|
+
# diff - tag migrations by git origin branch
|
|
33
|
+
# base:<branch> - base branch for diff (default: auto-detected)
|
|
34
|
+
# mask:auto - auto-extract ticket id from branch name
|
|
35
|
+
# mask:<re> - custom regex to extract branch label, e.g. mask:(WS-[^/]+)/
|
|
36
36
|
git: "author:all,diff,mask:auto"
|
|
37
37
|
|
|
38
|
-
# Author display format
|
|
38
|
+
# Author display format - preset or custom pattern (FF L, FFF LL, etc.):
|
|
39
39
|
# initials (J D), short (Jo D), first_name (John), last_name (Doe),
|
|
40
40
|
# full_name (John Doe), full_name_short (John D.)
|
|
41
41
|
author_format: "short"
|
|
@@ -50,11 +50,11 @@ module Railbow
|
|
|
50
50
|
# date: "full"
|
|
51
51
|
|
|
52
52
|
# Compact mode (comma-separated):
|
|
53
|
-
# oneline
|
|
54
|
-
# dense
|
|
55
|
-
# noheader
|
|
56
|
-
# maxw:<N>
|
|
57
|
-
# hide:<col>
|
|
53
|
+
# oneline - one line per migration
|
|
54
|
+
# dense - reduce padding
|
|
55
|
+
# noheader - hide table headers
|
|
56
|
+
# maxw:<N> - max column width
|
|
57
|
+
# hide:<col> - hide a column (repeatable)
|
|
58
58
|
# compact: ""
|
|
59
59
|
|
|
60
60
|
# Rename column headers and cell values in table output
|
|
@@ -8,6 +8,7 @@ module Railbow
|
|
|
8
8
|
SINGLE_TABLE_METHODS = %w[
|
|
9
9
|
create_table drop_table change_table
|
|
10
10
|
add_column remove_column rename_column change_column
|
|
11
|
+
change_column_default change_column_null
|
|
11
12
|
add_index remove_index
|
|
12
13
|
add_reference remove_reference
|
|
13
14
|
add_belongs_to remove_belongs_to
|
|
@@ -19,7 +20,9 @@ module Railbow
|
|
|
19
20
|
add_foreign_key remove_foreign_key
|
|
20
21
|
].freeze
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
# Dual-table methods are included here too: their first argument is always a
|
|
24
|
+
# table, even when the second is passed as `to_table:` instead of positionally.
|
|
25
|
+
SINGLE_TABLE_PATTERN = /\b(?:#{(SINGLE_TABLE_METHODS + DUAL_TABLE_METHODS).join("|")})\s+[:"](\w+)/
|
|
23
26
|
DUAL_TABLE_PATTERN = /\b(?:#{DUAL_TABLE_METHODS.join("|")})\s+[:"](\w+)["\s,]+[:"](\w+)/
|
|
24
27
|
|
|
25
28
|
# SQL keywords followed by a table name
|
|
@@ -33,6 +36,17 @@ module Railbow
|
|
|
33
36
|
/\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?/i
|
|
34
37
|
].freeze
|
|
35
38
|
|
|
39
|
+
# Helper/DSL calls that name their target table via a keyword argument, e.g.
|
|
40
|
+
# `convert_to_monthly_partitions(table: :apples)` or
|
|
41
|
+
# `remove_foreign_key :accounts, to_table: :owners`. Catches project-level
|
|
42
|
+
# migration helpers regardless of the method name. Bare `from:`/`to:` are
|
|
43
|
+
# deliberately not matched โ `change_column_default :users, :status,
|
|
44
|
+
# from: nil, to: "active"` would yield phantom tables.
|
|
45
|
+
KEYWORD_TABLE_PATTERN = /\b(?:table|to_table|from_table):\s*[:"']?(\w+)/
|
|
46
|
+
|
|
47
|
+
# Ruby `#` comments up to end of line, but not `#{}` interpolation.
|
|
48
|
+
COMMENT_PATTERN = /#(?!\{).*/
|
|
49
|
+
|
|
36
50
|
# AR class-level query/mutation methods that strongly signal a constant is a model.
|
|
37
51
|
# Kept narrow on purpose โ methods like `find`, `all`, `first`, `count`, `create`,
|
|
38
52
|
# `new`, `transaction`, `order`, `includes` collide with stdlib/non-model constants
|
|
@@ -60,6 +74,11 @@ module Railbow
|
|
|
60
74
|
def self.extract_tables_from_content(content)
|
|
61
75
|
return [] if content.nil? || content.empty?
|
|
62
76
|
|
|
77
|
+
# Comments would otherwise produce phantom tables โ an explanatory
|
|
78
|
+
# "# ALTER TABLE foo ..." or a commented-out create_table reads exactly
|
|
79
|
+
# like the real thing to the patterns below.
|
|
80
|
+
content = content.gsub(COMMENT_PATTERN, "")
|
|
81
|
+
|
|
63
82
|
tables = []
|
|
64
83
|
|
|
65
84
|
content.scan(SINGLE_TABLE_PATTERN) { |match| tables << match[0] }
|
|
@@ -67,6 +86,7 @@ module Railbow
|
|
|
67
86
|
SQL_TABLE_PATTERNS.each do |pattern|
|
|
68
87
|
content.scan(pattern) { |match| tables << match[0] }
|
|
69
88
|
end
|
|
89
|
+
content.scan(KEYWORD_TABLE_PATTERN) { |match| tables << match[0] }
|
|
70
90
|
tables.uniq!
|
|
71
91
|
|
|
72
92
|
# Model-based detection is heuristic (a constant that happens to respond
|
|
@@ -256,17 +256,17 @@ module Railbow
|
|
|
256
256
|
|
|
257
257
|
\e[1mOptions:\e[0m
|
|
258
258
|
RBW_GIT=<options> Git integration (comma-separated):
|
|
259
|
-
author
|
|
260
|
-
author:all
|
|
261
|
-
author:me
|
|
259
|
+
author - show all authors (same as author:all)
|
|
260
|
+
author:all - show author + date on each annotation
|
|
261
|
+
author:me - highlight your own annotations
|
|
262
262
|
|
|
263
263
|
RBW_SINCE=<period> Filter annotations by blame date (default: all)
|
|
264
264
|
Values: all, 2mo, 1w, 30d, 1y, etc.
|
|
265
265
|
Units: d (days), w (weeks), mo/m (months), y (years)
|
|
266
266
|
|
|
267
267
|
RBW_SORT=<mode> Sort order (default: file)
|
|
268
|
-
file
|
|
269
|
-
date
|
|
268
|
+
file - group by file (default Rails order)
|
|
269
|
+
date - sort by blame date (newest first)
|
|
270
270
|
|
|
271
271
|
RBW_PLAIN=1 Disable Railbow formatting (plain Rails output)
|
|
272
272
|
|
data/lib/railbow/params.rb
CHANGED
|
@@ -12,8 +12,113 @@ require_relative "../logo"
|
|
|
12
12
|
# db:migrate:status and db:migrate:status:<database_name> tasks.
|
|
13
13
|
module Railbow
|
|
14
14
|
module MigrateStatusFormatter
|
|
15
|
+
# Ghost migration data normalized for rendering, whether it came from
|
|
16
|
+
# mighost's orphan classification or a live snapshot recovery.
|
|
17
|
+
class GhostRow
|
|
18
|
+
attr_reader :filename, :branch_name, :source, :superseded_by, :deleted_in_sha,
|
|
19
|
+
:author_name, :author_email, :content
|
|
20
|
+
|
|
21
|
+
def initialize(filename: nil, branch_name: nil, source: nil, superseded_by: nil,
|
|
22
|
+
deleted_in_sha: nil, author_name: nil, author_email: nil, content: nil)
|
|
23
|
+
@filename = filename
|
|
24
|
+
@branch_name = branch_name
|
|
25
|
+
@source = source
|
|
26
|
+
@superseded_by = superseded_by
|
|
27
|
+
@deleted_in_sha = deleted_in_sha
|
|
28
|
+
@author_name = author_name
|
|
29
|
+
@author_email = author_email
|
|
30
|
+
@content = content
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
15
34
|
private
|
|
16
35
|
|
|
36
|
+
def mighost_attr(obj, name)
|
|
37
|
+
obj.respond_to?(name) ? obj.public_send(name) : nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def mighost_snapshot_content(version)
|
|
41
|
+
Mighost::API.find_snapshot(version)&.content
|
|
42
|
+
rescue
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def load_ghost_rows(versions, with_content: false)
|
|
47
|
+
# Detect once: OrphanedMigration carries the classification (supersession,
|
|
48
|
+
# deletion commit) that a bare snapshot doesn't, and already honors
|
|
49
|
+
# dismissals and hide_superseded.
|
|
50
|
+
orphans = begin
|
|
51
|
+
Mighost::API.orphaned_migrations.to_h { |o| [o.version.to_s, o] }
|
|
52
|
+
rescue
|
|
53
|
+
return {}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
rows = {}
|
|
57
|
+
versions.each do |v|
|
|
58
|
+
# Absent from detect = deliberately suppressed (dismissed, or superseded
|
|
59
|
+
# with hide_superseded on) - render as plain NO FILE, don't re-recover.
|
|
60
|
+
next unless (orphan = orphans[v])
|
|
61
|
+
|
|
62
|
+
if orphan.filename && !orphan.filename.empty?
|
|
63
|
+
rows[v] = GhostRow.new(
|
|
64
|
+
filename: orphan.filename,
|
|
65
|
+
branch_name: orphan.branch_name,
|
|
66
|
+
source: mighost_attr(orphan, :source),
|
|
67
|
+
superseded_by: mighost_attr(orphan, :superseded_by),
|
|
68
|
+
deleted_in_sha: mighost_attr(orphan, :deleted_in_sha),
|
|
69
|
+
author_name: mighost_attr(orphan, :author_name),
|
|
70
|
+
author_email: mighost_attr(orphan, :author_email),
|
|
71
|
+
content: with_content ? mighost_snapshot_content(v) : nil
|
|
72
|
+
)
|
|
73
|
+
else
|
|
74
|
+
# Detect reads stored snapshots only. A version it lists without a
|
|
75
|
+
# filename has no snapshot yet, so fall back to live git/worktree
|
|
76
|
+
# recovery - keeps fresh clones working with zero setup.
|
|
77
|
+
snapshot = begin
|
|
78
|
+
Mighost::API.find_or_recover_snapshot(v)
|
|
79
|
+
rescue
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
next unless snapshot&.filename && !snapshot.filename.empty?
|
|
83
|
+
|
|
84
|
+
rows[v] = GhostRow.new(
|
|
85
|
+
filename: snapshot.filename,
|
|
86
|
+
branch_name: snapshot.branch_name,
|
|
87
|
+
source: mighost_attr(snapshot, :source),
|
|
88
|
+
superseded_by: api_superseded_by(v),
|
|
89
|
+
deleted_in_sha: mighost_attr(snapshot, :deleted_in_sha),
|
|
90
|
+
author_name: mighost_attr(snapshot, :author_name),
|
|
91
|
+
author_email: mighost_attr(snapshot, :author_email),
|
|
92
|
+
content: with_content ? snapshot.content : nil
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
rows
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def api_superseded_by(version)
|
|
100
|
+
return nil unless Mighost::API.respond_to?(:superseded_by)
|
|
101
|
+
|
|
102
|
+
Mighost::API.superseded_by(version)
|
|
103
|
+
rescue
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# One tag slot per ghost row; most informative wins.
|
|
108
|
+
def ghost_tag(ghost)
|
|
109
|
+
if ghost.superseded_by
|
|
110
|
+
"\e[38;5;245mโก #{ghost.superseded_by}\e[38;5;217m"
|
|
111
|
+
elsif ghost.branch_name
|
|
112
|
+
if ghost.source == "worktree"
|
|
113
|
+
"\e[38;5;222mโฅโ#{ghost.branch_name}\e[38;5;217m"
|
|
114
|
+
else
|
|
115
|
+
"\e[38;5;222mโฅ #{ghost.branch_name}\e[38;5;217m"
|
|
116
|
+
end
|
|
117
|
+
elsif ghost.deleted_in_sha
|
|
118
|
+
"\e[38;5;245mโ deleted in:#{ghost.deleted_in_sha[0, 8]}\e[38;5;217m"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
17
122
|
def git_migration_authors(migrate_dir)
|
|
18
123
|
output, _status = Railbow::GitUtils.capture2(
|
|
19
124
|
"log", "--format=COMMIT:%aN\t%aE", "--diff-filter=AR", "--name-status", "--", migrate_dir
|
|
@@ -304,6 +409,9 @@ module Railbow
|
|
|
304
409
|
|
|
305
410
|
RBW_PLAIN=1 Disable Railbow formatting (plain Rails output)
|
|
306
411
|
|
|
412
|
+
RBW_FORCE=1 Force Railbow formatting even when piped, in CI,
|
|
413
|
+
or called by an LLM agent (RBW_PLAIN=1 still wins)
|
|
414
|
+
|
|
307
415
|
RBW_HELP=1 Show this help message
|
|
308
416
|
|
|
309
417
|
\e[2mAuto-disabled when piped, in CI, or when called by an LLM agent.\e[0m
|
|
@@ -428,19 +536,12 @@ module Railbow
|
|
|
428
536
|
uncommitted_files.each { |f| branch_origins[f] ||= current_branch }
|
|
429
537
|
end
|
|
430
538
|
|
|
431
|
-
# Load mighost
|
|
539
|
+
# Load mighost ghost data for "NO FILE" migrations (if mighost gem is available)
|
|
432
540
|
mighost_snapshots = {}
|
|
433
541
|
mighost_available = defined?(Mighost::API) && Mighost.enabled?
|
|
434
542
|
if mighost_available
|
|
435
543
|
no_file_versions = db_list.select { |_, _, n| n.include?("NO FILE") }.map { |_, v, _| v.to_s }
|
|
436
|
-
no_file_versions
|
|
437
|
-
snapshot = begin
|
|
438
|
-
Mighost::API.find_or_recover_snapshot(v)
|
|
439
|
-
rescue
|
|
440
|
-
nil
|
|
441
|
-
end
|
|
442
|
-
mighost_snapshots[v] = snapshot if snapshot&.filename && !snapshot.filename.empty?
|
|
443
|
-
end
|
|
544
|
+
mighost_snapshots = load_ghost_rows(no_file_versions, with_content: tables_enabled) if no_file_versions.any?
|
|
444
545
|
end
|
|
445
546
|
|
|
446
547
|
# Build columns
|
|
@@ -501,29 +602,25 @@ module Railbow
|
|
|
501
602
|
ghost_snapshot = name.include?("NO FILE") ? mighost_snapshots[version.to_s] : nil
|
|
502
603
|
if name.include?("NO FILE") && ghost_snapshot
|
|
503
604
|
ghost_rows << idx
|
|
504
|
-
# Mighost recovered this ghost migration โ show
|
|
505
|
-
|
|
605
|
+
# Mighost recovered this ghost migration โ show ghost status + name + badge.
|
|
606
|
+
# A superseded ghost lives on under another version: stale bookkeeping,
|
|
607
|
+
# not a lost migration, so it gets a calmer glyph.
|
|
608
|
+
colored_status = ghost_snapshot.superseded_by ? "๐ชฆ" : "๐ป"
|
|
506
609
|
ghost_name = ghost_snapshot.filename
|
|
507
610
|
.sub(/\A\d+_/, "") # strip version prefix
|
|
508
611
|
.sub(/\.rb\z/, "") # strip extension
|
|
509
612
|
.tr("_", " ")
|
|
510
613
|
.gsub(/\b\w/, &:upcase) # titleize
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
else
|
|
515
|
-
"\e[38;5;222mโฅ #{ghost_snapshot.branch_name}\e[38;5;217m"
|
|
516
|
-
end
|
|
517
|
-
end
|
|
518
|
-
if branch_tag && name_col_width
|
|
519
|
-
tag_width = formatter.display_width(formatter.strip_ansi(branch_tag))
|
|
614
|
+
ghost_badge = ghost_tag(ghost_snapshot)
|
|
615
|
+
if ghost_badge && name_col_width
|
|
616
|
+
tag_width = formatter.display_width(formatter.strip_ansi(ghost_badge))
|
|
520
617
|
available = name_col_width - tag_width - 2
|
|
521
618
|
ghost_name = formatter.truncate_str(ghost_name, available)
|
|
522
619
|
name_width = formatter.display_width(ghost_name)
|
|
523
620
|
padding = name_col_width - name_width - tag_width
|
|
524
|
-
display_name = "#{ghost_name}#{" " * [padding, 2].max}#{
|
|
525
|
-
elsif
|
|
526
|
-
display_name = "#{ghost_name} #{
|
|
621
|
+
display_name = "#{ghost_name}#{" " * [padding, 2].max}#{ghost_badge}"
|
|
622
|
+
elsif ghost_badge
|
|
623
|
+
display_name = "#{ghost_name} #{ghost_badge}"
|
|
527
624
|
else
|
|
528
625
|
display_name = ghost_name
|
|
529
626
|
end
|
data/lib/railbow/version.rb
CHANGED
data/lib/railbow.rb
CHANGED
|
@@ -9,8 +9,10 @@ module Railbow
|
|
|
9
9
|
|
|
10
10
|
# Returns true when Railbow formatting should be disabled.
|
|
11
11
|
# Checks for explicit opt-out, standard conventions, CI, and LLM agents.
|
|
12
|
+
# RBW_PLAIN always wins; RBW_FORCE beats every auto-detection below it.
|
|
12
13
|
def self.plain?
|
|
13
14
|
return true if Params.plain?
|
|
15
|
+
return false if Params.force?
|
|
14
16
|
return true if ENV.key?("NO_COLOR")
|
|
15
17
|
return true if ENV.key?("CLAUDECODE")
|
|
16
18
|
return true if ENV.key?("CI")
|