diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 44ed2c750b..a5bdb6c92e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -28,6 +28,7 @@ required-features = ["test-fakes"] [build-dependencies] cargo_metadata = { workspace = true } +chrono = { workspace = true } [dependencies] chrono = { workspace = true } diff --git a/cli/build.rs b/cli/build.rs index 2d626380f3..0433ed3f0d 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -12,11 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +extern crate chrono; + use std::path::Path; use std::process::Command; use std::str; use cargo_metadata::MetadataCommand; +use chrono::prelude::*; const GIT_HEAD_PATH: &str = "../.git/HEAD"; const JJ_OP_HEADS_PATH: &str = "../.jj/repo/op_heads/heads"; @@ -41,8 +44,16 @@ fn main() -> std::io::Result<()> { } println!("cargo:rerun-if-env-changed=NIX_JJ_GIT_HASH"); - if let Some(git_hash) = get_git_hash() { - println!("cargo:rustc-env=JJ_VERSION={}-{}", version, git_hash); + // TODO: timestamp can be "nix" + if let Some((maybe_date, git_hash)) = get_git_timestamp_and_hash() { + println!( + "cargo:rustc-env=JJ_VERSION={}-{}-{}", + version, + maybe_date + .map(|d| d.format("%Y%m%d").to_string()) + .unwrap_or_else(|| "dateunknown".to_string()), + git_hash + ); } else { println!("cargo:rustc-env=JJ_VERSION={}", version); } @@ -50,12 +61,27 @@ fn main() -> std::io::Result<()> { Ok(()) } -fn get_git_hash() -> Option { +/// Convert a string with a unix timestamp to a date +fn timestamp_to_date(ts_str: &str) -> Option> { + ts_str + .parse::() + .ok() + .and_then(|ts| DateTime::::from_timestamp(ts, 0)) +} + +/// Return the UTC committer date (maybe) and the git hash +fn get_git_timestamp_and_hash() -> Option<(Option>, String)> { if let Some(nix_hash) = std::env::var("NIX_JJ_GIT_HASH") .ok() .filter(|s| !s.is_empty()) { - return Some(nix_hash); + return Some((None, nix_hash)); + } + + fn parse_timestamp_vbar_hash(bytes: &[u8]) -> (Option>, String) { + let s = str::from_utf8(bytes).unwrap().trim_end(); + let (ts_str, id) = s.split_once('|').unwrap(); + (timestamp_to_date(ts_str), id.to_owned()) } if let Ok(output) = Command::new("jj") .args([ @@ -64,19 +90,21 @@ fn get_git_hash() -> Option { "log", "--no-graph", "-r=@-", - "-T=commit_id", + r#"-T=committer.timestamp().utc().format("%s") ++ "|" ++ commit_id"#, ]) .output() { if output.status.success() { - return Some(String::from_utf8(output.stdout).unwrap()); + return Some(parse_timestamp_vbar_hash(&output.stdout)); } } - if let Ok(output) = Command::new("git").args(["rev-parse", "HEAD"]).output() { + if let Ok(output) = Command::new("git") + .args(["log", "-1", "--format=%ct|%H", "HEAD"]) + .output() + { if output.status.success() { - let line = str::from_utf8(&output.stdout).unwrap(); - return Some(line.trim_end().to_owned()); + return Some(parse_timestamp_vbar_hash(&output.stdout)); } } diff --git a/cli/tests/test_global_opts.rs b/cli/tests/test_global_opts.rs index 08b85b17bf..ac81a843e4 100644 --- a/cli/tests/test_global_opts.rs +++ b/cli/tests/test_global_opts.rs @@ -14,6 +14,8 @@ use std::ffi::OsString; +use assert_matches::assert_matches; + use crate::common::{get_stderr_string, TestEnvironment}; pub mod common; @@ -67,10 +69,12 @@ fn test_no_subcommand() { let stdout = test_env.jj_cmd_success(test_env.env_root(), &["--version"]); let sanitized = stdout.replace(|c: char| c.is_ascii_hexdigit(), "?"); - assert!( - sanitized == "jj ?.??.?\n" - || sanitized == "jj ?.??.?-????????????????????????????????????????\n", - "{sanitized}" + assert_matches!( + sanitized.as_str(), + "jj ?.??.?\n" + | "jj ?.??.?-????????-????????????????????????????????????????\n" + // `dateunknown` turns into `??t?unknown` since d,a,e are hex digits. + | "jj ?.??.?-??t?unknown-????????????????????????????????????????\n" ); let stdout = test_env.jj_cmd_success(test_env.env_root(), &["-R", "repo"]);