1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! Simple management of the current VCS level that we're running at.

use crate::bail;
use crate::errors::Result;
use crate::git::Repo;
use std::cmp::{max, min};
use std::str::FromStr;
use tracing::debug;

#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Debug)]
pub struct VcsState {
  level: VcsLevel,
  ignore_current: bool
}

impl VcsState {
  pub fn new(level: VcsLevel, ignore_current: bool) -> VcsState { VcsState { level, ignore_current } }
  pub fn level(&self) -> &VcsLevel { &self.level }
  pub fn ignore_current(&self) -> bool { self.ignore_current }
}

#[derive(Debug)]
pub struct VcsRange {
  min: VcsLevel,
  max: VcsLevel
}

impl VcsRange {
  pub fn new(min: VcsLevel, max: VcsLevel) -> VcsRange { VcsRange { min, max } }
  pub fn full() -> VcsRange { VcsRange { min: VcsLevel::None, max: VcsLevel::Smart } }
  pub fn exact(lvl: VcsLevel) -> VcsRange { VcsRange { min: lvl, max: lvl } }

  pub fn min(&self) -> VcsLevel { self.min }
  pub fn max(&self) -> VcsLevel { self.max }
  pub fn is_empty(&self) -> bool { self.min > self.max }

  pub fn intersect(&self, other: &VcsRange) -> VcsRange {
    VcsRange::new(max(self.min(), other.min()), min(self.max(), other.max()))
  }

  pub fn detect() -> Result<VcsRange> { Ok(VcsRange::new(VcsLevel::None, Repo::detect(".")?)) }

  pub fn detect_and_combine(pref: &VcsRange, reqd: &VcsRange) -> Result<VcsRange> {
    if pref.is_empty() {
      bail!("Preferred VCS {:?} is empty.", pref);
    } else if reqd.is_empty() {
      bail!("Required VCS {:?} is empty.", reqd);
    }

    let i1 = pref.intersect(reqd);
    if i1.is_empty() {
      if pref.min() > reqd.max() {
        bail!("Preferred VCS {:?} grtr than required {:?}.", pref, reqd);
      } else {
        bail!("Preferred VCS {:?} less than required {:?}.", pref, reqd);
      }
    }

    let dctd = VcsRange::detect()?;
    let i2 = i1.intersect(&dctd);
    if i2.is_empty() {
      bail!("Couldn't detect {:?} with preferred {:?} required {:?}", dctd, pref, reqd);
    }

    debug!("Combining preferred {:?}, required {:?}, detected {:?} = {:?}", pref, reqd, dctd, i2.max());

    Ok(i2)
  }
}

#[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Debug)]
pub enum VcsLevel {
  None = 0,
  Local = 1,
  Remote = 2,
  Smart = 3
}

impl VcsLevel {
  pub fn is_none(&self) -> bool { matches!(self, Self::None) }
  pub fn is_local(&self) -> bool { matches!(self, Self::Local) }
  pub fn is_network(&self) -> bool { matches!(self, Self::Remote | Self::Smart) }
}

impl FromStr for VcsLevel {
  type Err = crate::errors::Error;

  fn from_str(v: &str) -> Result<VcsLevel> {
    match v {
      "none" => Ok(VcsLevel::None),
      "local" => Ok(VcsLevel::Local),
      "remote" => Ok(VcsLevel::Remote),
      "smart" => Ok(VcsLevel::Smart),
      other => err!("Illegal vcs level \"{}\".", other)
    }
  }
}