gitai/remote/common/
parse.rs

1use std::collections::HashSet;
2use std::ffi::OsStr;
3use std::fs::File;
4use std::io::BufReader;
5use std::path::{Component, Path};
6
7use cause::Cause;
8use cause::cause;
9use git2::Repository;
10
11use super::ErrorType::{
12    self, DotGitWireFileNameNotUnique, DotGitWireFileOpen, DotGitWireFileParse,
13    DotGitWireFileSoundness, RepositoryRootPathCommand,
14};
15use super::Parsed;
16
17const DOT_GIT_WIRE: &str = ".gitwire";
18
19pub fn parse_gitwire() -> Result<(String, Vec<Parsed>), Cause<ErrorType>> {
20    let (root, file) = get_dotgitwire_file_path()?;
21    Ok((root, parse_dotgitwire_file(&file)?))
22}
23
24fn get_dotgitwire_file_path() -> Result<(String, String), Cause<ErrorType>> {
25    let repo = Repository::discover(".").map_err(|e| cause!(RepositoryRootPathCommand).src(e))?;
26    let workdir = repo
27        .workdir()
28        .ok_or_else(|| cause!(RepositoryRootPathCommand))?;
29    let root = workdir.to_string_lossy().to_string();
30
31    let file = format!("{root}/{DOT_GIT_WIRE}");
32    if !Path::new(&file).exists() {
33        return Err(cause!(
34            DotGitWireFileOpen,
35            "There is no .gitwire file in this repository"
36        ));
37    }
38    Ok((root, file))
39}
40
41fn parse_dotgitwire_file(file: &str) -> Result<Vec<Parsed>, Cause<ErrorType>> {
42    let f = File::open(file)
43        .map_err(|e| cause!(DotGitWireFileOpen, "no .gitwire file read permission").src(e))?;
44    let reader = BufReader::new(f);
45    let parsed: Vec<Parsed> = serde_json::from_reader(reader)
46        .map_err(|e| cause!(DotGitWireFileParse, ".gitwire file format is wrong").src(e))?;
47
48    for item in &parsed {
49        if !check_parsed_item_soundness(item) {
50            Err(cause!(
51                DotGitWireFileSoundness,
52                ".gitwire file's `src` and `dst` must not include '.', '..', and '.git'."
53            ))?;
54        }
55    }
56
57    let mut name_set: HashSet<&str> = HashSet::new();
58    for p in &parsed {
59        if let Some(ref name) = p.name
60            && !name_set.insert(name.as_str())
61        {
62            Err(cause!(
63                DotGitWireFileNameNotUnique,
64                ".gitwire file's `name`s must be differ each other."
65            ))?;
66        }
67    }
68
69    Ok(parsed)
70}
71
72fn check_parsed_item_soundness(parsed: &Parsed) -> bool {
73    let is_ok = |e: &Component| -> bool {
74        match e {
75            Component::Prefix(_) | Component::RootDir => true,
76            Component::Normal(name) => name.ne(&OsStr::new(".git")),
77            Component::ParentDir | Component::CurDir => false,
78        }
79    };
80    let src_result_ok = Path::new(&parsed.src).components().all(|p| is_ok(&p));
81    let dst_result_ok = Path::new(&parsed.dst).components().all(|p| is_ok(&p));
82    src_result_ok && dst_result_ok
83}