gitlab-branch-triage 1.0.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 +7 -0
- data/CHANGELOG.md +38 -0
- data/LICENSE +21 -0
- data/README.md +269 -0
- data/bin/gitlab-branch-triage +488 -0
- data/gitlab-branch-triage.gemspec +38 -0
- data/lib/gitlab/branch_triage/actions/base.rb +44 -0
- data/lib/gitlab/branch_triage/actions/comment.rb +5 -0
- data/lib/gitlab/branch_triage/actions/delete.rb +32 -0
- data/lib/gitlab/branch_triage/actions/executor.rb +44 -0
- data/lib/gitlab/branch_triage/actions/mr_actions.rb +187 -0
- data/lib/gitlab/branch_triage/actions/notify.rb +145 -0
- data/lib/gitlab/branch_triage/actions/print.rb +40 -0
- data/lib/gitlab/branch_triage/client.rb +177 -0
- data/lib/gitlab/branch_triage/conditions/author_condition.rb +40 -0
- data/lib/gitlab/branch_triage/conditions/base.rb +21 -0
- data/lib/gitlab/branch_triage/conditions/date_condition.rb +34 -0
- data/lib/gitlab/branch_triage/conditions/evaluator.rb +52 -0
- data/lib/gitlab/branch_triage/conditions/inactive_days.rb +14 -0
- data/lib/gitlab/branch_triage/conditions/mr_conditions.rb +141 -0
- data/lib/gitlab/branch_triage/conditions/name_condition.rb +35 -0
- data/lib/gitlab/branch_triage/conditions/state_condition.rb +29 -0
- data/lib/gitlab/branch_triage/group_resolver.rb +47 -0
- data/lib/gitlab/branch_triage/policy_loader.rb +35 -0
- data/lib/gitlab/branch_triage/resource/branch.rb +89 -0
- data/lib/gitlab/branch_triage/resource/merge_request.rb +150 -0
- data/lib/gitlab/branch_triage/runner.rb +247 -0
- data/lib/gitlab/branch_triage/user_resolver.rb +103 -0
- data/lib/gitlab/branch_triage/version.rb +7 -0
- data/lib/gitlab-branch-triage.rb +60 -0
- data/logo.svg +87 -0
- metadata +111 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6f0c6207b42ee32ebdee0043b2bc42fa281174887aa4e0e7983d8ec730212635
|
|
4
|
+
data.tar.gz: 740c0de7ba602b533247202bdb9f8e2c4ef34fc29eac5ccbb130c87b6ccc19ed
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9b9e817f6c1b37d237ee74b08b23a0ac684de7cdbc20cffc0704e7699b465cf2f636ed19014f6583a3f76e1c98378dd4d93d3585ecc6a251d2c4d7ec9735dfbe
|
|
7
|
+
data.tar.gz: fd595edf5634bab0e9ea7b0030869d2d79630c6cd7262cfe1472240158b7f9511b07f20ae8da7045e3a3c9697150fb8873e7f1f9747ea725819429b9d73fd5d3
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-03-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- initial commit and push beta version v1.0.0 (bfba928)
|
|
13
|
+
|
|
14
|
+
### Other
|
|
15
|
+
|
|
16
|
+
- Initial commit (123cd8b)
|
|
17
|
+
|
|
18
|
+
## [1.0.0] - 2026-03-11
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- YAML-driven policy engine for branch and merge request triage
|
|
23
|
+
- Branch conditions: `inactive_days`, `merged`, `protected`, `has_open_mr`, `name`, `forbidden_name`, `author`, `forbidden_author`, `date`
|
|
24
|
+
- Branch actions: `notify`, `delete`, `print`, `comment`
|
|
25
|
+
- MR conditions: `date`, `draft`, `assigned`, `has_reviewer`, `pipeline_status`, `labels`, `forbidden_labels`, `target_branch`, `title`
|
|
26
|
+
- MR actions: `comment_mr`, `close_mr`, `label_mr`, `notify_mr`, `print`
|
|
27
|
+
- Inactive author detection with automatic branch deletion and owner notification
|
|
28
|
+
- Group-wide triage with recursive subgroup support
|
|
29
|
+
- Dry-run mode enabled by default
|
|
30
|
+
- `--init` helper to generate example policy file
|
|
31
|
+
- `--init-ci` helper to generate GitLab CI snippet
|
|
32
|
+
- Template variable system for notification messages
|
|
33
|
+
- Rate-limit handling with configurable max retries
|
|
34
|
+
- CLI with comprehensive options for token, source, host, policies, and filters
|
|
35
|
+
|
|
36
|
+
[1.0.0]: https://github.com/solucteam/gitlab-branch-triage/releases/tag/v1.0.0
|
|
37
|
+
|
|
38
|
+
[1.0.0]: https://github.com/solucteam/gitlab-branch-triage/releases/tag/v1.0.0
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SolucTeam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" alt="gitlab-branch-triage" width="560"/>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://rubygems.org/gems/gitlab-branch-triage"><img src="https://img.shields.io/gem/v/gitlab-branch-triage?color=E24329&style=for-the-badge&logo=rubygems&logoColor=white" alt="Gem Version"/></a>
|
|
7
|
+
<a href="https://rubygems.org/gems/gitlab-branch-triage"><img src="https://img.shields.io/gem/dt/gitlab-branch-triage?color=6B4FBB&style=for-the-badge&logo=rubygems&logoColor=white" alt="Downloads"/></a>
|
|
8
|
+
<a href="https://github.com/solucteam/gitlab-branch-triage/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/solucteam/gitlab-branch-triage/ci.yml?style=for-the-badge&logo=github&label=CI" alt="CI"/></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/ruby-%3E%3D%203.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white" alt="Ruby >= 3.0"/>
|
|
10
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/solucteam/gitlab-branch-triage?color=FCA326&style=for-the-badge" alt="License"/></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
**gitlab-branch-triage** automates branch and merge request cleanup on GitLab using YAML-driven policies. Notify stale branch authors, auto-delete merged branches, close abandoned MRs, detect inactive authors, and keep your repositories clean — all from a single configuration file.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Policy-driven** — define triage rules in a simple YAML file
|
|
20
|
+
- **Branch triage** — detect stale, merged, or abandoned branches and act on them
|
|
21
|
+
- **MR triage** — warn about abandoned MRs, close stale ones, flag failing pipelines
|
|
22
|
+
- **Inactive author detection** — automatically handles branches from blocked/deleted users
|
|
23
|
+
- **Group-wide** — triage all projects in a GitLab group (subgroups included recursively)
|
|
24
|
+
- **Dry-run by default** — safe to test before executing real actions
|
|
25
|
+
- **GitLab CI ready** — ship as a scheduled pipeline job
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
gem install gitlab-branch-triage
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or add to your `Gemfile`:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
gem "gitlab-branch-triage"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
**1. Generate an example policy file:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
gitlab-branch-triage --init
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This creates `.branch-triage-policies.yml` with sensible defaults.
|
|
48
|
+
|
|
49
|
+
**2. Run a dry-run against a project:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export GITLAB_TOKEN="glpat-xxxxx" # needs api scope
|
|
53
|
+
|
|
54
|
+
# Single project
|
|
55
|
+
gitlab-branch-triage --source-id my-group/my-project
|
|
56
|
+
|
|
57
|
+
# Entire group (all subgroups included)
|
|
58
|
+
gitlab-branch-triage --source groups --source-id my-group
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**3. Execute real actions:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
gitlab-branch-triage --source-id my-group/my-project --no-dry-run
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Policies are defined in `.branch-triage-policies.yml`. The file has two main sections: `branches` rules and `merge_requests` rules.
|
|
70
|
+
|
|
71
|
+
### Branch Rules
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
resource_rules:
|
|
75
|
+
branches:
|
|
76
|
+
rules:
|
|
77
|
+
- name: Notify stale branches (60+ days)
|
|
78
|
+
conditions:
|
|
79
|
+
inactive_days: 60
|
|
80
|
+
merged: false
|
|
81
|
+
protected: false
|
|
82
|
+
has_open_mr: false
|
|
83
|
+
forbidden_name:
|
|
84
|
+
matches: "^(main|master|develop)$"
|
|
85
|
+
limits:
|
|
86
|
+
most_recent: 50
|
|
87
|
+
actions:
|
|
88
|
+
notify:
|
|
89
|
+
title: "Stale branch: `{{name}}` in {{project_path}}"
|
|
90
|
+
body: |
|
|
91
|
+
@{{author_username}}, branch `{{name}}` has been inactive
|
|
92
|
+
for **{{days_inactive}} days**. It will be deleted on **{{delete_date}}**.
|
|
93
|
+
labels:
|
|
94
|
+
- branch-cleanup
|
|
95
|
+
|
|
96
|
+
- name: Delete abandoned branches (90+ days)
|
|
97
|
+
conditions:
|
|
98
|
+
inactive_days: 90
|
|
99
|
+
merged: false
|
|
100
|
+
protected: false
|
|
101
|
+
actions:
|
|
102
|
+
delete: true
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Branch Conditions
|
|
106
|
+
|
|
107
|
+
| Condition | Example | Description |
|
|
108
|
+
|-----------|---------|-------------|
|
|
109
|
+
| `inactive_days` | `60` | Days since last commit |
|
|
110
|
+
| `merged` | `true` / `false` | Branch merge status |
|
|
111
|
+
| `protected` | `true` / `false` | Branch protection status |
|
|
112
|
+
| `has_open_mr` | `true` / `false` | Has an open merge request |
|
|
113
|
+
| `name` | `{matches: "^feature/.*"}` | Branch name pattern (`matches`, `contains`, `starts_with`, `ends_with`) |
|
|
114
|
+
| `forbidden_name` | `{matches: "^main$"}` | Exclude branches by name |
|
|
115
|
+
| `author` | `{email_domain: "company.com"}` | Filter by author (`email_domain`, `name_matches`, `username`) |
|
|
116
|
+
| `forbidden_author` | `{email_domain: "bot.com"}` | Exclude by author |
|
|
117
|
+
| `date` | see below | Flexible date matching |
|
|
118
|
+
|
|
119
|
+
**Date condition:**
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
date:
|
|
123
|
+
attribute: committed_date
|
|
124
|
+
condition: older_than # or more_recent_than
|
|
125
|
+
interval_type: days # days | weeks | months | years
|
|
126
|
+
interval: 60
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Branch Actions
|
|
130
|
+
|
|
131
|
+
| Action | Config | Description |
|
|
132
|
+
|--------|--------|-------------|
|
|
133
|
+
| `notify` | `{title, body, labels}` | Create an issue to notify the author |
|
|
134
|
+
| `delete` | `true` | Delete the branch |
|
|
135
|
+
| `print` | `"template string"` | Log a message |
|
|
136
|
+
| `comment` | `{issue_iid, body}` | Comment on an existing issue |
|
|
137
|
+
|
|
138
|
+
### Merge Request Rules
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
resource_rules:
|
|
142
|
+
merge_requests:
|
|
143
|
+
rules:
|
|
144
|
+
- name: Warn abandoned MRs (30+ days)
|
|
145
|
+
conditions:
|
|
146
|
+
date:
|
|
147
|
+
attribute: updated_at
|
|
148
|
+
condition: older_than
|
|
149
|
+
interval_type: days
|
|
150
|
+
interval: 30
|
|
151
|
+
forbidden_labels:
|
|
152
|
+
- do-not-close
|
|
153
|
+
actions:
|
|
154
|
+
label_mr:
|
|
155
|
+
- stale
|
|
156
|
+
comment_mr: |
|
|
157
|
+
@{{author_username}}, this MR has had no activity for **{{days_since_update}} days**.
|
|
158
|
+
|
|
159
|
+
- name: Close abandoned MRs (60+ days)
|
|
160
|
+
conditions:
|
|
161
|
+
date:
|
|
162
|
+
attribute: updated_at
|
|
163
|
+
condition: older_than
|
|
164
|
+
interval_type: days
|
|
165
|
+
interval: 60
|
|
166
|
+
actions:
|
|
167
|
+
close_mr: true
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### MR Conditions
|
|
171
|
+
|
|
172
|
+
| Condition | Example | Description |
|
|
173
|
+
|-----------|---------|-------------|
|
|
174
|
+
| `date` | `{attribute: updated_at, ...}` | Filter by `updated_at` or `created_at` |
|
|
175
|
+
| `draft` | `true` / `false` | Draft/WIP status |
|
|
176
|
+
| `assigned` | `true` / `false` | Has assignees |
|
|
177
|
+
| `has_reviewer` | `true` / `false` | Has reviewers |
|
|
178
|
+
| `pipeline_status` | `"failed"` | Pipeline state (`success`, `failed`, `running`, `pending`) |
|
|
179
|
+
| `labels` | `["label1"]` | All labels must match (AND) |
|
|
180
|
+
| `forbidden_labels` | `["on-hold"]` | None must match |
|
|
181
|
+
| `target_branch` | `"main"` or `{matches: "regex"}` | Target branch filter |
|
|
182
|
+
| `title` | `{contains: "hotfix"}` | Title pattern matching |
|
|
183
|
+
|
|
184
|
+
### MR Actions
|
|
185
|
+
|
|
186
|
+
| Action | Config | Description |
|
|
187
|
+
|--------|--------|-------------|
|
|
188
|
+
| `comment_mr` | `"template"` | Post a comment on the MR |
|
|
189
|
+
| `close_mr` | `true` | Close the MR |
|
|
190
|
+
| `label_mr` | `["stale"]` | Add labels |
|
|
191
|
+
| `notify_mr` | `{title, body, labels}` | Create a notification issue |
|
|
192
|
+
| `print` | `"template"` | Log a message |
|
|
193
|
+
|
|
194
|
+
### Template Variables
|
|
195
|
+
|
|
196
|
+
**Branches:** `{{name}}`, `{{author_name}}`, `{{author_email}}`, `{{author_username}}`, `{{committed_date}}`, `{{days_inactive}}`, `{{delete_date}}`, `{{commit_title}}`, `{{short_sha}}`, `{{project_path}}`, `{{today}}`
|
|
197
|
+
|
|
198
|
+
**Merge Requests:** `{{iid}}`, `{{title}}`, `{{web_url}}`, `{{source_branch}}`, `{{target_branch}}`, `{{author_username}}`, `{{author_name}}`, `{{labels}}`, `{{state}}`, `{{draft}}`, `{{days_since_update}}`, `{{days_since_creation}}`, `{{updated_at}}`, `{{pipeline_status}}`, `{{close_date}}`, `{{project_path}}`, `{{today}}`
|
|
199
|
+
|
|
200
|
+
## CLI Options
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
Usage: gitlab-branch-triage [options]
|
|
204
|
+
|
|
205
|
+
Connection:
|
|
206
|
+
-t, --token TOKEN GitLab API token (or GITLAB_TOKEN env var)
|
|
207
|
+
-H, --host-url URL GitLab host (default: https://gitlab.com)
|
|
208
|
+
|
|
209
|
+
Source:
|
|
210
|
+
-s, --source TYPE 'projects' (default) or 'groups'
|
|
211
|
+
-i, --source-id ID Project or group path/ID
|
|
212
|
+
|
|
213
|
+
Group filters:
|
|
214
|
+
--[no-]exclude-archived Exclude archived projects (default: true)
|
|
215
|
+
--[no-]exclude-forks Exclude forked projects (default: true)
|
|
216
|
+
|
|
217
|
+
Policies:
|
|
218
|
+
-f, --policies-file FILE YAML file (default: .branch-triage-policies.yml)
|
|
219
|
+
|
|
220
|
+
Behaviour:
|
|
221
|
+
-n, --dry-run Don't perform real actions (default: on)
|
|
222
|
+
--no-dry-run Execute real actions
|
|
223
|
+
-d, --debug Print extra debug information
|
|
224
|
+
|
|
225
|
+
Helpers:
|
|
226
|
+
--init Create example policy file
|
|
227
|
+
--init-ci Print example .gitlab-ci.yml snippet
|
|
228
|
+
-v, --version Print version
|
|
229
|
+
-h, --help Print help
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## GitLab CI Integration
|
|
233
|
+
|
|
234
|
+
Run as a scheduled pipeline job:
|
|
235
|
+
|
|
236
|
+
```yaml
|
|
237
|
+
branch-triage:
|
|
238
|
+
stage: triage
|
|
239
|
+
image: ruby:3.2-slim
|
|
240
|
+
before_script:
|
|
241
|
+
- gem install gitlab-branch-triage --no-document
|
|
242
|
+
script:
|
|
243
|
+
- gitlab-branch-triage --token $GITLAB_TOKEN --source-id $CI_PROJECT_PATH --no-dry-run
|
|
244
|
+
rules:
|
|
245
|
+
- if: $CI_PIPELINE_SOURCE == "schedule"
|
|
246
|
+
- if: $CI_PIPELINE_SOURCE == "web"
|
|
247
|
+
when: manual
|
|
248
|
+
allow_failure: true
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Generate the snippet with `gitlab-branch-triage --init-ci`.
|
|
252
|
+
|
|
253
|
+
## Inactive Author Detection
|
|
254
|
+
|
|
255
|
+
When the `notify` action detects that a branch author is inactive (blocked, deactivated, or deleted from GitLab), it automatically deletes the branch and creates a cleanup issue assigned to a project maintainer. This prevents stale notifications to users who can no longer act on them.
|
|
256
|
+
|
|
257
|
+
## Contributing
|
|
258
|
+
|
|
259
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/solucteam/gitlab-branch-triage).
|
|
260
|
+
|
|
261
|
+
1. Fork it
|
|
262
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
263
|
+
3. Commit your changes (`git commit -am 'Add my feature'`)
|
|
264
|
+
4. Push to the branch (`git push origin feature/my-feature`)
|
|
265
|
+
5. Open a Pull Request
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
Released under the [MIT License](LICENSE). Copyright (c) 2026 SolucTeam.
|