Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitissues/issues/0000000057/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
_version: 1
id: 57
title: '[cmd/list] allow filtering for ''me'' on assignee/reporter'
state: new
state: resolved
type: feature
labels: []
reporter: t.burkard
assignee: ''
assignee: t.burkard
priority: ''
due_date: ''
relationships: {}
created: 2026-01-14T22:53:37Z
updated: 2026-01-14T22:53:37Z
updated: 2026-01-20T19:52:44Z
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

### Added

- [cmd/list] enabled 'me' as filter for assignee/reporter (#57)

### Removed

- [cargo] removed `.gitissues/` directory from crates package
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ git issue list
git issue list --columns id,assignee,title
git issue list --columns '*'

git issue list --filter priority=P2 title='*driver*' assignee='' description='*hardware*' # Equal operator with support for wildcard and empty
git issue list --filter priority=P2 title='*driver*' reporter=me assignee='' description='*hardware*' # Equal operator with support for wildcard, me and empty
git issue list --filter due_date\>2025-05-31 due_date\<2026-01-01 # Range operator
git issue list --filter state=new,active # Equal operator with OR: All issues with state 'new' OR 'active' are shown
git issue list --filter labels=ui labels=cli # Equal operator with AND: Only issues with both labels 'ui' AND 'cli' are shown
Expand Down
57 changes: 41 additions & 16 deletions src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use chrono::Utc;
use regex::Regex;

use crate::model::{
Config, Filter, Meta, NamedColor, Operator, Priority, Settings, Sorting, cache_path, current_timestamp, dash_if_empty, issue_desc_path,
issue_exports_dir, issues_dir, load_config, load_description, load_meta, load_settings,
Config, Filter, Meta, NamedColor, Operator, Priority, Settings, Sorting, Users, cache_path, current_timestamp, dash_if_empty,
issue_desc_path, issue_exports_dir, issues_dir, load_config, load_description, load_meta, load_settings, load_users, user_handle_me,
};

pub fn run(
Expand All @@ -21,19 +21,21 @@ pub fn run(
no_color: bool,
) -> Result<(), String> {
let config = load_config()?;
let settings = load_settings()?;

let mut issues = get_issues_metadata()?;

sort_issues(&config, &mut issues, sort)?;

filter_issues(&config, &mut issues, filter)?;
filter_issues(&config, &settings, &mut issues, filter)?;

// Print
match columns {
None => {
print_list(&config, &issues, None, print_csv, no_color)?;
print_list(&config, &settings, &issues, None, print_csv, no_color)?;
}
Some(cols) => {
print_list(&config, &issues, Some(cols), print_csv, no_color)?;
print_list(&config, &settings, &issues, Some(cols), print_csv, no_color)?;
}
}

Expand Down Expand Up @@ -258,9 +260,14 @@ fn print_header_separator(settings: &Settings, cols: &[String], column_widths: &
/// - columns: list of columns to print (None means default from config)
/// - print_csv: whether to print as CSV
/// - no_color: whether to disable color output
fn print_list(config: &Config, issues: &Vec<Meta>, columns: Option<Vec<String>>, print_csv: bool, no_color: bool) -> Result<(), String> {
let settings = load_settings()?;

fn print_list(
config: &Config,
settings: &Settings,
issues: &Vec<Meta>,
columns: Option<Vec<String>>,
print_csv: bool,
no_color: bool,
) -> Result<(), String> {
let mut cols = match &columns {
Some(value) => value.clone(),
None => config.list_columns.clone(),
Expand Down Expand Up @@ -291,7 +298,7 @@ fn print_list(config: &Config, issues: &Vec<Meta>, columns: Option<Vec<String>>,
} else {
let width = *column_widths.get(col).unwrap_or(&22);
let styled = if color_enabled {
colorize_header(&settings, col)
colorize_header(settings, col)
} else {
col.to_string()
};
Expand All @@ -303,7 +310,7 @@ fn print_list(config: &Config, issues: &Vec<Meta>, columns: Option<Vec<String>>,
print_ln(print_csv, &mut csv_content);

if !print_csv {
print_header_separator(&settings, &cols, &column_widths);
print_header_separator(settings, &cols, &column_widths);
}

// Print rows
Expand All @@ -316,7 +323,7 @@ fn print_list(config: &Config, issues: &Vec<Meta>, columns: Option<Vec<String>>,
} else {
let width = *column_widths.get(col).unwrap_or(&22);
let colored_value = if color_enabled {
colorize_value(&settings, col, &value)
colorize_value(settings, col, &value)
} else {
value.clone()
};
Expand Down Expand Up @@ -402,7 +409,7 @@ fn calculate_column_widths(issues: &[Meta], columns: &[String]) -> Result<std::c
Ok(widths)
}

fn filter_issues(config: &Config, issues: &mut Vec<Meta>, filters: Option<Vec<Filter>>) -> Result<(), String> {
fn filter_issues(config: &Config, settings: &Settings, issues: &mut Vec<Meta>, filters: Option<Vec<Filter>>) -> Result<(), String> {
if let Some(mut filters) = filters {
// Validate all filter fields
let mut filter_fields: Vec<String> = filters.iter().map(|f| f.field.clone()).collect();
Expand All @@ -415,10 +422,12 @@ fn filter_issues(config: &Config, issues: &mut Vec<Meta>, filters: Option<Vec<Fi

validate_filters(&filters)?;

let users = load_users()?;

// Apply filters
issues.retain(|meta| {
filters.iter().all(|filter| match filter.operator {
Operator::Eq => filter_eq(filter, meta),
Operator::Eq => filter_eq(filter, meta, settings, &users),
Operator::Gt => filter_gt(filter, meta).unwrap_or(false),
Operator::Lt => filter_lt(filter, meta).unwrap_or(false),
})
Expand Down Expand Up @@ -459,15 +468,15 @@ fn validate_filters(filters: &[Filter]) -> Result<(), String> {
Ok(())
}

fn filter_eq(filter: &Filter, meta: &Meta) -> bool {
fn filter_eq(filter: &Filter, meta: &Meta, settings: &Settings, users: &Users) -> bool {
match filter.field.as_str() {
"id" => do_strings_match(&meta.id.to_string(), &filter.value),
"title" => do_strings_match(&meta.title, &filter.value),
"state" => do_strings_match(&meta.state, &filter.value),
"type" => do_strings_match(&meta.type_, &filter.value),
"labels" => is_in_str_list(&meta.labels, &filter.value),
"reporter" => do_strings_match(&meta.reporter, &filter.value),
"assignee" => do_strings_match(&meta.assignee, &filter.value),
"assignee" => do_strings_match_with_me(&meta.assignee, &filter.value, settings, users),
"reporter" => do_strings_match_with_me(&meta.reporter, &filter.value, settings, users),
"priority" => do_strings_match(&format!("{:?}", meta.priority).replace("-", ""), &filter.value),
"due_date" => do_strings_match(&meta.due_date, &filter.value),
"created" => do_strings_match(&meta.created, &filter.value),
Expand Down Expand Up @@ -539,6 +548,22 @@ fn do_strings_match(value: &str, pattern: &str) -> bool {
false
}

fn do_strings_match_with_me(value: &str, pattern: &str, settings: &Settings, users: &Users) -> bool {
let mut pattern_me_replaced = Vec::new();

for word in pattern.split(',') {
let mut word = word.to_string();
match user_handle_me(users, settings, &mut word) {
Ok(_) => { /* continue */ }
Err(_) => return false,
}

pattern_me_replaced.push(word);
}

do_strings_match(value, pattern_me_replaced.join(",").as_str())
}

/// Check if pattern matches any string in the list
fn is_in_str_list(list: &[String], pattern: &str) -> bool {
if pattern.is_empty() && list.is_empty() {
Expand Down