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
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
//! defines functions/types related to local repository access.

use std::ffi::OsStr;
use std::fmt::Display;
use std::path::{Path, PathBuf};

use util::{self, process};
use vcs::{self, Vcs};


/// Information of remote repository
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Remote {
    url: String,
}

impl Remote {
    pub fn new<S: Into<String>>(url: S) -> Remote {
        // TODO: verify URL
        Remote { url: url.into() }
    }

    pub fn url(&self) -> &str {
        &self.url
    }
}


/// local repository
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Repository {
    /// name of repository
    name: String,
    /// (canonicalized) absolute path of the repository
    path: PathBuf,
    /// used version control system
    vcs: Vcs,
    /// information of remote repository
    #[serde(skip_serializing_if = "Option::is_none")]
    remote: Option<Remote>,
}

impl Repository {
    /// Make an instance of `Repository` from local path.
    pub fn from_path<P: AsRef<Path>>(path: P) -> ::Result<Self> {
        let path = util::canonicalize_pretty(path)?;
        let name = path.file_name()
            .map(|s| s.to_string_lossy().into_owned())
            .ok_or("cannot determine repository name")?;
        let vcs = vcs::detect_from_path(&path).ok_or("cannot detect VCS")?;
        let remote = vcs.get_remote_url(&path)?.map(Remote::new);
        Ok(Repository {
            name: name,
            path: path,
            vcs: vcs,
            remote: remote,
        })
    }

    pub fn from_path_with_remote<P: AsRef<Path>>(path: P, remote: Remote) -> ::Result<Self> {
        let path = util::canonicalize_pretty(path)?;
        let name = path.file_name()
            .map(|s| s.to_string_lossy().into_owned())
            .ok_or("cannot determine repository name")?;
        let vcs = vcs::detect_from_path(&path).ok_or("cannot detect VCS")?;
        Ok(Repository {
            name: name,
            path: path,
            vcs: vcs,
            remote: Some(remote),
        })
    }

    /// Check existence of repository and drop if not exists.
    pub fn refresh(self) -> Option<Self> {
        Self::from_path(self.path).ok()
    }

    pub fn is_same_local(&self, other: &Self) -> bool {
        self.path.as_path() == other.path.as_path()
    }

    pub fn is_contained<P: AsRef<Path>>(&self, path: P) -> bool {
        self.path.starts_with(path)
    }

    /// Run command into the repository.
    pub fn run_command<I, S>(&self, command: &str, args: I) -> ::Result<bool>
    where
        I: IntoIterator<Item = S>,
        S: AsRef<OsStr> + Display,
    {
        let output = process::inherit(command)
            .args(args)
            .current_dir(&self.path)
            .output()?;
        Ok(output.status.success())
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn path_string(&self) -> String {
        format!("{}", self.path.display())
    }

    pub fn remote(&self) -> Option<&Remote> {
        self.remote.as_ref()
    }
}