π GitLab MR Tracker
GitLab MR Tracker is a fast, asynchronous Terminal User Interface (TUI) dashboard designed for engineering teams. It provides real-time verification of GitLab Merge Requests across target environment branches (main, preproduction, staging, etc.), handling strict SHA verification as well as cherry-picked commit identification.

β¨ Key Features
-
π OS Keyring Integration (Zero Plain-Text Secrets): Personal Access Tokens (PAT) can be securely stored directly in your OS secret manager (GNOME Keyring, KWallet, macOS Keychain, or Windows Credential Manager).
-
π·οΈ Dynamic Scoped Labels & Custom Chips:
- Smart Filtering: Configure specific label prefixes (e.g.,
deploy::,review::) to display cleanly as colored chips in the main table grid, while keeping all attached tags visible in the side inspector panel. - Customizable Palette: Map label names or wildcard patterns (e.g.,
deploy::*) to custom terminal colors or standard HEX codes (#FF5733) via an XDG-compliant JSON config. Labels without a config override automatically fall back to their GitLab-side colour (fetched at startup), with foreground computed for legibility.
- Smart Filtering: Configure specific label prefixes (e.g.,
-
β‘ High Performance & Asynchronous: Powered by
tokioandreqwest, utilizing non-blocking event loops and bounded concurrent requests via semaphores to protect GitLab API rate limits. -
π‘οΈ Pass-Through Pass Caching: Core MR metadata (author, milestone, assignee, description, labels) is fetched once and cached locally. Fully deployed MRs bypass network re-queries entirely ("Green Pass").
-
π Dual Match Verification Engine:
- System 1 (Strict SHA): Validates precise merge/squash commit SHAs on target branches (resistant to
git reset --hard). - System 2 (Intelligent Fuzzy Matcher): Uses a keyword relevance matrix to verify cherry-picked commits deployed across branches.
- System 1 (Strict SHA): Validates precise merge/squash commit SHAs on target branches (resistant to
-
π₯οΈ Responsive Flexbox TUI Grid: Features a dynamic layout engine (
Constraint::Fill) that seamlessly scales table columns and side panels from 1080p laptop displays to ultra-wide 4K monitors without empty trailing spaces. -
π Smart Auto-Sorting by Last Update: The dashboard defaults to sorting MRs by
updated_at(most recently pushed to remote first), automatically re-applied after each refresh. Cycle through sort columns (S) and toggle direction (Shift+S). The active sort is always visible in the table title bar. -
π Browser Integration: Open any selected MR directly in your default browser with a single keypress (
O). -
π Smart Desktop Notifications: Receives native OS desktop notifications only when an MR's branch status has changed since the last run β no duplicate alerts on restart or redundant refreshes.
-
β¨ Refresh Highlight: After each background refresh, any MR whose
updated_attimestamp has changed since the previous cycle is briefly highlighted in the table with a green tint. The highlight fades out automatically after ~10 seconds. -
π XDG-Compliant Persistence: Saves tracked dashboard state, UI configurations, and last-known branch statuses automatically to platform-standard configuration paths using
directories. -
Customizable Refresh Interval: Tailor the background polling rate to your needs (defaults to 15 minutes / 900s) via
config.jsonor theGITLAB_REFRESH_INTERVAL_SECSenvironment variable. -
π Activity Badge: Each MR in the Context Inspector displays a color-coded activity badge based on its
updated_attimestamp β π’ Active, π‘ Slowing, or π΄ Stale. Thresholds are fully configurable viaconfig.jsonor environment variables (ACTIVITY_RECENT_DAYS,ACTIVITY_STALE_DAYS). -
π¬ Notes Indicator: The total number of comments and discussion threads (
user_notes_count) is fetched from the GitLab API at no extra cost and displayed both in the optional Notes table column and in the Context Inspector. A yellowπ¬ Nbadge signals that comments are awaiting attention; a dimmedβ No commentsconfirms there is nothing to address. -
π Animated Status Badge: For open MRs, the Status column cycles through three phases every second with no extra column:
Phase Badge Color Meaning 1 OPENπ© Green Base state 2 Mergeability varies Live mergeability from GitLab API 3 CI RUNNING/CI PENDINGπ§ Orange Latest pipeline is active β dimmed to (n/a)when no pipeline existsThe CI badge only appears when the most recent pipeline is in
RunningorPendingstate; otherwise phase 3 falls back to the mergeability badge. The animation keeps the layout compact while surfacing both merge-readiness and CI status at a glance. -
ποΈ Toggleable Table Columns (
C): PressCat any time to open an interactive column picker popup. Useβ/βto navigate andSpaceto toggle each optional column on or off. Your selection is instantly saved toconfig.jsonand persisted across restarts β no manual file editing required. Available optional columns:Column Description Activity Color-coded activity badge β π’ Active, π‘ Slowing, π΄ Stale (same thresholds as the Inspector) Target The branch the MR is intended to merge into Labels Filtered label chips (respects table_label_prefixes)Milestone The associated milestone title Notes Total number of comments and discussion threads β π¬ Nin yellow when non-zero, dimmedβ 0otherwiseAll columns are hidden by default to keep the layout compact. They can also be enabled statically via
visible_columnsinconfig.json(see configuration section below). -
β MR Flagging & Advanced Filters: Manually flag any MR with
Spaceto mark it with a coloured star chevron (β ) in the title column. PressFto open the filter picker popup, which lets you narrow the table by:Flagged ββ only your manually flagged MRs- GitLab state β
Opened,Merged, orClosed - Mergeability β
Mergeable,Conflict,Needs Rebase,Not Approved,Requested Changes,Draft,Discussions - Has comments β MRs with at least one note or discussion thread
- Milestone β free-text search on the milestone title (case-insensitive)
- Assignee β free-text search on the GitLab assignee and, when a tracker ticket is linked (e.g. Redmine), its assignee as well
The active filter is shown in the table header. Flagged state is persisted across restarts via
tracker_state.json. -
π Milestone Bulk-Add (Release Manager Workflow): In Insert mode, type
@followed by any part of a milestone name to trigger a live autocomplete dropdown. Active and upcoming milestones are fetched from GitLab on startup and filtered in real time as you type. Selecting a milestone withEnterautomatically adds all open MRs attached to that milestone in a single action β no need to enter IDs one by one. Ideal for release managers preparing a deployment checklist.i β Enter Insert mode @5.2 β filters milestones containing "5.2" β / Tab β navigate suggestions Enter β bulk-add all open MRs from the selected milestone Esc β close dropdown without selecting -
π¬ Pipeline Inspector (
P): PressPon any selected MR to toggle the side panel between MR metadata and its pipeline history. The last 5 pipeline runs are displayed with per-stage job breakdown, status icons, and execution durations:#9981 β passed βΈ test β lint (18s) β unit-tests (74s) βΈ build β build (42s) βΈ deploy β deploy-staging (31s)Pipeline data is fetched alongside MR metadata in the same refresh cycle and persisted to disk β so it is immediately available on restart without an extra network call. Re-fetching only occurs when GitLab reports a new
updated_attimestamp, keeping API usage minimal.
π Authentication & Configuration
The application requires your GitLab Project configuration and an API Personal Access Token.
Step 1: Set up environment variables (optional)
β¨ Zero-config first run: If no
.envfile orconfig.jsonis present,gitlab-trackerwill interactively prompt you for the required values on first launch and persist them automatically toconfig.json. No manual file setup is needed.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β FIRST-RUN INTERACTIVE ONBOARDING β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β π GitLab URL [https://gitlab.com]: _ β
β π’ GitLab Project ID: _ β
β π GitLab Personal Access Token: _ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
For teams and CI pipelines, you can still pre-configure everything via a .env file to skip the prompts entirely:
-
Copy the provided template to create your local
.envfile: -
Open
.envand specify your project details:# Required: Your target GitLab Project ID GITLAB_PROJECT_ID=12345678 # Optional: Custom self-hosted GitLab instance (Defaults to https://gitlab.com if omitted) GITLAB_URL=https://gitlab.my-company.com # Optional: Override token via environment variable (Not recommended for disk storage) # GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx # Optional: Override initial tracked branches for new sessions (comma-separated) DEFAULT_BRANCHES="main,develop" # Optional: Filter table column tags by prefix (comma-separated) TABLE_LABEL_PREFIXES="deploy::,review::" # Optional: Activity badge thresholds in the Context Inspector (in days) ACTIVITY_RECENT_DAYS=2 # π’ Green if updated within N days (default: 2) ACTIVITY_STALE_DAYS=7 # π΄ Red if not updated for N days (default: 7)
π Settings Resolution Order
Settings are resolved in the following order (highest to lowest priority):
- System Environment Variables & Local
.env(current directory) - Global
.env(~/.config/gitlab-tracker/.env) - User Config File (
~/.config/gitlab-tracker/config.json) - Built-in Fallback Defaults (
https://gitlab.com,["main"]for default branch)
Step 2: First-Run Interactive Onboarding & Keyring PAT Security Layer
Your GitLab personal access token is never stored in plain text.
On first launch, gitlab-tracker resolves each required value using the following priority order β prompting interactively only as a last resort:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SETTINGS LOOKUP ORDER β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β GITLAB_PROJECT_ID & GITLAB_URL β
β 1. Environment variable / .env file β
β 2. ~/.config/gitlab-tracker/config.json β
β 3. Interactive CLI prompt β saved to config.json β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β GITLAB_TOKEN β
β 1. GITLAB_TOKEN environment variable (if set) β
β 2. Native OS Keyring (GNOME Keyring / macOS Keychain) β
β 3. Interactive CLI prompt β saved to OS Keyring β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-
First-Run Onboarding: If no
GITLAB_TOKENis found in your.envor environment, the application will prompt you interactively in the terminal on its initial launch:π No GitLab URL found in config or environment. Leave empty to use the default (https://gitlab.com) GitLab URL [https://gitlab.com]: https://gitlab.my-company.com π’ No GitLab Project ID found in config or environment. Please enter your GitLab Project ID: 12345678 β Config saved to config.json! π No GITLAB_TOKEN found in environment or system Keyring. Please enter your GitLab Personal Access Token: glpat-xxxxxxxxxxxx β Token securely saved to OS Keyring! -
Secure Token Persistence: The token is encrypted and handed off directly to your operating system's native secret manager:
- Linux: GNOME Keyring / KWallet via Secret Service API
- macOS: Apple Keychain Service
- Windows: Windows Credential Manager
-
Subsequent Launches: You can delete the
GITLAB_TOKENentry from your.envcompletely. On subsequent runs,gitlab-trackerretrieves the token silently from the OS Keyring without requiring plain-text files or manual re-entry.
Step 3: UI, Default Branches & Label Customization (config.json)
On its first launch, the tool automatically generates a config.json file inside your OS user configuration directory:
- Linux:
~/.config/gitlab-tracker/config.json - macOS:
~/Library/Application Support/gitlab-tracker/config.json - Windows:
C:\Users\<User>\AppData\Roaming\gitlab-tracker\config.json
You can edit this file to adjust default environment branches, label badge colors, wildcard rules, and activity badge thresholds:
Optional table columns β By default the table only shows the fixed columns (ID, Title, Status) plus your tracked branches, keeping the layout compact. Enable any optional column individually in
config.jsonundervisible_columns:
Key Default Column shown activityfalseActivity β π’ Active / π‘ Slowing / π΄ Stale badge target_branchfalseTarget β the branch the MR merges into labelsfalseLabels β filtered label chips (respects table_label_prefixes)milestonefalseMilestone β the associated milestone title notesfalseNotes β total comment count ( π¬ Nin yellow when non-zero)Example β enable Activity and Target only:
"visible_columns":
Activity badge thresholds control the colored indicator displayed next to the
Updatedfield in the Context Inspector:
Badge Meaning Condition π’ Active Updated recently elapsed days < activity_recent_daysπ‘ Slowing Activity slowing down between the two thresholds π΄ Stale No recent activity elapsed days β₯ activity_stale_daysβ¬ Unknown Timestamp unavailable β
π How Desktop Notifications Work
Notifications fire on four events (new branch, MR updated, mergeability changed, milestone changed) and include a clickable "Open MR" button that opens the MR in your default browser. Change notifications are suppressed during the initial sync to avoid spurious alerts on restart.
See gitlab-tracker-notify/README.md for the full event reference, platform support details, and feature flags.
πΏ How Branch Resolution Works:
- Active Session Priority: If
tracker_state.jsonexists from a previous run, the app restores your last active layout (columns added/removed via input). - First Run / Fresh Session: If no state exists, initial branches are loaded from
DEFAULT_BRANCHESin.envif provided, falling back todefault_branchesinconfig.json(defaults to["main"]).
π¦ Installation
Recommended β Install from crates.io
The simplest way to install gitlab-tracker if you have Rust (1.80+) available:
This downloads, compiles, and installs the latest published release directly from crates.io into ~/.cargo/bin/. No cloning required.
Pre-built Binaries
If you prefer not to compile, download the latest pre-compiled binary for your architecture from the Releases Page and place it somewhere on your $PATH.
Building from Source
For development or to test unreleased changes, clone the repository and build manually:
# Build optimized release executable (builds all workspace members)
# Optional: install binary globally to ~/.cargo/bin/
# Build without desktop notifications (headless / CI environments)
Once installed via any of the methods above, launch the dashboard from any terminal folder:
β¨οΈ Dashboard Navigation & Shortcuts
The dashboard operates in two keyboard modes, inspired by vim:
π¦ Normal Mode (default)
Shortcut keys are active. The input field is passive.
| Shortcut | Action |
|---|---|
i or / |
Enter Insert mode β focus the input field |
β² / βΌ or k / j |
Navigate rows in the table |
Tab |
Cycle focus between panes: Dashboard β Inspector β Tracker β Dashboard (Tracker pane only when a ticket is linked) |
T |
When focus is on Dashboard or Inspector: jump to Tracker pane. When already on Tracker: open ticket URL in browser |
P |
Inspector pane focused: cycle MR Info β Pipelines. Tracker pane focused: toggle Ticket Info β Time Log |
L |
Log time on the linked tracker ticket (only when a tracker plugin is configured) |
C |
Open column picker β toggle optional columns on/off |
O |
Open selected MR in your default web browser |
R |
Force immediate network refresh for all MRs |
s |
Cycle sort column (Updated β ID β Milestone β Title β β¦) |
S |
Toggle sort direction (ascending / descending) |
Space |
Toggle flag β on the selected MR β persisted across restarts |
F |
Open filter picker (state, mergeability, notes, milestone, assigneeβ¦) |
Del |
Delete selected MR row |
Esc |
Quit dashboard |
π© Column Picker Mode
Opened with C. The table border turns cyan as a visual indicator.
| Shortcut | Action |
|---|---|
β² / βΌ or k / j |
Navigate the column list |
Space |
Toggle the highlighted column on/off |
Enter or Esc |
Close the picker β changes are saved immediately to config.json |
π¨ Insert Mode
The input field has exclusive focus. All printable keys feed the field β shortcuts are suspended. The input bar turns yellow as a visual indicator.
| Shortcut | Action |
|---|---|
142 + Enter |
Add MR ID !142 to tracking |
staging + Enter |
Add branch staging to target columns |
-142 + Enter |
Remove MR ID !142 from tracking |
-staging + Enter |
Remove branch column staging |
@name |
Filter milestones matching name β opens autocomplete dropdown |
Enter |
Submit input, or confirm highlighted milestone suggestion |
Esc |
Close autocomplete dropdown, or cancel and return to Normal mode |
π Milestone Autocomplete (Insert Mode)
When the input starts with @, a dropdown appears above the input bar listing all active/upcoming milestones fetched from GitLab. The list is filtered in real time as you type.
| Shortcut | Action |
|---|---|
β / β or Shift+Tab / Tab |
Navigate suggestions |
Enter |
Confirm selection β bulk-adds all open MRs from the milestone |
Esc |
Close dropdown without selecting |
Why two modes? Branch names starting with
s,S,p,P,o,O,rorRwould otherwise collide with shortcut keys. Insert mode guarantees the full branch name is captured without interference.
ποΈ Project Architecture
This project is structured as a Cargo workspace with four crates:
gitlab-tracker/ # Binary crate β TUI orchestrator
βββ src/
βββ main.rs # Entry point: wires providers, calls build_tracker_colors(), event loop
βββ app.rs # State machine, InputMode, row navigation & sort logic
βββ config.rs # Label filtering, wildcard matching, parse_color() & activity badge
βββ models.rs # Strongly-typed API DTOs & runtime event types
βββ gitlab.rs # Async network handling & rate-limit semaphores
βββ events.rs # Keyboard & mouse event dispatch (Normal / Insert mode routing)
βββ storage.rs # OS Keyring interface & XDG state/config persistence
βββ utils.rs # Fuzzy matching algorithmic utilities
βββ demo.rs # Demo mode with pre-populated mock data (screenshots & CI)
βββ ui/
βββ mod.rs # Root layout renderer & input bar (mode-aware)
βββ table.rs # Main MR table widget
βββ inspector.rs # Upper-right pane: MR metadata & pipeline history
βββ tracker.rs # Lower-right pane: linked ticket details & time log (TrackerLabelColors)
gitlab-tracker-core/ # Library crate β shared trait contracts, zero UI dependency
βββ src/
βββ lib.rs # Re-exports: TrackerProvider, LinkedTicket, LabelColorMaps, β¦
βββ provider.rs # TrackerProvider trait + all shared domain types
# LinkedTicket: flat ticket data (type, priority, version, progress, β¦)
# LabelColorMaps: raw (String, String) badge colour maps β no ratatui
gitlab-tracker-notify/ # Library crate β optional desktop notification plugin
βββ src/
βββ lib.rs # notify-rust integration (no-op stubs when feature `desktop` is disabled)
gitlab-tracker-redmine/ # Library crate β optional Redmine integration plugin
βββ src/
βββ lib.rs # RedmineProvider: implements TrackerProvider + label_colors()
βββ client.rs # Async Redmine REST API client (issue, time entries, activities)
βββ config.rs # RedmineConfig: YAML load/save, LabelColorConfig, onboarding prompt
βββ detector.rs # Regex-based ticket ID detector (title & description)
βββ keyring.rs # Secure token retrieval via OS Keyring
Optional Feature Flags
| Feature flag | Default | Effect |
|---|---|---|
notifications |
β enabled | Desktop notifications via notify-rust |
redmine |
β disabled | Redmine ticket & time-tracking integration (see gitlab-tracker-redmine) |
Tracker Plugins
gitlab-tracker supports optional external tracker integrations (Redmine, and future providers such as Jira or Linear) through a plugin architecture based on the TrackerProvider trait defined in gitlab-tracker-core.
When a tracker plugin is configured, the dashboard is enriched with:
- Linked ticket display in the Inspector β subject, type, priority, status, assignee, target version, start date, progress bar, and time tracking (estimate / spent / remaining)
- Coloured badges for Type and Priority β colours are fully configurable per-label in the plugin's config file (no hardcoded values β works with any language or custom workflow)
- Time Log view (
PΓ 2) β chronological list of time entries for the linked ticket, auto-refreshed on MR navigation - Log time (
L) β submit a new time entry directly from the TUI - Tracker column in the table (toggleable via
C)
Each plugin lives in its own crate and is activated via a Cargo feature flag. See the plugin's own README for setup instructions:
| Plugin | Feature flag | Documentation |
|---|---|---|
| Redmine | --features redmine |
gitlab-tracker-redmine/README.md |
π License
Distributed under the MIT License. See LICENSE for details.