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.
-
π 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.
- 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 Mergeability Badge: For open MRs, the Status column alternates every second between the base
Openbadge and a live mergeability indicator sourced directly from the GitLab API:Badge Color Meaning Mergeableπ© Light green MR can be merged cleanly Conflictπ₯ Red Merge conflicts must be resolved Rebaseπ¨ Yellow Branch is behind target β rebase required No extra column is added: the animation keeps the layout compact while surfacing critical merge-readiness 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 & Focused Filter: Manually flag any MR with
Spaceto mark it with a coloured star chevron (β ) in the title column. PressFto cycle the active filter betweenAllandFlagged β, instantly narrowing the table to only your flagged MRs. Flagged state is persisted across restarts viatracker_state.jsonβ your watchlist survives application restarts and background refreshes. -
π 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.
The application requires your GitLab Project configuration and an API Personal Access Token.
β¨ 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:cp .env.example .env
-
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 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)
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.
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:
{
"project_id": "12345678",
"gitlab_url": "https://gitlab.my-company.com",
"refresh_interval_secs": 900,
"default_branches": [
"main"
],
"table_label_prefixes": [
"deploy::",
"review::"
],
"activity_recent_days": 2,
"activity_stale_days": 7,
"visible_columns": {
"target_branch": false,
"labels": false,
"milestone": false
},
"label_colors": {
"deploy::*": {
"bg": "#2E7D32",
"fg": "white"
},
"review::approved": {
"bg": "magenta",
"fg": "white"
},
"review::*": {
"bg": "cyan",
"fg": "black"
},
"size::*": {
"bg": "dark_gray",
"fg": "white"
},
"bug": {
"bg": "#D32F2F",
"fg": "white"
}
}
}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": true, "target_branch": true, "labels": false, "milestone": false, "notes": false }
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 β
Notifications are powered by notify-rust and rely on your system's native notification daemon (e.g., libnotify on Linux, NSUserNotifications on macOS).
Four events trigger a desktop notification:
| Event | Trigger condition |
|---|---|
| πΏ New branch detected | An MR's commit SHA was found on a branch not present in the last persisted state |
| π MR updated | updated_at from GitLab differs from the previously stored value |
| π Mergeability changed | The mergeability status transitioned (e.g. Mergeable β Conflict) |
| π Milestone changed | The milestone attached to the MR changed (e.g. v2.4.0 β v2.5.0) |
Anti-spam on startup: change notifications (updated_at, mergeability, milestone) are suppressed during the initial sync β the first fetch cycle after launch. This prevents a flood of toasts when the app starts and reconciles its in-memory state with the GitLab API. Only genuine changes detected during subsequent background refreshes (or a manual R refresh) will produce notifications.
- β No duplicate alerts when restarting the app with an unchanged state.
- β No spam during the initial sync or redundant refresh cycles.
- β Reliable detection of real changes across refreshes and restarts.
The last-known branch state per MR is persisted in tracker_state.json under the last_known_branches key.
- 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"]).
The simplest way to install gitlab-tracker if you have Rust (1.80+) available:
cargo install gitlab-trackerThis downloads, compiles, and installs the latest published release directly from crates.io into ~/.cargo/bin/. No cloning required.
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.
For development or to test unreleased changes, clone the repository and build manually:
git clone git@github.com:julien-langlois/gitlab-tracker.git
cd gitlab-tracker
# Build optimized release executable (builds all workspace members)
cargo build --release
# Optional: install binary globally to ~/.cargo/bin/
cargo install --path gitlab-tracker
# Build without desktop notifications (headless / CI environments)
cargo install --path gitlab-tracker --no-default-featuresOnce installed via any of the methods above, launch the dashboard from any terminal folder:
gitlab-trackerThe dashboard operates in two keyboard modes, inspired by vim:
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 Dashboard and Inspector panes |
P |
Cycle Inspector view: MR Info β Pipelines β Time Log (redmine) β MR Info |
L |
Log time on the linked Redmine ticket (redmine feature only) |
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 |
Cycle filter (All β Flagged β
β All) |
Del |
Delete selected MR row |
Esc |
Quit dashboard |
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 |
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 |
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.
This project is structured as a Cargo workspace with four crates:
gitlab-tracker/ # Binary crate β TUI orchestrator
βββ src/
βββ main.rs # Event loop orchestrator & async channel setup
βββ app.rs # State machine, InputMode, row navigation & sort logic
βββ config.rs # Label filtering, wildcard matching, HEX color parsing & 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 # Side panel: MR metadata & pipeline history views
gitlab-tracker-core/ # Library crate β shared trait contracts (TrackerProvider, LinkedTicket)
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
βββ client.rs # Async Redmine REST API client
βββ config.rs # RedmineConfig: YAML config load/save & interactive onboarding
βββ detector.rs # Regex-based ticket ID detector (title & description)
βββ keyring.rs # Secure token retrieval via OS Keyring
| Feature flag | Default | Effect |
|---|---|---|
notifications |
β enabled | Desktop notifications via notify-rust |
redmine |
β disabled | Redmine ticket integration |
The Redmine integration is an opt-in feature. It is not compiled or active by default β the application works fully without it.
When enabled, gitlab-tracker detects Redmine ticket IDs in MR titles and descriptions (via configurable regex patterns) and enriches the Context Inspector panel with ticket information and time tracking data.
- Linked ticket display: subject, status, author, and assignee are shown in the Inspector's MR Info view.
- Time Log view (
P): pressPtwice to reach the Time Log view. It displays:- A progress bar comparing time spent vs. the ticket's estimate.
- The full list of time entries (date, user, activity, duration, comment) fetched live from Redmine via
GET /time_entries.json?issue_id={id}. - Navigating between MRs with
β/βwhile on this view automatically refreshes the entries for the newly selected ticket.
- Log time (
L): pressLfrom any view to open a popup and submit a new time entry directly to Redmine. Select the activity category, enter the duration (e.g.1h30,0.5), optionally add a comment, and confirm withEnter.
You must explicitly enable the redmine feature flag when building or installing:
# Build from source with Redmine support
cargo build --release --features redmine
# Install from source with Redmine support
cargo install --path gitlab-tracker --features redmine
# Install from crates.io with Redmine support
cargo install gitlab-tracker --features redmineNote: The
redminefeature pulls ingitlab-tracker-redmineandgitlab-tracker-coreas additional dependencies (HTTP client, YAML config, OS Keyring). Without the flag, neither crate is compiled and there is zero runtime overhead.
On first launch with the redmine feature enabled, the app interactively prompts for your Redmine URL if none is found:
π No Redmine URL found in config or environment.
Leave empty to disable Redmine integration.
Redmine URL: https://redmine.my-company.com
Leaving the prompt empty silently disables the integration β no error, no impact on the rest of the dashboard.
The configuration is persisted as a YAML file:
- Linux:
~/.config/gitlab-tracker/gitlab-tracker/redmine.yaml - macOS:
~/Library/Application Support/gitlab-tracker/gitlab-tracker/redmine.yaml - Windows:
C:\Users\<User>\AppData\Roaming\gitlab-tracker\gitlab-tracker\redmine.yaml
You can also set the URL via environment variable to skip the prompt entirely:
REDMINE_URL=https://redmine.my-company.comThe generated redmine.yaml file looks like this (edit it to customise the ticket detection patterns):
redmine_url: "https://redmine.my-company.com"
ticket_patterns:
- "#(\\d+)"
- "(?i)(?:refs|fixes|closes|resolves)\\s+#(\\d+)"
- "/issues/(\\d+)"| Field | Description |
|---|---|
redmine_url |
Base URL of your Redmine instance (no trailing slash) |
ticket_patterns |
Regex patterns to detect ticket IDs β capture group 1 must match the numeric ID |
The Redmine personal API token follows the same secure lookup chain as the GitLab token:
1. REDMINE_TOKEN environment variable (if set)
2. Native OS Keyring (GNOME Keyring / macOS Keychain / Windows Credential Manager)
3. Interactive CLI prompt β saved to OS Keyring
The token is never stored in plain text on disk.
Distributed under the MIT License. See LICENSE for details.