1use super::event_path::{EventPath, base64_standard};
20use super::content_digest::ContentDigest;
22use super::json_record::{ObjectWriter, string_array};
23use super::transaction_journal::{
25 AddedPath, Base, Conclusion, FileIdentity, IndexLockRecord, JOURNAL_SCHEMA_VERSION,
26 LandingOperation, LandingRecord, LockIdentity, PreparedRecord, PreparingRecord, RefFormat,
27 SymbolicHead, TransactionMode,
28};
29
30fn encode_base(base: &Base) -> String {
40 let mut writer: ObjectWriter = ObjectWriter::new();
41 match base {
42 Base::Unborn => {
43 writer.string("kind", "unborn");
44 }
45 Base::Commit(oid) => {
46 writer.string("kind", "commit").string("oid", oid.as_str());
47 }
48 }
49 return writer.finish();
50}
51
52fn encode_identity(identity: &FileIdentity) -> String {
62 let mut writer: ObjectWriter = ObjectWriter::new();
63 writer
64 .string("device", identity.device.as_str())
65 .string("inode", identity.inode.as_str());
66 return writer.finish();
67}
68
69fn encode_digest(digest: &ContentDigest) -> String {
79 let mut writer: ObjectWriter = ObjectWriter::new();
80 writer
81 .integer("length", digest.length as i64)
82 .string("fnv1a64", digest.hash_text().as_str());
83 return writer.finish();
84}
85
86fn encode_lock(lock: &LockIdentity) -> String {
96 let mut writer: ObjectWriter = ObjectWriter::new();
97 writer
98 .string("device", lock.file.device.as_str())
99 .string("inode", lock.file.inode.as_str())
100 .string("fsId", lock.fs_id.as_str());
101 return writer.finish();
102}
103
104fn push_path_list(writer: &mut ObjectWriter, key: &str, bytes_key: &str, paths: &[Vec<u8>]) {
117 let event_paths: Vec<EventPath> = paths.iter().map(|path| { return EventPath::from_git_bytes(path.as_slice()); }).collect();
118 let texts: Vec<String> = event_paths.iter().map(|path| { return String::from(path.text()); }).collect();
119 writer.raw(key, string_array(texts.as_slice()).as_str());
120 if event_paths.iter().any(|path| { return path.exact().is_some(); }) {
121 let encoded: Vec<String> = event_paths.iter().map(|path| { return base64_standard(path.bytes()); }).collect();
122 writer.raw(bytes_key, string_array(encoded.as_slice()).as_str());
123 }
124}
125
126fn encode_added_paths(records: &[AddedPath]) -> String {
136 let mut text: String = String::from("[");
137 for (index, record) in records.iter().enumerate() {
138 if index > 0 {
139 text.push(',');
140 }
141 let mut writer: ObjectWriter = ObjectWriter::new();
142 let path: EventPath = EventPath::from_git_bytes(record.path.as_slice());
143 writer.string("path", path.text());
144 if let Some(exact) = path.exact() {
145 writer.string("pathBytes", base64_standard(exact).as_str());
146 }
147 writer
148 .string("gitMode", record.git_mode.as_str())
149 .string("originalOid", record.original_oid.as_str())
150 .string("intendedOid", record.intended_oid.as_str());
151 text.push_str(writer.finish().as_str());
152 }
153 text.push(']');
154 return text;
155}
156
157fn record_head(state: &str) -> ObjectWriter {
168 let mut writer: ObjectWriter = ObjectWriter::new();
169 writer
170 .integer("schemaVersion", JOURNAL_SCHEMA_VERSION)
171 .string("state", state);
172 return writer;
173}
174
175pub fn mode_name(mode: TransactionMode) -> &'static str {
185 match mode {
186 TransactionMode::ExplicitPath => return "explicit-path",
187 TransactionMode::Index => return "index",
188 }
189}
190
191pub fn conclusion_name(conclusion: Conclusion) -> &'static str {
201 match conclusion {
202 Conclusion::None => return "none",
203 Conclusion::Amend => return "amend",
204 Conclusion::Merge => return "merge",
205 Conclusion::CherryPick => return "cherry-pick",
206 Conclusion::Revert => return "revert",
207 }
208}
209
210pub fn ref_format_name(format: RefFormat) -> &'static str {
220 match format {
221 RefFormat::Files => return "files",
222 RefFormat::Reftable => return "reftable",
223 }
224}
225
226pub fn encode_preparing(record: &PreparingRecord) -> String {
236 let mut writer: ObjectWriter = record_head("preparing");
237 writer
238 .string("transactionId", record.transaction_id.as_str())
239 .string("mode", mode_name(record.mode))
240 .raw("base", encode_base(&record.base).as_str());
241 let head: String = match &record.symbolic_head {
242 SymbolicHead::Branch(name) => {
243 let mut nested: ObjectWriter = ObjectWriter::new();
244 nested.string("kind", "branch").string("ref", name.as_str());
245 nested.finish()
246 }
247 SymbolicHead::Detached => String::from("{\"kind\":\"detached\"}"),
248 };
249 writer
250 .raw("symbolicHead", head.as_str())
251 .string("targetRef", record.target_ref.as_str())
252 .string("conclusion", conclusion_name(record.conclusion))
253 .string("repositoryRoot", record.repository_root.as_str())
254 .string("gitDir", record.git_dir.as_str())
255 .string("commonDir", record.common_dir.as_str())
256 .string("realIndexPath", record.real_index_path.as_str())
257 .string("objectDirectory", record.object_directory.as_str())
258 .string("refFormat", ref_format_name(record.ref_format))
259 .string("emptyTreeOid", record.empty_tree_oid.as_str())
260 .string("shadowPath", record.shadow_path.as_str());
261 push_path_list(&mut writer, "selectedPathspecs", "selectedPathspecBytes", record.selected_pathspecs.as_slice());
262 writer.string("invokedAt", record.invoked_at.as_str());
263 return format!("{}\n", writer.finish());
264}
265
266pub fn encode_prepared(record: &PreparedRecord) -> String {
276 let mut writer: ObjectWriter = record_head("prepared");
277 writer
278 .string("shadowPath", record.shadow_path.as_str())
279 .string("preparedOid", record.prepared_oid.as_str())
280 .boolean("signed", record.signed)
281 .string("intendedTreeOid", record.intended_tree_oid.as_str());
282 push_path_list(&mut writer, "committedPaths", "committedPathBytes", record.committed_paths.as_slice());
283 writer
284 .raw(
285 "addedPaths",
286 encode_added_paths(record.added_paths.as_slice()).as_str(),
287 )
288 .raw(
289 "selectedWorktreePaths",
290 encode_added_paths(record.selected_worktree_paths.as_slice()).as_str(),
291 );
292 return format!("{}\n", writer.finish());
293}
294
295pub fn encode_index_lock(record: &IndexLockRecord) -> String {
305 let mut writer: ObjectWriter = record_head("index-locked");
306 writer
307 .integer("attempt", record.attempt)
308 .raw("lock", encode_lock(&record.lock).as_str());
309 return format!("{}\n", writer.finish());
310}
311
312pub fn encode_landing(record: &LandingRecord) -> String {
325 let mut writer: ObjectWriter = record_head("landing");
326 writer.integer("attempt", record.attempt).string(
327 "operation",
328 match record.operation {
329 LandingOperation::Commit => "commit",
330 LandingOperation::NormalizeOnly => "normalize-only",
331 },
332 );
333 writer.raw("expectedOld", encode_base(&record.expected_old).as_str());
334 if let Some(oid) = &record.new_oid {
335 writer.string("newOid", oid.as_str());
336 }
337 writer
338 .string("landedTreeOid", record.landed_tree_oid.as_str())
339 .raw(
340 "preLandingIndex",
341 encode_identity(&record.pre_landing_index).as_str(),
342 )
343 .raw("postIndex", encode_identity(&record.post_index).as_str())
344 .raw("lock", encode_lock(&record.lock).as_str());
345 if let Some(pack) = &record.pack_name {
346 writer.string("packName", pack.as_str());
347 }
348 writer
349 .raw(
350 "addedPaths",
351 encode_added_paths(record.added_paths.as_slice()).as_str(),
352 )
353 .raw(
354 "selectedWorktreePaths",
355 encode_added_paths(record.selected_worktree_paths.as_slice()).as_str(),
356 );
357 if let Some(digest) = &record.pre_landing_digest {
358 writer.raw("preLandingDigest", encode_digest(digest).as_str());
359 }
360 if let Some(digest) = &record.post_digest {
361 writer.raw("postDigest", encode_digest(digest).as_str());
362 }
363 return format!("{}\n", writer.finish());
364}
365
366pub fn encode_ref_updated(landed_oid: &str) -> String {
376 let mut writer: ObjectWriter = record_head("ref-updated");
377 writer.string("landedOid", landed_oid);
378 return format!("{}\n", writer.finish());
379}
380
381#[cfg(test)]
383#[path = "transaction_journal_encode_tests.rs"]
384pub(crate) mod tests;