2.6 KiB · text History d859987
//! Who changed what: a repository's or an organization's audit log.
//!
//! The same entries the web's audit pages show, minus the client
//! addresses (those are the instance admins', on the web only). Reading
//! one takes a token with `audit:read`, which no bundle includes — tick
//! it by hand on the tokens page.

use anyhow::Result;

use crate::cli::AuditArgs;
use crate::commands::Ctx;
use crate::output::{Table, ago, note};

pub async fn run(ctx: &Ctx, args: &AuditArgs) -> Result<()> {
    let mut query: Vec<(&str, String)> = vec![("limit", args.limit.to_string())];
    if let Some(category) = &args.category {
        query.push(("category", category.clone()));
    }
    if let Some(actor) = &args.actor {
        query.push(("actor", actor.trim_start_matches('@').to_string()));
    }
    if let Some(before) = args.before {
        query.push(("before", before.to_string()));
    }

    let (res, whose) = match &args.org {
        Some(org) => {
            let org = org.trim_start_matches('@');
            if let Some(name) = &args.in_repo {
                query.push(("repo", name.clone()));
            }
            (ctx.api.org_audit(org, &query).await?, format!("@{org}"))
        }
        None => {
            let repo = ctx.repo()?;
            (ctx.api.repo_audit(&repo, &query).await?, repo.to_string())
        }
    };

    if ctx.emitted(&res.raw)? {
        return Ok(());
    }
    if res.entries.is_empty() {
        note(&format!("Nothing in {whose}'s audit log matches."));
        return Ok(());
    }

    // An org's log spans its repositories, so say which; a repository's
    // doesn't need the column.
    let mut table = if args.org.is_some() {
        Table::new(["ID", "WHEN", "WHO", "WHAT", "WHERE"])
    } else {
        Table::new(["ID", "WHEN", "WHO", "WHAT"])
    };

    for entry in &res.entries {
        let what = match &entry.target.label {
            Some(label) => format!("{} {label}", entry.summary),
            None => entry.summary.clone(),
        };
        let mut row = vec![
            entry.id.to_string(),
            ago(entry.inserted_at.as_deref()),
            entry.actor.label.clone(),
            what,
        ];
        if args.org.is_some() {
            row.push(
                entry
                    .repository
                    .clone()
                    .or_else(|| entry.organization.clone())
                    .unwrap_or_default(),
            );
        }
        table.row(row);
    }
    table.print();

    if let Some(next) = res.next_before {
        note(&format!("More: the same command with --before {next}"));
    }
    Ok(())
}