>(
    repo: &github::Repo,
    db: &database::Db,
    mirror_root: &str,
    base_cgitrc: Option,
    max_repo_size_bytes: Option,
) -> anyhow::Result<()> {
    if let Some(max_repo_size_bytes) = max_repo_size_bytes {
        if is_repo_oversize(repo.size, max_repo_size_bytes) {
            return Ok(());
        }
    }
    let id = repo.id;
    let path = clone_path(&mirror_root, &repo);
    let db_repo = database::Repo::from(repo);
    match db.repo_get(id) {
        // If we've already seen the repo and it's been updated, fetch the
        // latest.
        Ok(current_repo) => {
            if db.repo_is_updated(&db_repo)? {
                update(&path, ¤t_repo, &repo)?;
                db.repo_update(&db_repo)?;
            }
        },
        // If the repo doesn't exist, mirror it and store it in the
        // database.
        Err(database::Error::Db(rusqlite::Error::QueryReturnedNoRows)) => {
            mirror(
                path,
                &repo,
                base_cgitrc,
            )?;
            db.repo_insert(db_repo)?;
        },
        Err(e) => anyhow::bail!(e),
    }
    Ok(())
}
/// Return `true` if `size_kilobytes` is larger than `max_repo_size_bytes`.
fn is_repo_oversize(
    size_kilobytes: u64,
    max_repo_size_bytes: u64,
) -> bool {
    let size_bytes = size_kilobytes * 1000;
    if size_bytes > max_repo_size_bytes {
        return true;
    }
    false
}
/// Get the clone path for a repository.
///
/// If `repo` is a fork, add `/fork/` to `base_path`.
fn clone_path>(base_path: P, repo: &github::Repo) -> PathBuf {
    let git_dir = format!("{}.git", repo.name);
    if repo.fork {
        base_path
            .as_ref()
            .join("fork")
            .join(git_dir)
    } else {
        base_path
            .as_ref()
            .join(git_dir)
    }
}
/// Mirror a repository.
fn mirror(
    clone_path: P1,
    repo: &github::Repo,
    base_cgitrc: Option,
) -> anyhow::Result<()>
where
    P1: AsRef,
    P2: AsRef,
{
    git::mirror(
        &repo.clone_url,
        &clone_path,
        repo.description(),
        &repo.default_branch,
    )?;
    // Copy the base cgitrc file into the newly-cloned repository.
    if let Some(base_cgitrc) = base_cgitrc {
        let cgitrc_path = clone_path.as_ref().join("cgitrc");
        fs::copy(&base_cgitrc, &cgitrc_path)
            .with_context(|| format!(
                "unable to copy '{}' to '{}'",
                "./cgitrc",
                &cgitrc_path.display(),
            ))?;
    }
    if repo.default_branch != "master" {
        repo_cgitrc_set_defbranch(&clone_path, &repo.default_branch)?;
    }
    update_mtime(&clone_path, &repo)?;
    Ok(())
}
/// Update a previously-mirrored repository.
fn update>(
    repo_path: P,
    current_repo: &database::Repo,
    updated_repo: &github::Repo,
) -> anyhow::Result<()> {
    git::update(&repo_path)?;
    let remote_description = updated_repo.description();
    if current_repo.description() != remote_description {
        git::update_description(&repo_path, remote_description)?;
    }
    if let Some(default_branch) = ¤t_repo.default_branch {
        if default_branch != &updated_repo.default_branch {
            git::change_current_branch(
                &repo_path,
                &updated_repo.default_branch,
            )?;
            repo_cgitrc_set_defbranch(&repo_path, &updated_repo.default_branch)?;
        }
    }
    update_mtime(&repo_path, &updated_repo)?;
    Ok(())
}
/// Set the mtime of the repository to GitHub's `pushed_at` time.
///
/// Used for CGit "age" sorting.
fn update_mtime>(
    repo_path: P,
    repo: &github::Repo,
) -> anyhow::Result<()> {
    let update_time = filetime::FileTime::from_system_time(
        DateTime::parse_from_rfc3339(&repo.pushed_at)
            .with_context(|| format!(
                "unable to parse update time from '{}'",
                &repo.pushed_at,
            ))?
            .into()
    );
    let default_branch_ref = repo_path
        .as_ref()
        .join("refs/heads")
        .join(&repo.default_branch);
    // Try updating times on the default ref.
    match filetime::set_file_times(
        &default_branch_ref,
        update_time,
        update_time,
    ) {
        Ok(_) => Ok(()),
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
            // If the default ref file doesn't exist, update times on the
            // 'packed-refs' file.
            let packed_refs_path = repo_path
                .as_ref()
                .join("packed-refs");
            match filetime::set_file_times(
                &packed_refs_path,
                update_time,
                update_time,
            ) {
                Ok(_) => Ok(()),
                Err(e) if e.kind() == io::ErrorKind::NotFound => {
                    // In the absence of a 'packed-refs' file, create a CGit
                    // agefile and add the update time to it.
                    Ok(set_agefile_time(&repo_path, &repo.pushed_at)?)
                },
                Err(e) => Err(e),
            }
                .with_context(|| format!(
                    "unable to set mtime on '{}'",
                    &packed_refs_path.display(),
                ))?;
            Ok(())
        },
        Err(e) => Err(e),
    }
        .with_context(|| format!(
            "unable to set mtime on '{}'",
            &default_branch_ref.display(),
        ))?;
    Ok(())
}
/// Write `update_time` into the repo's `info/web/last-modified` file.
fn set_agefile_time>(
    repo_path: P,
    update_time: &str,
) -> anyhow::Result<()> {
    let agefile_dir = repo_path.as_ref().join("info/web");
    fs::DirBuilder::new()
        .create(&agefile_dir)
        .with_context(|| format!(
            "unable to create directory '{}'",
            &agefile_dir.display(),
        ))?;
    let agefile_path = agefile_dir.join("last-modified");
    let mut agefile = fs::OpenOptions::new()
        .write(true)
        .truncate(true)
        .create(true)
        .open(&agefile_path)
        .with_context(|| format!(
            "unable to open '{}'",
            &agefile_path.display(),
        ))?;
    writeln!(agefile, "{}", &update_time)
        .with_context(|| format!(
            "unable to write to '{}'",
            &agefile_path.display(),
        ))?;
    Ok(())
}
/// Set the default CGit branch in the repository's "cgitrc" file.
fn repo_cgitrc_set_defbranch>(
    repo_path: P,
    default_branch: &str,
) -> anyhow::Result<()> {
    repo_cgitrc_append(
        &repo_path,
        &format!("defbranch={}", default_branch),
    )?;
    Ok(())
}
/// Append `config` to the repo-local "cgitrc" file.
fn repo_cgitrc_append>(
    repo_path: P,
    config: &str,
) -> anyhow::Result<()> {
    let cgitrc_path = repo_path
        .as_ref()
        .join("cgitrc");
    let mut cgitrc_file = fs::OpenOptions::new()
        .append(true)
        .create(true)
        .open(&cgitrc_path)
        .with_context(|| format!(
            "unable to open '{}'",
            &cgitrc_path.display(),
        ))?;
    writeln!(cgitrc_file, "{}", config)
        .with_context(|| format!(
            "unable to write to '{}'",
            &cgitrc_path.display(),
        ))?;
    Ok(())
}