gitai/remote/common/
parse.rs1use 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}