Skip to content

log: use structured logging everywhere #30

log: use structured logging everywhere

log: use structured logging everywhere #30

Triggered via push January 3, 2026 18:43
Status Failure
Total duration 1m 15s
Artifacts

check.yml

on: push
stable / fmt
9s
stable / fmt
nightly / doc
1m 7s
nightly / doc
ubuntu / stable / features
1m 11s
ubuntu / stable / features
Matrix: clippy
Matrix: msrv
Fit to window
Zoom out
Zoom in

Annotations

2 errors and 12 warnings
stable / fmt
Process completed with exit code 1.
ubuntu / 1.74.0
Process completed with exit code 101.
[clippy] src/lib.rs#L6: src/lib.rs#L6
warning: unused import: `info` --> src/lib.rs:6:22 | 6 | use tracing::{debug, info, warn}; | ^^^^ | = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
[clippy] src/tui/mod.rs#L407: src/tui/mod.rs#L407
warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> src/tui/mod.rs:407:13 | 407 | / match event::read()? { 408 | | Event::Key(key) => match app.mode { 409 | | AppMode::Normal => match key.code { 410 | | KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { ... | 614 | | _ => {} 615 | | } | |_____________^ | = note: you might want to preserve the comments from inside the `match` = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#single_match = note: `#[warn(clippy::single_match)]` on by default help: try | 407 ~ if let Event::Key(key) = event::read()? { match app.mode { 408 + AppMode::Normal => match key.code { 409 + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { 410 + return Ok(Action::None); 411 + } 412 + KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { 413 + // Ctrl+D = drop workspace repo(s) to library 414 + if app.active_section == Section::Workspace 415 + && let Some(node) = app.selected_workspace_node() 416 + { 417 + let repo_paths = node.collect_repo_paths(); 418 + if !repo_paths.is_empty() { 419 + return Ok(Action::DropToLibrary(repo_paths)); 420 + } 421 + } 422 + } 423 + KeyCode::Right => { 424 + // Right arrow = expand node 425 + app.toggle_expand(); 426 + } 427 + KeyCode::Left => { 428 + // Left arrow = collapse node 429 + app.toggle_expand(); 430 + } 431 + KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => { 432 + // Ctrl+A = clone repo dialog 433 + app.mode = AppMode::CloneRepo; 434 + app.clone_repo_input.clear(); 435 + 436 + // Fetch suggestions in background (blocking for now) 437 + let mut suggestions = Vec::new(); 438 + suggestions.extend(get_github_suggestions()); 439 + suggestions.extend(get_gitlab_suggestions()); 440 + suggestions.sort(); 441 + suggestions.dedup(); 442 + 443 + // Filter out repos that already exist in workspace or library 444 + let existing_repos: std::collections::HashSet<String> = app 445 + .get_flattened_workspace() 446 + .iter() 447 + .chain(app.get_flattened_library().iter()) 448 + .filter_map(|(node, _, _, _)| { 449 + node.repo_info.as_ref().map(|r| r.display_name.clone()) 450 + }) 451 + .collect(); 452 + 453 + suggestions.retain(|s| { 454 + // Extract the repo name from the suggestion (e.g., "github.com/user/repo" -> "github.com/user/repo") 455 + !existing_repos.contains(s) 456 + }); 457 + 458 + app.clone_repo_suggestions = suggestions; 459 + 460 + if !app.clone_repo_suggestions.is_empty() { 461 + app.clone_repo_state.select(Some(0)); 462 + } 463 + } 464 + KeyCode::Esc => { 465 + // Return None to exit - cleanup happens in the outer loop 466 + return Ok(Action::None); 467 + } 468 + KeyCode::Tab => { 469 + // Tab switches between workspace and library 470 + match app.active_section { 471 + Section::Workspace => { 472 + let library_items = app.get_flattened_library(); 473 + if !library_items.is_empty() { 474 + app.workspace_state.select(None); 475 + app.library_state.select(Some(0)); 476 + app.active_section = Section::Library; 477 + } 478 + } 479 + Section::Library => { 480 + let workspace_items = app.get_flattened_workspace(); 481 + if !workspace_items.is_empty() { 482 + app.library_state.select(None); 483 + app.workspace_state.select(Some(0)); 484 + app.active_section = Section::Workspace; 485 + } 486 + } 487 + } 488 + } 489 + KeyCode::Down => app.next(), 490 + KeyCode::Up => app.previous(), 491 + KeyCode::Enter => { 492 + // Enter on workspace repo = open shell 493 + // Enter on library repo = restore from library 494 + return Ok(match app.active_section { 495 + Section::Workspace => { 496 + if let Some(node) = app.selected_workspace_node() { 497 + if let Some(repo) = node.repo_info { 498 + Action::OpenShell(repo.path.clone()) 499 + } else { 500 + // Just a directory node, toggle expand 501 + app.toggle_expand(); 502 + Action::None 503 + } 504 + } else { 505 + Action::None 506 + } 507 + } 508 + Section::Library => { 509 + if let Some(node) = app.selected_library_node() { 510 + let repo_paths = node.collect_repo_paths(); 511 + if !repo_paths.is_empty() { 512 + Action::RestoreFromLibrary(repo_paths) 513 + } else { 514 + // Just a directory node, toggle expand 515 + app.toggle_expand(); 516 + Action::None 517 + } 518 + } else { 519 + Action::None 520 + } 521 + } 522 + }); 523 + } 524 + KeyCode::Char(c) => { 525 + app.search_query.push(c); 526 + app.filter_repos(); 527 + } 528 + KeyCode::Backspace => { 529 + app.search_query.pop(); 530 + app.filter_repos(); 531 + } 532 + _ => {} 533 + }, 534 + AppMode::CloneRepo => match key.code { 535 + KeyCode::Esc => { 536 + app.mode = AppMode::Normal; 537 + app.clone_repo_input.clear(); 538 + } 539 + KeyCode::Enter => { 540 + // Use selected suggestion or manual input 541 + let repo = if let Some(idx) = app.clone_repo_state.selected() { 542 + // Filter suggestions by current input 543 + let filtered: Vec<_> = app 544 + .clone_repo_suggestions 545 + .iter() 546 + .filter(|s| { 547 + s.to_lowercase() 548 + .contains(&app.clone_repo_input.to_lowercase()) 549 + }) 550 + .collect(); 551 + 552 + filtered.get(idx).map(|s| (*s).to_string()) 553 + } else { 554 + None 555 + }; 556 + 557 + let repo = repo.unwrap_or_else(|| app.clone_repo_input.clone()); 558 + 559 + if !repo.is_empty() { 560 + app.mode = AppMode::Normal; 561 + return Ok(Action::CloneRepo(repo)); 562 + } 563 + } 564 + KeyCode::Down => { 565 + let filtered: Vec<_> = app 566 + .clone_repo_suggestions 567 + .iter() 568 + .filter(|s| { 569 + s.to_lowercase() 570 + .contains(&app.clone_repo_input.to_lowercase()) 571 + }) 572 + .collect(); 573 + 574 + if !filtered.is_empty() { 575 + let next = match app.clone_repo_state.selected() { 576 + Some(i) if i >= filtered.len() - 1 => 0, 577 + Some(i) => i + 1, 578 + None => 0, 579 + }; 580 + app.clone_repo_state.select(Some(next)); 581 + } 582 + } 583 + KeyCode::Up => { 584 + let filtered: Vec<_> = app 585 + .clone_repo_suggestions 586 + .iter() 587 + .filter(|s| { 588 + s.to_lowercase() 589 + .contains(&app.clone_repo_input.to_lowercase()) 590 + }) 591 + .collect(); 592 + 593 + if !filtered.is_empty() { 594 + let prev = match app.clone_repo_state.selected() { 595 + Some(0) => filtered.len() - 1, 596 + Some(i) => i - 1, 597 + None => 0, 598 + }; 599 + app.clone_repo_state.select(Some(prev)); 600 + } 601 + } 602 + KeyCode::Char(c) => { 603 + app.clone_repo_input.push(c); 604 + app.clone_repo_state.select(Some(0)); 605 + } 606 + KeyCode::Backspace => { 607 + app.clone_repo_input.pop(); 608 + } 609 + _ => {} 610 + }, 611 + } } |
[clippy] src/tui/mod.rs#L1325: src/tui/mod.rs#L1325
warning: this `if` statement can be collapsed --> src/tui/mod.rs:1325:5 | 1325 | / if let Ok(output) = std::process::Command::new("gh") 1326 | | .args(["auth", "status", "--active", "--json", "hosts"]) 1327 | | .output() 1328 | | && output.status.success() ... | 1337 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#collapsible_if = note: `#[warn(clippy::collapsible_if)]` on by default help: collapse nested if block | 1328 ~ && output.status.success() 1329 ~ && let Ok(json) = serde_json::from_slice::<serde_json::Value>(&output.stdout) { 1330 | if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { ... 1334 | } 1335 ~ } |
[clippy] src/tui/mod.rs#L1330: src/tui/mod.rs#L1330
warning: this `if` statement can be collapsed --> src/tui/mod.rs:1330:9 | 1330 | / if let Ok(json) = serde_json::from_slice::<serde_json::Value>(&output.stdout) { 1331 | | if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { 1332 | | if let Some(hostname) = hosts.keys().next() { 1333 | | return hostname.clone(); ... | 1336 | | } | |_________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#collapsible_if help: collapse nested if block | 1330 ~ if let Ok(json) = serde_json::from_slice::<serde_json::Value>(&output.stdout) 1331 ~ && let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { 1332 | if let Some(hostname) = hosts.keys().next() { 1333 | return hostname.clone(); 1334 | } 1335 ~ } |
[clippy] src/tui/mod.rs#L1331: src/tui/mod.rs#L1331
warning: this `if` statement can be collapsed --> src/tui/mod.rs:1331:13 | 1331 | / if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { 1332 | | if let Some(hostname) = hosts.keys().next() { 1333 | | return hostname.clone(); 1334 | | } 1335 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#collapsible_if help: collapse nested if block | 1331 ~ if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) 1332 ~ && let Some(hostname) = hosts.keys().next() { 1333 | return hostname.clone(); 1334 ~ } |
[clippy] src/lib.rs#L871: src/lib.rs#L871
warning: this `if` statement can be collapsed --> src/lib.rs:871:13 | 871 | / if let Ok(remote) = source_repo.find_remote(remote_name.as_str()) { 872 | | if let Some(url) = remote.url(gix::remote::Direction::Fetch) { 873 | | let remote_url = url.to_bstring().to_string(); 874 | | debug!(remote = %remote_name, url = %remote_url, "Restoring remote"); ... | 895 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.92.0/index.html#collapsible_if help: collapse nested if block | 871 ~ if let Ok(remote) = source_repo.find_remote(remote_name.as_str()) 872 ~ && let Some(url) = remote.url(gix::remote::Direction::Fetch) { 873 | let remote_url = url.to_bstring().to_string(); ... 893 | } 894 ~ } |
[clippy] src/lib.rs#L6: src/lib.rs#L6
warning: unused import: `info` --> src/lib.rs:6:22 | 6 | use tracing::{debug, info, warn}; | ^^^^ | = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
[clippy] src/tui/mod.rs#L407: src/tui/mod.rs#L407
warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> src/tui/mod.rs:407:13 | 407 | / match event::read()? { 408 | | Event::Key(key) => match app.mode { 409 | | AppMode::Normal => match key.code { 410 | | KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { ... | 614 | | _ => {} 615 | | } | |_____________^ | = note: you might want to preserve the comments from inside the `match` = help: for further information visit https://rust-lang.github.io/rust-clippy/beta/index.html#single_match = note: `#[warn(clippy::single_match)]` on by default help: try | 407 ~ if let Event::Key(key) = event::read()? { match app.mode { 408 + AppMode::Normal => match key.code { 409 + KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { 410 + return Ok(Action::None); 411 + } 412 + KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => { 413 + // Ctrl+D = drop workspace repo(s) to library 414 + if app.active_section == Section::Workspace 415 + && let Some(node) = app.selected_workspace_node() 416 + { 417 + let repo_paths = node.collect_repo_paths(); 418 + if !repo_paths.is_empty() { 419 + return Ok(Action::DropToLibrary(repo_paths)); 420 + } 421 + } 422 + } 423 + KeyCode::Right => { 424 + // Right arrow = expand node 425 + app.toggle_expand(); 426 + } 427 + KeyCode::Left => { 428 + // Left arrow = collapse node 429 + app.toggle_expand(); 430 + } 431 + KeyCode::Char('a') if key.modifiers.contains(KeyModifiers::CONTROL) => { 432 + // Ctrl+A = clone repo dialog 433 + app.mode = AppMode::CloneRepo; 434 + app.clone_repo_input.clear(); 435 + 436 + // Fetch suggestions in background (blocking for now) 437 + let mut suggestions = Vec::new(); 438 + suggestions.extend(get_github_suggestions()); 439 + suggestions.extend(get_gitlab_suggestions()); 440 + suggestions.sort(); 441 + suggestions.dedup(); 442 + 443 + // Filter out repos that already exist in workspace or library 444 + let existing_repos: std::collections::HashSet<String> = app 445 + .get_flattened_workspace() 446 + .iter() 447 + .chain(app.get_flattened_library().iter()) 448 + .filter_map(|(node, _, _, _)| { 449 + node.repo_info.as_ref().map(|r| r.display_name.clone()) 450 + }) 451 + .collect(); 452 + 453 + suggestions.retain(|s| { 454 + // Extract the repo name from the suggestion (e.g., "github.com/user/repo" -> "github.com/user/repo") 455 + !existing_repos.contains(s) 456 + }); 457 + 458 + app.clone_repo_suggestions = suggestions; 459 + 460 + if !app.clone_repo_suggestions.is_empty() { 461 + app.clone_repo_state.select(Some(0)); 462 + } 463 + } 464 + KeyCode::Esc => { 465 + // Return None to exit - cleanup happens in the outer loop 466 + return Ok(Action::None); 467 + } 468 + KeyCode::Tab => { 469 + // Tab switches between workspace and library 470 + match app.active_section { 471 + Section::Workspace => { 472 + let library_items = app.get_flattened_library(); 473 + if !library_items.is_empty() { 474 + app.workspace_state.select(None); 475 + app.library_state.select(Some(0)); 476 + app.active_section = Section::Library; 477 + } 478 + } 479 + Section::Library => { 480 + let workspace_items = app.get_flattened_workspace(); 481 + if !workspace_items.is_empty() { 482 + app.library_state.select(None); 483 + app.workspace_state.select(Some(0)); 484 + app.active_section = Section::Workspace; 485 + } 486 + } 487 + } 488 + } 489 + KeyCode::Down => app.next(), 490 + KeyCode::Up => app.previous(), 491 + KeyCode::Enter => { 492 + // Enter on workspace repo = open shell 493 + // Enter on library repo = restore from library 494 + return Ok(match app.active_section { 495 + Section::Workspace => { 496 + if let Some(node) = app.selected_workspace_node() { 497 + if let Some(repo) = node.repo_info { 498 + Action::OpenShell(repo.path.clone()) 499 + } else { 500 + // Just a directory node, toggle expand 501 + app.toggle_expand(); 502 + Action::None 503 + } 504 + } else { 505 + Action::None 506 + } 507 + } 508 + Section::Library => { 509 + if let Some(node) = app.selected_library_node() { 510 + let repo_paths = node.collect_repo_paths(); 511 + if !repo_paths.is_empty() { 512 + Action::RestoreFromLibrary(repo_paths) 513 + } else { 514 + // Just a directory node, toggle expand 515 + app.toggle_expand(); 516 + Action::None 517 + } 518 + } else { 519 + Action::None 520 + } 521 + } 522 + }); 523 + } 524 + KeyCode::Char(c) => { 525 + app.search_query.push(c); 526 + app.filter_repos(); 527 + } 528 + KeyCode::Backspace => { 529 + app.search_query.pop(); 530 + app.filter_repos(); 531 + } 532 + _ => {} 533 + }, 534 + AppMode::CloneRepo => match key.code { 535 + KeyCode::Esc => { 536 + app.mode = AppMode::Normal; 537 + app.clone_repo_input.clear(); 538 + } 539 + KeyCode::Enter => { 540 + // Use selected suggestion or manual input 541 + let repo = if let Some(idx) = app.clone_repo_state.selected() { 542 + // Filter suggestions by current input 543 + let filtered: Vec<_> = app 544 + .clone_repo_suggestions 545 + .iter() 546 + .filter(|s| { 547 + s.to_lowercase() 548 + .contains(&app.clone_repo_input.to_lowercase()) 549 + }) 550 + .collect(); 551 + 552 + filtered.get(idx).map(|s| (*s).to_string()) 553 + } else { 554 + None 555 + }; 556 + 557 + let repo = repo.unwrap_or_else(|| app.clone_repo_input.clone()); 558 + 559 + if !repo.is_empty() { 560 + app.mode = AppMode::Normal; 561 + return Ok(Action::CloneRepo(repo)); 562 + } 563 + } 564 + KeyCode::Down => { 565 + let filtered: Vec<_> = app 566 + .clone_repo_suggestions 567 + .iter() 568 + .filter(|s| { 569 + s.to_lowercase() 570 + .contains(&app.clone_repo_input.to_lowercase()) 571 + }) 572 + .collect(); 573 + 574 + if !filtered.is_empty() { 575 + let next = match app.clone_repo_state.selected() { 576 + Some(i) if i >= filtered.len() - 1 => 0, 577 + Some(i) => i + 1, 578 + None => 0, 579 + }; 580 + app.clone_repo_state.select(Some(next)); 581 + } 582 + } 583 + KeyCode::Up => { 584 + let filtered: Vec<_> = app 585 + .clone_repo_suggestions 586 + .iter() 587 + .filter(|s| { 588 + s.to_lowercase() 589 + .contains(&app.clone_repo_input.to_lowercase()) 590 + }) 591 + .collect(); 592 + 593 + if !filtered.is_empty() { 594 + let prev = match app.clone_repo_state.selected() { 595 + Some(0) => filtered.len() - 1, 596 + Some(i) => i - 1, 597 + None => 0, 598 + }; 599 + app.clone_repo_state.select(Some(prev)); 600 + } 601 + } 602 + KeyCode::Char(c) => { 603 + app.clone_repo_input.push(c); 604 + app.clone_repo_state.select(Some(0)); 605 + } 606 + KeyCode::Backspace => { 607 + app.clone_repo_input.pop(); 608 + } 609 + _ => {} 610 + }, 611 + } } |
[clippy] src/tui/mod.rs#L1325: src/tui/mod.rs#L1325
warning: this `if` statement can be collapsed --> src/tui/mod.rs:1325:5 | 1325 | / if let Ok(output) = std::process::Command::new("gh") 1326 | | .args(["auth", "status", "--active", "--json", "hosts"]) 1327 | | .output() 1328 | | && output.status.success() ... | 1337 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/beta/index.html#collapsible_if = note: `#[warn(clippy::collapsible_if)]` on by default help: collapse nested if block | 1328 ~ && output.status.success() 1329 ~ && let Ok(json) = serde_json::from_slice::<serde_json::Value>(&output.stdout) { 1330 | if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { ... 1334 | } 1335 ~ } |
[clippy] src/tui/mod.rs#L1330: src/tui/mod.rs#L1330
warning: this `if` statement can be collapsed --> src/tui/mod.rs:1330:9 | 1330 | / if let Ok(json) = serde_json::from_slice::<serde_json::Value>(&output.stdout) { 1331 | | if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { 1332 | | if let Some(hostname) = hosts.keys().next() { 1333 | | return hostname.clone(); ... | 1336 | | } | |_________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/beta/index.html#collapsible_if help: collapse nested if block | 1330 ~ if let Ok(json) = serde_json::from_slice::<serde_json::Value>(&output.stdout) 1331 ~ && let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { 1332 | if let Some(hostname) = hosts.keys().next() { 1333 | return hostname.clone(); 1334 | } 1335 ~ } |
[clippy] src/tui/mod.rs#L1331: src/tui/mod.rs#L1331
warning: this `if` statement can be collapsed --> src/tui/mod.rs:1331:13 | 1331 | / if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) { 1332 | | if let Some(hostname) = hosts.keys().next() { 1333 | | return hostname.clone(); 1334 | | } 1335 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/beta/index.html#collapsible_if help: collapse nested if block | 1331 ~ if let Some(hosts) = json.get("hosts").and_then(|h| h.as_object()) 1332 ~ && let Some(hostname) = hosts.keys().next() { 1333 | return hostname.clone(); 1334 ~ } |
[clippy] src/lib.rs#L871: src/lib.rs#L871
warning: this `if` statement can be collapsed --> src/lib.rs:871:13 | 871 | / if let Ok(remote) = source_repo.find_remote(remote_name.as_str()) { 872 | | if let Some(url) = remote.url(gix::remote::Direction::Fetch) { 873 | | let remote_url = url.to_bstring().to_string(); 874 | | debug!(remote = %remote_name, url = %remote_url, "Restoring remote"); ... | 895 | | } | |_____________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/beta/index.html#collapsible_if help: collapse nested if block | 871 ~ if let Ok(remote) = source_repo.find_remote(remote_name.as_str()) 872 ~ && let Some(url) = remote.url(gix::remote::Direction::Fetch) { 873 | let remote_url = url.to_bstring().to_string(); ... 893 | } 894 ~ } |