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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#![allow(clippy::needless_doctest_main)]
//! Provide the current Git commit SHA1 during build.
//!
//! When building crates from a Git repository it is often desirable to extract the
//! current version number in form of the Git SHA1 to be displayed. This crate extracts
//! the current Git SHA1 during build time and makes it accessible as an environment
//! variable.
//!
//! If the crate is currently built without access to the Git SHA1 (i. e. it was extracted
//! from a tar-archive, or Git is not installed), instead of failing, it falls back on a
//! default value. This value defaults to "", but can be changed with the
//! [`use_default()`](struct.GitSHA1.html#method.use_default) method.
//!
//!
//! # Example
//!
//! In `build.rs`:
//! ```
//! use git_sha1::GitSHA1;
//!
//! fn main() {
//!     GitSHA1::read().set("GIT_SHA1");
//! }
//! ```
//!
//! In `main.rs`:
//! ```
//! use git_sha1::GitSHA1;
//!
//! // either as static &str:
//! static SHA1: &str = env!("GIT_SHA1");
//!
//! // or during runtime:
//! fn main() {
//!     let sha1 = GitSHA1::from_env("GIT_SHA1");
//!
//!     let long = sha1.long();
//!     assert_eq!(SHA1, long);
//!
//!     let short = sha1.short(10);
//!     // `short` may be shorter if SHA1 does not exist
//!     assert_eq!(short.len(), usize::min(10, short.len()));
//! }
//! ```
//!

use std::process::Command;

/// *deprecated*
///
/// Number of hex-characters in the Git SHA1.
///
/// There are half as many __bytes__ in a SHA1.
///
/// This value is true for any Git SHA1, but must not necessarily be the number of digits
/// available in the extracted value. Either because Git is not even installed, or because
/// the crate is not compiled from a Git repository and the implementation falls back on a
/// default string to return instead.
///
#[allow(dead_code)]
pub const GIT_SHA1_LENGTH: usize = 40;

pub struct GitSHA1 {
    /// default value that can be used instead of the real Git SHA1, if the crate is not
    /// in a Git repository (i. e. when building from a .tar-archive)
    default: String,
    /// Contains `None` if the current crate does not contain a Git SHA1.
    sha1: Option<String>,
}

impl GitSHA1 {
    /// Although designed with Git in mind, your crate may be compiled from source without
    /// being in a Git repository. This lets you define the value which is used instead of
    /// the real Git SHA1 as a drop-in replacement. The default value is "" (an empty
    /// string).
    ///
    /// # Example
    ///
    /// ```
    /// # use git_sha1::GitSHA1;
    /// GitSHA1::read().use_default("<empty>").set("GIT_SHA1");
    /// // will use: GIT_SHA1="<empty>"
    /// ```
    ///
    #[allow(dead_code)] // not used here
    pub fn use_default(mut self, default: &str) -> Self {
        self.default = default.into();
        self
    }

    /// Read current Git SHA1 by running `git rev-parse HEAD`.
    ///
    /// This only retrieves the SHA1 from git. Use
    /// [`set(name)`](struct.GitSHA1.html#method.set) to pass compile-time environment
    /// variable to cargo-build. Afterwards it can be read from your source files.
    ///
    /// If the current crate is not a git repository, the read will silently fail and the
    /// returned string in the end will be substituded by a default value.
    ///
    pub fn read() -> Self {
        let sha1: Option<String> = Command::new("git")
            .args(&["rev-parse", "HEAD"])
            .output()
            .map_or(None, |out| {
                Some(String::from_utf8(out.stdout).unwrap().trim().into())
            });

        Self {
            default: String::new(),
            sha1,
        }
    }

    /// Read the comiple-time environment variable from carto-build. This can be used
    /// during runtime, i.e. in `fn main() {}`.
    ///
    /// # Note:
    ///
    /// You can read the Git SHA1 statically using `env!`:
    ///
    /// ```
    /// // "GIT_SHA1" set in `build.rs`
    /// const GIT_SHA1: &'static str = env!("GIT_SHA1");
    /// ```
    #[allow(dead_code)] // because we never use it in `build.rs`
    pub fn from_env(name: &str) -> Self {
        Self {
            default: String::new(),
            sha1: std::env::var(name).ok(),
        }
    }

    /// Generate cargo environment variable for long Git SHA1.
    ///
    /// # Example
    ///
    /// ```
    /// # use git_sha1::GitSHA1;
    /// GitSHA1::read().set("GIT_SHA1");
    /// ```
    pub fn set(&self, name: &str) {
        println!(
            "cargo:rustc-env={}={}",
            name,
            self.sha1.as_ref().unwrap_or(&self.default)
        );
    }

    /// Return the complete SHA1 as a String.
    #[allow(dead_code)] // because we never use it in `build.rs`
    pub fn long(&self) -> String {
        self.sha1.as_ref().unwrap_or(&self.default).clone()
    }

    /// Return a subset of digits (short sha) as a String.
    ///
    /// # Note
    ///
    /// The number of digits extracted is silently truncated to the maximum number of
    /// digits available in the Git SHA1.
    ///
    #[allow(dead_code)] // because we never use it in `build.rs`
    pub fn short(&self, count: usize) -> String {
        match &self.sha1 {
            None => self.default.clone(),
            Some(s) => {
                let count = usize::min(s.len(), count);
                s.as_str()[..count].into()
            }
        }
    }
}