Jump to…
snowinitial commitqoxwzsukwmkx1mo
1//! Conversion from `gix` types to `df-store` types.
2//!
3//! Everything Git-shaped stops here. Callers above the trait see only the
4//! plain data structures defined in `lib.rs`.
5
6use chrono::TimeZone;
7
8use crate::{EntryKind, Result, RevId, Revision, Signature, StoreError};
9
10/// Parse an opaque [`RevId`] back into a Git object id.
11///
12/// This is the one place allowed to know that a `RevId` is a Git oid.
13pub fn parse_oid(repo: &gix::Repository, rev: &RevId) -> Result<gix::ObjectId> {
14 repo.rev_parse_single(rev.as_str())
15 .map(|id| id.detach())
16 .map_err(|_| StoreError::NoSuchRevision)
17}
18
19pub fn find_commit<'r>(repo: &'r gix::Repository, rev: &RevId) -> Result<gix::Commit<'r>> {
20 let oid = parse_oid(repo, rev)?;
21 repo.find_object(oid)
22 .map_err(|_| StoreError::NoSuchRevision)?
23 .try_into_commit()
24 .map_err(|_| StoreError::NoSuchRevision)
25}
26
27pub fn entry_kind(mode: gix::object::tree::EntryMode) -> EntryKind {
28 use gix::object::tree::EntryKind as GixKind;
29 match mode.kind() {
30 GixKind::Tree => EntryKind::Directory,
31 GixKind::Link => EntryKind::Symlink,
32 GixKind::Commit => EntryKind::Submodule,
33 GixKind::Blob | GixKind::BlobExecutable => EntryKind::File,
34 }
35}
36
37/// Convert a `gix` signature into ours.
38///
39/// A commit with an unparseable or out-of-range timestamp still has to render —
40/// refusing to display history because one commit has a broken date would be
41/// worse than showing the epoch.
42fn to_signature(sig: gix::actor::SignatureRef<'_>) -> Signature {
43 // A commit written by a broken tool can carry a timestamp outside chrono's
44 // range. Falling back to the epoch keeps history renderable rather than
45 // failing a whole page for one bad date.
46 let when = chrono::Utc
47 .timestamp_opt(sig.time.seconds, 0)
48 .single()
49 .unwrap_or_else(|| chrono::Utc.timestamp_opt(0, 0).unwrap());
50
51 Signature {
52 name: sig.name.to_string(),
53 email: sig.email.to_string(),
54 when,
55 }
56}
57
58/// Build a [`Revision`] from a commit, including jj metadata.
59///
60/// The change id and conflict structure are read from the raw commit object by
61/// `df-index`, which owns that format knowledge — this crate does not duplicate
62/// the parser.
63pub fn to_revision(commit: &gix::Commit<'_>) -> Result<Revision> {
64 let raw = commit.data.as_ref();
65
66 let change_id = df_index::extract_change_id(raw).map(|c| c.as_str().to_owned());
67 let conflict = df_index::extract_conflict_trees(raw);
68
69 let decoded = commit
70 .decode()
71 .map_err(|e| StoreError::Other(anyhow::anyhow!("decoding commit: {e}")))?;
72
73 let parents = commit
74 .parent_ids()
75 .map(|p| RevId::from_stored(p.detach().to_string()))
76 .collect();
77
78 Ok(Revision {
79 rev: RevId::from_stored(commit.id().to_string()),
80 change_id,
81 parents,
82 author: to_signature(decoded.author()),
83 committer: to_signature(decoded.committer()),
84 message: decoded.message.to_string(),
85 conflicted: conflict.is_some(),
86 conflict_sides: conflict.as_ref().map(|c| c.sides.clone()).unwrap_or_default(),
87 conflict_bases: conflict.map(|c| c.bases).unwrap_or_default(),
88 })
89}

89 lines · Rust