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
//! Abstraction over git repositories.
//! Uses git command available on the system to keep a repo in-sync.

use core::errors::*;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use url::Url;

const GIT_BIN_ENV: &'static str = "REPROTO_GIT_BIN";
const FETCH_HEAD: &'static str = "FETCH_HEAD";

#[cfg(unix)]
mod sys {
    pub const DEFAULT_GIT_COMMAND: &'static str = "git";
}

#[cfg(windows)]
mod sys {
    pub const DEFAULT_GIT_COMMAND: &'static str = "git.exe";
}

use self::sys::*;

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct GitRepo {
    git_command: String,
    work_tree: PathBuf,
    git_dir: PathBuf,
    remote: Url,
    revspec: String,
}

impl GitRepo {
    pub fn with_remote<P: AsRef<Path>>(path: P, remote: Url, revspec: String) -> Result<GitRepo> {
        let path = path.as_ref();

        let git_command = find_git_command()?;

        let git_repo = GitRepo {
            git_command: git_command,
            work_tree: path.to_owned(),
            git_dir: path.join(".git"),
            remote: remote,
            revspec: revspec,
        };

        if !path.is_dir() {
            trace!("Initializing git repo in {}", path.display());
            fs::create_dir_all(path)?;
            git_repo.git(&["init"])?;
            git_repo.update()?;
        }

        Ok(git_repo)
    }

    pub fn path(&self) -> &Path {
        self.work_tree.as_ref()
    }

    pub fn git(&self, args: &[&str]) -> Result<()> {
        let mut command = Command::new(&self.git_command);

        command
            .args(args)
            .env("GIT_DIR", &self.git_dir)
            .env("GIT_WORK_TREE", &self.work_tree);

        debug!("git: {:?}", command);

        let status = command.status()?;

        if !status.success() {
            let code = status.code().unwrap_or(-1);
            return Err(format!("git: bad exit code: {}", code).into());
        }

        Ok(())
    }

    pub fn reset(&self, revspec: &str) -> Result<()> {
        self.git(&["reset", "--hard", revspec])?;
        Ok(())
    }

    /// Update the repository.
    pub fn update(&self) -> Result<()> {
        info!("Updating {}", self.remote);
        self.git(&["fetch", self.remote.as_ref(), &self.revspec])?;
        self.reset(FETCH_HEAD)
    }
}

fn find_git_command() -> Result<String> {
    match env::var(GIT_BIN_ENV) {
        Ok(git_bin) => return Ok(git_bin.to_owned()),
        Err(_) => {}
    };

    Ok(DEFAULT_GIT_COMMAND.to_owned())
}