Skip to content

Instantly share code, notes, and snippets.

@stuarth
Created July 21, 2018 21:55
Show Gist options
  • Save stuarth/8ebd1a5e16052d1f9940a1d60587ee28 to your computer and use it in GitHub Desktop.
Save stuarth/8ebd1a5e16052d1f9940a1d60587ee28 to your computer and use it in GitHub Desktop.

Revisions

  1. stuarth created this gist Jul 21, 2018.
    223 changes: 223 additions & 0 deletions command_records.rs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,223 @@
    pub fn command<P: AsRef<Path>, P1: AsRef<Path>>(
    matches: &ArgMatches,
    repo: &Repository,
    mut config: Configuration,
    working_directory: P,
    config_path: P1,
    ) -> i32 {
    if !matches.is_present("no-author") && config.author.is_none() {
    if let Some(author) =
    cfg::Author::from_gitconfig(working_directory.as_ref().join(".git").join("config"))
    {
    config.author = Some(author);
    } else {
    if atty::is(atty::Stream::Stdin) {
    println!("SIT needs your authorship identity to be configured\n");
    use question::{Answer, Question};
    let name = loop {
    match Question::new("What is your name?").ask() {
    None => continue,
    Some(Answer::RESPONSE(value)) => {
    if value.trim() == "" {
    continue;
    } else {
    break value;
    }
    }
    Some(answer) => panic!("Invalid answer {:?}", answer),
    }
    };
    let email = match Question::new("What is your e-mail address?")
    .clarification("optional")
    .ask()
    {
    None => None,
    Some(Answer::RESPONSE(value)) => {
    if value.trim() == "" {
    None
    } else {
    Some(value)
    }
    }
    Some(answer) => panic!("Invalid answer {:?}", answer),
    };
    config.author = Some(cfg::Author { name, email });
    let file =
    fs::File::create(config_path).expect("can't open config file for writing");
    serde_json::to_writer_pretty(file, &config).expect("can't write config");
    } else {
    eprintln!("SIT needs your authorship identity to be configured (supported sources: sit, git), or re-run this command in a terminal\n");
    return 1;
    }
    }
    }

    let id = matches.value_of("id").unwrap();
    match repo.item(id) {
    None => {
    eprintln!("Item {} not found", id);
    return 1;
    }
    Some(item) => {
    fn record_files(
    matches: &ArgMatches,
    utc: DateTime<Utc>,
    config: &Configuration,
    ) -> Result<BoxedOrderedFiles<'static>, io::Error> {
    let files = matches
    .values_of("FILES")
    .unwrap_or(clap::Values::default());
    let files: OrderedFiles<_> = files
    .into_iter()
    .flat_map(move |name| {
    let path = PathBuf::from(&name);
    if path.is_file() {
    vec![(OsString::from(name), path)]
    } else if path.is_dir() {
    path.read_dir()
    .expect(&format!("error reading {}", path.display()))
    .filter_map(|f| {
    if let Ok(f) = f {
    if f.metadata().expect("error reading metadata").is_file() {
    Some(f)
    } else {
    None
    }
    } else {
    None
    }
    })
    .map(|entry: ::std::fs::DirEntry| (entry.file_name(), entry.path()))
    .collect()
    } else {
    eprintln!(
    "{} does not exist or is not a regular file",
    path.to_str().unwrap()
    );
    exit(1);
    }
    })
    .map(|(name, path)| {
    let abs_name =
    ::dunce::canonicalize(path).expect("can't canonicalize path");
    let cur_dir = ::dunce::canonicalize(
    env::current_dir().expect("can't get current directory"),
    ).expect("can't canonicalize current directory");
    match abs_name.strip_prefix(&cur_dir) {
    Err(_) => {
    eprintln!(
    "Path {:?} is not relative to {} current directory",
    name,
    cur_dir.to_str().unwrap()
    );
    exit(1);
    }
    Ok(path) => String::from(path.to_str().unwrap()),
    }
    })
    .map(|name| {
    (
    name.clone(),
    ::std::fs::File::open(name).expect("can't open file"),
    )
    })
    .into();

    let types: Vec<_> = match matches.value_of("type") {
    Some(types) => types.split(",").collect(),
    None => vec![],
    };

    let type_files: OrderedFiles<_> = types
    .iter()
    .map(|t| (format!(".type/{}", *t), &[][..]))
    .into();

    // .authors
    let authorship_files: Option<OrderedFiles<(String, _)>> =
    if !matches.is_present("no-author") {
    let authors = format!("{}", config.author.clone().unwrap());
    Some(vec![(String::from(".authors"), Cursor::new(authors))].into())
    } else {
    None
    };

    let timestamp: Option<OrderedFiles<(String, _)>> =
    if !matches.is_present("no-timestamp") {
    let timestamp = format!("{:?}", utc);
    Some(vec![(String::from(".timestamp"), Cursor::new(timestamp))].into())
    } else {
    None
    };

    Ok(files + type_files + authorship_files + timestamp)
    }

    let utc: DateTime<Utc> = Utc::now();

    let signing = matches.is_present("sign") || config.signing.enabled;

    let files = record_files(matches, utc, &config).expect("failed collecting files");

    let files = if signing {
    use std::ffi::OsString;
    let program = super::gnupg(matches, &config).expect("can't find GnuPG");
    let key = match matches
    .value_of("signing-key")
    .map(String::from)
    .or_else(|| config.signing.key.clone())
    {
    Some(key) => Some(OsString::from(key)),
    None => None,
    };
    let mut command = ::std::process::Command::new(program);

    command
    .stdin(::std::process::Stdio::piped())
    .stdout(::std::process::Stdio::piped())
    .arg("--sign")
    .arg("--armor")
    .arg("--detach-sign")
    .arg("-o")
    .arg("-");

    if key.is_some() {
    let _ = command.arg("--default-key").arg(key.unwrap());
    }

    let mut child = command.spawn().expect("failed spawning gnupg");

    {
    let mut stdin = child.stdin.as_mut().expect("Failed to open stdin");
    let mut hasher = repo.config().hashing_algorithm().hasher();
    files.hash(&mut *hasher).expect("failed hashing files");
    let hash = hasher.result_box();
    let encoded_hash = repo.config().encoding().encode(&hash);
    stdin
    .write_all(encoded_hash.as_bytes())
    .expect("Failed to write to stdin");
    }

    let output = child.wait_with_output().expect("failed to read stdout");

    if !output.status.success() {
    eprintln!("Error: {}", String::from_utf8_lossy(&output.stderr));
    return 1;
    } else {
    let files =
    record_files(matches, utc, &config).expect("failed collecting files");
    let signature_file: OrderedFiles<(String, _)> =
    vec![(String::from(".signature"), Cursor::new(output.stdout))].into();
    files + signature_file
    }
    } else {
    files
    };

    let record = item.new_record(files, true).expect("can't create a record");

    println!("{}", record.encoded_hash());
    }
    }
    return 0;
    }