use crate::config::Config;
use crate::db::{self, DbPool};
use crate::import::{self, ImportSummary};
use crate::import::github::{self, StateFilter};
use crate::import::jira::{self, JiraStatusMap};
use crate::import::linear::{self, LinearStatusMap};
pub fn run(
cfg: &Config,
action: &crate::cli::ImportAction,
json: bool,
) -> Result<(), Box<dyn std::error::Error>> {
use crate::cli::ImportAction;
let pool = db::open(&cfg.database.path)?;
crate::actor::set_default_transport(crate::actor::Transport::Cli);
let summary = match action {
ImportAction::Github {
repo,
project,
state,
token,
map_open,
map_closed,
user,
dry_run,
} => run_github(
&pool, repo, project, state, token.as_deref(), map_open, map_closed,
user.as_deref(), *dry_run,
)?,
ImportAction::Linear {
team,
project,
token,
user,
dry_run,
} => run_linear(&pool, team, project, token.as_deref(), user.as_deref(), *dry_run)?,
ImportAction::Jira {
site,
jira_project,
project,
email,
token,
user,
dry_run,
} => run_jira(
&pool, site, jira_project, project, email.as_deref(), token.as_deref(),
user.as_deref(), *dry_run,
)?,
};
print_summary(&summary, json);
Ok(())
}
fn resolve_project(pool: &DbPool, ident: &str) -> Result<i64, Box<dyn std::error::Error>> {
let conn = pool.read()?;
crate::db::queries::resolve_project_identifier(&conn, ident)
.map_err(|_| format!("Lific project '{ident}' not found. Create it first.").into())
}
fn resolve_bot(
pool: &DbPool,
source_slug: &str,
display: &str,
user: Option<&str>,
) -> Result<Option<i64>, Box<dyn std::error::Error>> {
match import::resolve_owner(pool, user)? {
Some(owner) => Ok(Some(import::ensure_import_bot(pool, owner, source_slug, display)?)),
None => Ok(None),
}
}
#[allow(clippy::too_many_arguments)]
fn run_github(
pool: &DbPool,
repo: &str,
project: &str,
state: &str,
token: Option<&str>,
map_open: &str,
map_closed: &str,
user: Option<&str>,
dry_run: bool,
) -> Result<ImportSummary, Box<dyn std::error::Error>> {
let (owner, name) = github::parse_repo(repo)?;
let state = StateFilter::parse(state)?;
let project_id = resolve_project(pool, project)?;
let status_map = import::StatusMap {
open: map_open.to_string(),
closed: map_closed.to_string(),
};
let bot = if dry_run {
None
} else {
resolve_bot(pool, "github", "GitHub Import", user)?
};
let fetcher = github::LiveGithub::new(&owner, &name, token.map(|s| s.to_string()))?;
let slug = format!("{owner}/{name}");
let fetched = github::collect(&fetcher, &slug, state, &status_map)?;
let summary = import::run_import(pool, project_id, bot, &fetched, dry_run)?;
Ok(summary)
}
fn run_linear(
pool: &DbPool,
team: &str,
project: &str,
token: Option<&str>,
user: Option<&str>,
dry_run: bool,
) -> Result<ImportSummary, Box<dyn std::error::Error>> {
let token = token
.ok_or("Linear API key required: pass --token or set LINEAR_API_KEY")?
.to_string();
let project_id = resolve_project(pool, project)?;
let map = LinearStatusMap::default();
let bot = if dry_run {
None
} else {
resolve_bot(pool, "linear", "Linear Import", user)?
};
let fetcher = linear::LiveLinear::new(team, token)?;
let fetched = linear::collect(&fetcher, &map)?;
let summary = import::run_import(pool, project_id, bot, &fetched, dry_run)?;
Ok(summary)
}
#[allow(clippy::too_many_arguments)]
fn run_jira(
pool: &DbPool,
site: &str,
jira_project: &str,
project: &str,
email: Option<&str>,
token: Option<&str>,
user: Option<&str>,
dry_run: bool,
) -> Result<ImportSummary, Box<dyn std::error::Error>> {
let email = email.ok_or("Jira email required: pass --email or set JIRA_EMAIL")?;
let token = token.ok_or("Jira API token required: pass --token or set JIRA_API_TOKEN")?;
let project_id = resolve_project(pool, project)?;
let map = JiraStatusMap::default();
let bot = if dry_run {
None
} else {
resolve_bot(pool, "jira", "Jira Import", user)?
};
let fetcher = jira::LiveJira::new(site, jira_project, email, token)?;
let fetched = jira::collect(&fetcher, site, &map)?;
let summary = import::run_import(pool, project_id, bot, &fetched, dry_run)?;
Ok(summary)
}
pub fn print_summary(summary: &ImportSummary, json: bool) {
if json {
println!("{}", serde_json::to_string_pretty(summary).unwrap());
return;
}
println!();
if summary.dry_run {
println!(" Dry run — nothing was written.");
println!();
println!(" Would create:");
println!(" issues: {}", summary.issues_created);
println!(" comments: {}", summary.comments_planned);
println!(" labels: {}", summary.labels_planned);
if summary.issues_skipped_existing > 0 {
println!(
" (skipping {} already-imported issue(s))",
summary.issues_skipped_existing
);
}
} else {
println!(" Imported:");
println!(" issues: {}", summary.issues_created);
println!(" comments: {}", summary.comments_created);
println!(" labels: {}", summary.labels_created);
if summary.issues_skipped_existing > 0 {
println!(
" skipped: {} already imported",
summary.issues_skipped_existing
);
}
}
if summary.skipped_non_issues
+ summary.skipped_assignees
+ summary.skipped_other
> 0
{
println!();
println!(" Not imported (by design):");
if summary.skipped_non_issues > 0 {
println!(" {} pull request(s)", summary.skipped_non_issues);
}
if summary.skipped_assignees > 0 {
println!(" {} assignee reference(s)", summary.skipped_assignees);
}
if summary.skipped_other > 0 {
println!(
" {} milestone/estimate/type reference(s)",
summary.skipped_other
);
}
}
println!();
}