1use std::cmp::Ordering;
9use std::collections::{HashMap, HashSet};
10
11use std::fmt::Display;
12use std::str::FromStr;
13use std::time::{Duration, SystemTime};
14
15use eyre::Context;
16use tracing::{error, instrument};
17
18use crate::core::effects::{Effects, OperationType};
19use crate::core::repo_ext::RepoExt;
20use crate::git::{CategorizedReferenceName, MaybeZeroOid, NonZeroOid, ReferenceName, Repo};
21
22use super::repo_ext::RepoReferencesSnapshot;
23
24pub const BRANCHLESS_TRANSACTION_ID_ENV_VAR: &str = "BRANCHLESS_TRANSACTION_ID";
27
28#[derive(Clone, Debug)]
30struct Row {
31 timestamp: f64,
32 type_: String,
33 event_tx_id: isize,
34 ref1: Option<ReferenceName>,
35 ref2: Option<ReferenceName>,
36 ref_name: Option<ReferenceName>,
37 message: Option<ReferenceName>,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum EventTransactionId {
58 Id(isize),
60
61 Suppressed,
63}
64
65impl Display for EventTransactionId {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 EventTransactionId::Id(event_id) => write!(f, "{event_id}"),
69 EventTransactionId::Suppressed => write!(f, "SUPPRESSED"),
70 }
71 }
72}
73
74impl FromStr for EventTransactionId {
75 type Err = <isize as FromStr>::Err;
76
77 fn from_str(s: &str) -> Result<Self, Self::Err> {
78 if s == "SUPPRESSED" {
79 Ok(EventTransactionId::Suppressed)
80 } else {
81 let event_id = s.parse()?;
82 Ok(EventTransactionId::Id(event_id))
83 }
84 }
85}
86
87#[derive(Clone, Debug, PartialEq)]
89pub enum Event {
90 RewriteEvent {
97 timestamp: f64,
99
100 event_tx_id: EventTransactionId,
102
103 old_commit_oid: MaybeZeroOid,
105
106 new_commit_oid: MaybeZeroOid,
108 },
109
110 RefUpdateEvent {
116 timestamp: f64,
118
119 event_tx_id: EventTransactionId,
121
122 ref_name: ReferenceName,
126
127 old_oid: MaybeZeroOid,
129
130 new_oid: MaybeZeroOid,
132
133 message: Option<ReferenceName>,
135 },
136
137 CommitEvent {
141 timestamp: f64,
143
144 event_tx_id: EventTransactionId,
146
147 commit_oid: NonZeroOid,
149 },
150
151 ObsoleteEvent {
156 timestamp: f64,
158
159 event_tx_id: EventTransactionId,
161
162 commit_oid: NonZeroOid,
164 },
165
166 UnobsoleteEvent {
171 timestamp: f64,
173
174 event_tx_id: EventTransactionId,
176
177 commit_oid: NonZeroOid,
179 },
180
181 WorkingCopySnapshot {
184 timestamp: f64,
186
187 event_tx_id: EventTransactionId,
189
190 head_oid: MaybeZeroOid,
192
193 commit_oid: NonZeroOid,
196
197 ref_name: Option<ReferenceName>,
200 },
201}
202
203impl Event {
204 pub fn get_timestamp(&self) -> SystemTime {
206 let timestamp = match self {
207 Event::RewriteEvent { timestamp, .. } => timestamp,
208 Event::RefUpdateEvent { timestamp, .. } => timestamp,
209 Event::CommitEvent { timestamp, .. } => timestamp,
210 Event::ObsoleteEvent { timestamp, .. } => timestamp,
211 Event::UnobsoleteEvent { timestamp, .. } => timestamp,
212 Event::WorkingCopySnapshot { timestamp, .. } => timestamp,
213 };
214 SystemTime::UNIX_EPOCH + Duration::from_secs_f64(*timestamp)
215 }
216
217 pub fn get_event_tx_id(&self) -> EventTransactionId {
219 match self {
220 Event::RewriteEvent { event_tx_id, .. } => *event_tx_id,
221 Event::RefUpdateEvent { event_tx_id, .. } => *event_tx_id,
222 Event::CommitEvent { event_tx_id, .. } => *event_tx_id,
223 Event::ObsoleteEvent { event_tx_id, .. } => *event_tx_id,
224 Event::UnobsoleteEvent { event_tx_id, .. } => *event_tx_id,
225 Event::WorkingCopySnapshot { event_tx_id, .. } => *event_tx_id,
226 }
227 }
228}
229
230impl TryFrom<Event> for Row {
231 type Error = ();
232
233 fn try_from(event: Event) -> Result<Self, Self::Error> {
234 let row = match event {
235 Event::RewriteEvent {
236 event_tx_id: EventTransactionId::Suppressed,
237 ..
238 }
239 | Event::RefUpdateEvent {
240 event_tx_id: EventTransactionId::Suppressed,
241 ..
242 }
243 | Event::CommitEvent {
244 event_tx_id: EventTransactionId::Suppressed,
245 ..
246 }
247 | Event::ObsoleteEvent {
248 event_tx_id: EventTransactionId::Suppressed,
249 ..
250 }
251 | Event::UnobsoleteEvent {
252 event_tx_id: EventTransactionId::Suppressed,
253 ..
254 }
255 | Event::WorkingCopySnapshot {
256 event_tx_id: EventTransactionId::Suppressed,
257 ..
258 } => return Err(()),
259
260 Event::RewriteEvent {
261 timestamp,
262 event_tx_id: EventTransactionId::Id(event_tx_id),
263 old_commit_oid,
264 new_commit_oid,
265 } => Row {
266 timestamp,
267 event_tx_id,
268 type_: String::from("rewrite"),
269 ref1: Some(old_commit_oid.into()),
270 ref2: Some(new_commit_oid.into()),
271 ref_name: None,
272 message: None,
273 },
274
275 Event::RefUpdateEvent {
276 timestamp,
277 event_tx_id: EventTransactionId::Id(event_tx_id),
278 ref_name,
279 old_oid,
280 new_oid,
281 message,
282 } => Row {
283 timestamp,
284 event_tx_id,
285 type_: String::from("ref-move"),
286 ref1: Some(old_oid.into()),
287 ref2: Some(new_oid.into()),
288 ref_name: Some(ref_name),
289 message,
290 },
291
292 Event::CommitEvent {
293 timestamp,
294 event_tx_id: EventTransactionId::Id(event_tx_id),
295 commit_oid,
296 } => Row {
297 timestamp,
298 event_tx_id,
299 type_: String::from("commit"),
300 ref1: Some(commit_oid.into()),
301 ref2: None,
302 ref_name: None,
303 message: None,
304 },
305
306 Event::ObsoleteEvent {
307 timestamp,
308 event_tx_id: EventTransactionId::Id(event_tx_id),
309 commit_oid,
310 } => Row {
311 timestamp,
312 event_tx_id,
313 type_: String::from("hide"), ref1: Some(commit_oid.into()),
315 ref2: None,
316 ref_name: None,
317 message: None,
318 },
319
320 Event::UnobsoleteEvent {
321 timestamp,
322 event_tx_id: EventTransactionId::Id(event_tx_id),
323 commit_oid,
324 } => Row {
325 timestamp,
326 event_tx_id,
327 type_: String::from("unhide"), ref1: Some(commit_oid.into()),
329 ref2: None,
330 ref_name: None,
331 message: None,
332 },
333
334 Event::WorkingCopySnapshot {
335 timestamp,
336 event_tx_id: EventTransactionId::Id(event_tx_id),
337 head_oid,
338 commit_oid,
339 ref_name,
340 } => Row {
341 timestamp,
342 event_tx_id,
343 type_: String::from("snapshot"),
344 ref1: Some(head_oid.to_string().into()),
345 ref2: Some(commit_oid.into()),
346 ref_name,
347 message: None,
348 },
349 };
350 Ok(row)
351 }
352}
353
354fn try_from_row_helper(row: &Row) -> Result<Event, eyre::Error> {
355 let row: Row = row.clone();
356 let Row {
357 timestamp,
358 event_tx_id,
359 type_,
360 ref_name,
361 ref1,
362 ref2,
363 message,
364 } = row;
365 let event_tx_id = EventTransactionId::Id(event_tx_id);
366
367 let get_oid =
368 |reference_name: &Option<ReferenceName>, oid_name: &str| -> eyre::Result<MaybeZeroOid> {
369 match reference_name {
370 Some(reference_name) => {
371 let oid: MaybeZeroOid = reference_name.as_str().parse()?;
372 Ok(oid)
373 }
374 None => Err(eyre::eyre!(
375 "OID '{}' was `None` for event type '{}'",
376 oid_name,
377 type_
378 )),
379 }
380 };
381
382 let event = match type_.as_str() {
383 "rewrite" => {
384 let old_commit_oid = get_oid(&ref1, "old commit OID")?;
385 let new_commit_oid = get_oid(&ref2, "new commit OID")?;
386 Event::RewriteEvent {
387 timestamp,
388 event_tx_id,
389 old_commit_oid,
390 new_commit_oid,
391 }
392 }
393
394 "ref-move" => {
395 let ref_name =
396 ref_name.ok_or_else(|| eyre::eyre!("ref-move event missing ref name"))?;
397 let ref1 = ref1.ok_or_else(|| eyre::eyre!("ref-move event missing ref1"))?;
398 let ref2 = ref2.ok_or_else(|| eyre::eyre!("ref-move event missing ref2"))?;
399 Event::RefUpdateEvent {
400 timestamp,
401 event_tx_id,
402 ref_name,
403 old_oid: ref1.as_str().parse()?,
404 new_oid: ref2.as_str().parse()?,
405 message,
406 }
407 }
408
409 "commit" => {
410 let commit_oid: NonZeroOid = get_oid(&ref1, "commit OID")?.try_into()?;
411 Event::CommitEvent {
412 timestamp,
413 event_tx_id,
414 commit_oid,
415 }
416 }
417
418 "hide" => {
419 let commit_oid: NonZeroOid = get_oid(&ref1, "commit OID")?.try_into()?;
420 Event::ObsoleteEvent {
421 timestamp,
422 event_tx_id,
423 commit_oid,
424 }
425 }
426
427 "unhide" => {
428 let commit_oid: NonZeroOid = get_oid(&ref1, "commit OID")?.try_into()?;
429 Event::UnobsoleteEvent {
430 timestamp,
431 event_tx_id,
432 commit_oid,
433 }
434 }
435
436 "snapshot" => {
437 let head_oid: MaybeZeroOid = get_oid(&ref1, "head OID")?;
438 let commit_oid: NonZeroOid = get_oid(&ref2, "commit OID")?.try_into()?;
439 Event::WorkingCopySnapshot {
440 timestamp,
441 event_tx_id,
442 head_oid,
443 commit_oid,
444 ref_name,
445 }
446 }
447
448 other => eyre::bail!("Unknown event type {}", other),
449 };
450 Ok(event)
451}
452
453impl TryFrom<Row> for Event {
454 type Error = eyre::Error;
455
456 fn try_from(row: Row) -> Result<Self, Self::Error> {
457 match try_from_row_helper(&row) {
458 Ok(event) => Ok(event),
459 Err(err) => {
460 error!(?row, "Could not convert row into event");
461 Err(err)
462 }
463 }
464 }
465}
466
467pub struct EventLogDb<'conn> {
469 conn: &'conn rusqlite::Connection,
470}
471
472impl std::fmt::Debug for EventLogDb<'_> {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 write!(f, "<EventLogDb path={:?}>", self.conn.path())
475 }
476}
477
478#[instrument]
479fn init_tables(conn: &rusqlite::Connection) -> eyre::Result<()> {
480 conn.execute(
481 "
482CREATE TABLE IF NOT EXISTS event_log (
483 timestamp REAL NOT NULL,
484 type TEXT NOT NULL,
485 event_tx_id INTEGER NOT NULL,
486 old_ref TEXT,
487 new_ref TEXT,
488 ref_name TEXT,
489 message TEXT
490)
491",
492 rusqlite::params![],
493 )
494 .wrap_err("Creating `event_log` table")?;
495
496 conn.execute(
497 "
498CREATE TABLE IF NOT EXISTS event_transactions (
499 timestamp REAL NOT NULL,
500
501 -- Set as `PRIMARY KEY` to have SQLite select a value automatically. Set as
502 -- `AUTOINCREMENT` to ensure that SQLite doesn't reuse the value later if a
503 -- row is deleted. (We don't plan to delete rows right now, but maybe
504 -- later?)
505 event_tx_id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
506
507 message TEXT
508)
509",
510 rusqlite::params![],
511 )
512 .wrap_err("Creating `event_transactions` table")?;
513
514 Ok(())
515}
516
517impl<'conn> EventLogDb<'conn> {
518 #[instrument]
520 pub fn new(conn: &'conn rusqlite::Connection) -> eyre::Result<Self> {
521 init_tables(conn)?;
522 Ok(EventLogDb { conn })
523 }
524
525 #[instrument]
530 pub fn add_events(&self, events: Vec<Event>) -> eyre::Result<()> {
531 let tx = self.conn.unchecked_transaction()?;
532 for event in events {
533 let row = match Row::try_from(event) {
534 Ok(row) => row,
535 Err(()) => continue,
536 };
537 let Row {
538 timestamp,
539 type_,
540 event_tx_id,
541 ref1,
542 ref2,
543 ref_name,
544 message,
545 } = row;
546
547 let ref1 = ref1.as_ref().map(|x| x.as_str());
548 let ref2 = ref2.as_ref().map(|x| x.as_str());
549 let ref_name = ref_name.as_ref().map(|x| x.as_str());
550 let message = message.as_ref().map(|x| x.as_str());
551
552 tx.execute(
553 "
554INSERT INTO event_log VALUES (
555 :timestamp,
556 :type,
557 :event_tx_id,
558 :old_ref,
559 :new_ref,
560 :ref_name,
561 :message
562)
563 ",
564 rusqlite::named_params! {
565 ":timestamp": timestamp,
566 ":type": &type_,
567 ":event_tx_id": event_tx_id,
568 ":old_ref": &ref1,
569 ":new_ref": &ref2,
570 ":ref_name": &ref_name,
571 ":message": &message,
572 },
573 )?;
574 }
575 tx.commit()?;
576 Ok(())
577 }
578
579 #[instrument]
583
584 pub fn get_events(&self) -> eyre::Result<Vec<Event>> {
585 let mut stmt = self.conn.prepare(
586 "
587SELECT timestamp, type, event_tx_id, old_ref, new_ref, ref_name, message
588FROM event_log
589ORDER BY rowid ASC
590",
591 )?;
592 let rows: rusqlite::Result<Vec<Row>> = stmt
593 .query_map(rusqlite::params![], |row| {
594 let timestamp: f64 = row.get("timestamp")?;
595 let event_tx_id: isize = row.get("event_tx_id")?;
596 let type_: String = row.get("type")?;
597 let ref_name: Option<String> = row.get("ref_name")?;
598 let old_ref: Option<String> = row.get("old_ref")?;
599 let new_ref: Option<String> = row.get("new_ref")?;
600 let message: Option<String> = row.get("message")?;
601
602 Ok(Row {
603 timestamp,
604 event_tx_id,
605 type_,
606 ref_name: ref_name.map(ReferenceName::from),
607 ref1: old_ref.map(ReferenceName::from),
608 ref2: new_ref.map(ReferenceName::from),
609 message: message.map(ReferenceName::from),
610 })
611 })?
612 .collect();
613 let rows = rows?;
614 rows.into_iter().map(Event::try_from).collect()
615 }
616
617 #[instrument]
618 fn make_transaction_id_inner(
619 &self,
620 now: SystemTime,
621 message: &str,
622 ) -> eyre::Result<EventTransactionId> {
623 if let Ok(transaction_id) = std::env::var(BRANCHLESS_TRANSACTION_ID_ENV_VAR) {
624 if let Ok(transaction_id) = transaction_id.parse::<EventTransactionId>() {
625 return Ok(transaction_id);
626 }
627 }
628
629 let tx = self.conn.unchecked_transaction()?;
630
631 let timestamp = now
632 .duration_since(SystemTime::UNIX_EPOCH)
633 .wrap_err("Calculating event transaction timestamp")?
634 .as_secs_f64();
635 self.conn
636 .execute(
637 "
638 INSERT INTO event_transactions
639 (timestamp, message)
640 VALUES
641 (:timestamp, :message)
642 ",
643 rusqlite::named_params! {
644 ":timestamp": timestamp,
645 ":message": message,
646 },
647 )
648 .wrap_err("Creating event transaction")?;
649
650 let event_tx_id: isize = self.conn.last_insert_rowid().try_into()?;
654 tx.commit()?;
655 Ok(EventTransactionId::Id(event_tx_id))
656 }
657
658 pub fn make_transaction_id(
661 &self,
662 now: SystemTime,
663 message: impl AsRef<str>,
664 ) -> eyre::Result<EventTransactionId> {
665 self.make_transaction_id_inner(now, message.as_ref())
666 }
667
668 pub fn get_transaction_message(&self, event_tx_id: EventTransactionId) -> eyre::Result<String> {
670 let event_tx_id = match event_tx_id {
671 EventTransactionId::Id(event_tx_id) => event_tx_id,
672 EventTransactionId::Suppressed => {
673 eyre::bail!("No message available for suppressed transaction ID")
674 }
675 };
676 let mut stmt = self.conn.prepare(
677 "
678SELECT message
679FROM event_transactions
680WHERE event_tx_id = :event_tx_id
681",
682 )?;
683 let result: String = stmt.query_row(
684 rusqlite::named_params![":event_tx_id": event_tx_id,],
685 |row| {
686 let message: String = row.get("message")?;
687 Ok(message)
688 },
689 )?;
690 Ok(result)
691 }
692}
693
694pub fn is_gc_ref(reference_name: &ReferenceName) -> bool {
700 reference_name.as_str().starts_with("refs/branchless/")
701}
702
703pub fn should_ignore_ref_updates(reference_name: &ReferenceName) -> bool {
707 if is_gc_ref(reference_name) {
708 return true;
709 }
710
711 matches!(
712 reference_name.as_str(),
713 "ORIG_HEAD"
714 | "CHERRY_PICK"
715 | "REBASE_HEAD"
716 | "CHERRY_PICK_HEAD"
717 | "AUTO_MERGE"
719 | "FETCH_HEAD"
720 )
721}
722
723#[derive(Debug)]
724enum EventClassification {
725 Show,
726 Hide,
727}
728
729#[derive(Debug)]
734pub enum CommitActivityStatus {
735 Active,
737
738 Inactive,
742
743 Obsolete,
747}
748
749#[derive(Debug)]
750struct EventInfo {
751 id: isize,
752 event: Event,
753 event_classification: EventClassification,
754}
755
756#[derive(Clone, Copy, Debug, PartialEq, Eq)]
767pub struct EventCursor {
768 event_id: isize,
769}
770
771pub struct EventReplayer {
773 id_counter: isize,
775
776 events: Vec<Event>,
778
779 main_branch_reference_name: ReferenceName,
781
782 commit_history: HashMap<NonZeroOid, Vec<EventInfo>>,
784
785 ref_locations: HashMap<ReferenceName, NonZeroOid>,
791}
792
793impl std::fmt::Debug for EventReplayer {
794 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
795 write!(
796 f,
797 "<EventReplayer events.len={:?} ref_locations.len={:?}>",
798 self.events.len(),
799 self.ref_locations.len()
800 )
801 }
802}
803
804impl EventReplayer {
805 fn new(main_branch_reference_name: ReferenceName) -> Self {
806 EventReplayer {
807 id_counter: 0,
808 events: vec![],
809 main_branch_reference_name,
810 commit_history: HashMap::new(),
811 ref_locations: HashMap::new(),
812 }
813 }
814
815 #[instrument]
822 pub fn from_event_log_db(
823 effects: &Effects,
824 repo: &Repo,
825 event_log_db: &EventLogDb,
826 ) -> eyre::Result<Self> {
827 let (_effects, _progress) = effects.start_operation(OperationType::ProcessEvents);
828
829 let main_branch_reference_name = repo.get_main_branch()?.get_reference_name()?;
830 let mut result = EventReplayer::new(main_branch_reference_name);
831 for event in event_log_db.get_events()? {
832 result.process_event(&event);
833 }
834 Ok(result)
835 }
836
837 pub fn process_event(&mut self, event: &Event) {
846 if let Event::RefUpdateEvent { ref_name, .. } = event {
848 if should_ignore_ref_updates(ref_name) {
849 return;
850 }
851 }
852
853 let event = match self.fix_event_git_v2_31(event.clone()) {
854 None => {
855 return;
856 }
857 Some(event) => {
858 self.events.push(event);
859 self.events.last().unwrap()
860 }
861 };
862 let id = self.id_counter;
863 self.id_counter += 1;
864
865 match &event {
866 Event::RewriteEvent {
867 timestamp: _,
868 event_tx_id: _,
869 old_commit_oid,
870 new_commit_oid,
871 } => {
872 if let MaybeZeroOid::NonZero(old_commit_oid) = old_commit_oid {
873 self.commit_history
874 .entry(*old_commit_oid)
875 .or_default()
876 .push(EventInfo {
877 id,
878 event: event.clone(),
879 event_classification: EventClassification::Hide,
880 });
881 }
882 if let MaybeZeroOid::NonZero(new_commit_oid) = new_commit_oid {
883 self.commit_history
884 .entry(*new_commit_oid)
885 .or_default()
886 .push(EventInfo {
887 id,
888 event: event.clone(),
889 event_classification: EventClassification::Show,
890 });
891 }
892 }
893
894 Event::RefUpdateEvent {
899 ref_name, new_oid, ..
900 } => match new_oid {
901 MaybeZeroOid::NonZero(new_oid) => {
902 self.ref_locations.insert(ref_name.clone(), *new_oid);
903 }
904 MaybeZeroOid::Zero => {
905 self.ref_locations.remove(ref_name);
906 }
907 },
908
909 Event::CommitEvent {
910 timestamp: _,
911 event_tx_id: _,
912 commit_oid,
913 } => self
914 .commit_history
915 .entry(*commit_oid)
916 .or_default()
917 .push(EventInfo {
918 id,
919 event: event.clone(),
920 event_classification: EventClassification::Show,
921 }),
922
923 Event::ObsoleteEvent {
924 timestamp: _,
925 event_tx_id: _,
926 commit_oid,
927 } => self
928 .commit_history
929 .entry(*commit_oid)
930 .or_default()
931 .push(EventInfo {
932 id,
933 event: event.clone(),
934 event_classification: EventClassification::Hide,
935 }),
936
937 Event::UnobsoleteEvent {
938 timestamp: _,
939 event_tx_id: _,
940 commit_oid,
941 } => self
942 .commit_history
943 .entry(*commit_oid)
944 .or_default()
945 .push(EventInfo {
946 id,
947 event: event.clone(),
948 event_classification: EventClassification::Show,
949 }),
950
951 Event::WorkingCopySnapshot { .. } => {
952 }
955 };
956 }
957
958 fn fix_event_git_v2_31(&self, event: Event) -> Option<Event> {
960 let event = match event {
961 Event::RefUpdateEvent {
966 timestamp,
967 event_tx_id,
968 ref_name,
969 old_oid: MaybeZeroOid::Zero,
970 new_oid: MaybeZeroOid::Zero,
971 message,
972 } => {
973 let old_oid: MaybeZeroOid = self.ref_locations.get(&ref_name).copied().into();
974 Event::RefUpdateEvent {
975 timestamp,
976 event_tx_id,
977 ref_name,
978 old_oid,
979 new_oid: MaybeZeroOid::Zero,
980 message,
981 }
982 }
983
984 _ => event,
985 };
986
987 match (event, self.events.last()) {
988 (
994 Event::RefUpdateEvent {
995 timestamp: _,
996 event_tx_id: _,
997 ref ref_name,
998 old_oid: _,
999 new_oid: MaybeZeroOid::Zero,
1000 ref message,
1001 },
1002 Some(Event::RefUpdateEvent {
1003 timestamp: _,
1004 event_tx_id: _,
1005 ref_name: last_ref_name,
1006 old_oid: _,
1007 new_oid: MaybeZeroOid::Zero,
1008 message: last_message,
1009 }),
1010 ) if ref_name == last_ref_name && message == last_message => None,
1011
1012 (event, _) => Some(event),
1013 }
1014 }
1015
1016 fn get_cursor_commit_history(&self, cursor: EventCursor, oid: NonZeroOid) -> Vec<&EventInfo> {
1017 match self.commit_history.get(&oid) {
1018 None => vec![],
1019 Some(history) => history
1020 .iter()
1021 .filter(|event_info| event_info.id < cursor.event_id)
1022 .collect(),
1023 }
1024 }
1025
1026 pub fn get_cursor_commit_activity_status(
1029 &self,
1030 cursor: EventCursor,
1031 oid: NonZeroOid,
1032 ) -> CommitActivityStatus {
1033 let history = self.get_cursor_commit_history(cursor, oid);
1034 match history.last() {
1035 Some(EventInfo {
1036 id: _,
1037 event: _,
1038 event_classification: EventClassification::Show,
1039 }) => CommitActivityStatus::Active,
1040
1041 Some(EventInfo {
1042 id: _,
1043 event: _,
1044 event_classification: EventClassification::Hide,
1045 }) => CommitActivityStatus::Obsolete,
1046
1047 None => CommitActivityStatus::Inactive,
1048 }
1049 }
1050
1051 pub fn get_cursor_commit_latest_event(
1060 &self,
1061 cursor: EventCursor,
1062 oid: NonZeroOid,
1063 ) -> Option<&Event> {
1064 let history = self.get_cursor_commit_history(cursor, oid);
1065 let event_info = *history.last()?;
1066 Some(&event_info.event)
1067 }
1068
1069 pub fn get_cursor_oids(&self, cursor: EventCursor) -> HashSet<NonZeroOid> {
1072 self.commit_history
1073 .iter()
1074 .filter_map(|(oid, history)| {
1075 if history.iter().any(|event| event.id < cursor.event_id) {
1076 Some(*oid)
1077 } else {
1078 None
1079 }
1080 })
1081 .collect()
1082 }
1083
1084 pub fn make_default_cursor(&self) -> EventCursor {
1086 self.make_cursor(self.events.len().try_into().unwrap())
1087 }
1088
1089 pub fn make_cursor(&self, event_id: isize) -> EventCursor {
1094 let event_id = if event_id < 0 { 0 } else { event_id };
1095 let num_events: isize = self.events.len().try_into().unwrap();
1096 let event_id = if event_id > num_events {
1097 num_events
1098 } else {
1099 event_id
1100 };
1101 EventCursor { event_id }
1102 }
1103
1104 pub fn advance_cursor(&self, cursor: EventCursor, num_events: isize) -> EventCursor {
1111 self.make_cursor(cursor.event_id + num_events)
1112 }
1113
1114 fn get_event_tx_id_before_cursor(&self, cursor: EventCursor) -> Option<EventTransactionId> {
1115 self.get_event_before_cursor(cursor)
1116 .map(|(_event_id, event)| event.get_event_tx_id())
1117 }
1118
1119 fn snap_to_transaction_boundary(&self, cursor: EventCursor) -> EventCursor {
1123 let next_cursor = self.advance_cursor(cursor, 1);
1124 if cursor == next_cursor {
1125 return cursor;
1126 }
1127 let current_tx_id = self.get_event_tx_id_before_cursor(cursor);
1128 let next_tx_id = self.get_event_tx_id_before_cursor(next_cursor);
1129 if current_tx_id == next_tx_id {
1130 self.snap_to_transaction_boundary(next_cursor)
1131 } else {
1132 cursor
1133 }
1134 }
1135
1136 fn advance_cursor_by_transaction_helper(
1137 &self,
1138 cursor: EventCursor,
1139 num_transactions: isize,
1140 ) -> EventCursor {
1141 match num_transactions.cmp(&0) {
1142 Ordering::Equal => self.snap_to_transaction_boundary(cursor),
1143 Ordering::Greater => {
1144 let next_cursor = self.advance_cursor(cursor, 1);
1145 if cursor == next_cursor {
1146 return next_cursor;
1147 }
1148 let current_tx_id = self.get_event_tx_id_before_cursor(cursor);
1149 let next_tx_id = self.get_event_tx_id_before_cursor(next_cursor);
1150 let num_transactions = if current_tx_id == next_tx_id {
1151 num_transactions
1152 } else {
1153 num_transactions - 1
1154 };
1155 self.advance_cursor_by_transaction_helper(next_cursor, num_transactions)
1156 }
1157 Ordering::Less => {
1158 let prev_cursor = self.advance_cursor(cursor, -1);
1159 if cursor == prev_cursor {
1160 return prev_cursor;
1161 }
1162 let current_tx_id = self.get_event_tx_id_before_cursor(cursor);
1163 let prev_tx_id = self.get_event_tx_id_before_cursor(prev_cursor);
1164 let num_transactions = if current_tx_id == prev_tx_id {
1165 num_transactions
1166 } else {
1167 num_transactions + 1
1168 };
1169 self.advance_cursor_by_transaction_helper(prev_cursor, num_transactions)
1170 }
1171 }
1172 }
1173
1174 pub fn advance_cursor_by_transaction(
1180 &self,
1181 cursor: EventCursor,
1182 num_transactions: isize,
1183 ) -> EventCursor {
1184 if self.events.is_empty() {
1185 cursor
1186 } else {
1187 let cursor = self.snap_to_transaction_boundary(cursor);
1188 self.advance_cursor_by_transaction_helper(cursor, num_transactions)
1189 }
1190 }
1191
1192 fn get_cursor_head_oid(&self, cursor: EventCursor) -> Option<NonZeroOid> {
1197 let cursor_event_id: usize = cursor.event_id.try_into().unwrap();
1198 self.events[0..cursor_event_id]
1199 .iter()
1200 .rev()
1201 .find_map(|event| {
1202 match &event {
1203 Event::RefUpdateEvent {
1204 ref_name,
1205 new_oid: MaybeZeroOid::NonZero(new_oid),
1206 ..
1207 } if ref_name.as_str() == "HEAD" => Some(*new_oid),
1208 Event::RefUpdateEvent { .. } => None,
1209
1210 Event::CommitEvent { commit_oid, .. } => Some(*commit_oid),
1214
1215 Event::WorkingCopySnapshot {
1216 head_oid: MaybeZeroOid::NonZero(head_oid),
1217 ..
1218 } => Some(*head_oid),
1219 Event::WorkingCopySnapshot {
1220 head_oid: MaybeZeroOid::Zero,
1221 ..
1222 } => None,
1223
1224 Event::RewriteEvent { .. }
1225 | Event::ObsoleteEvent { .. }
1226 | Event::UnobsoleteEvent { .. } => None,
1227 }
1228 })
1229 }
1230
1231 fn get_cursor_branch_oid(
1232 &self,
1233 cursor: EventCursor,
1234 reference_name: &ReferenceName,
1235 ) -> eyre::Result<Option<NonZeroOid>> {
1236 let cursor_event_id: usize = cursor.event_id.try_into().unwrap();
1237 let oid = self.events[0..cursor_event_id]
1238 .iter()
1239 .rev()
1240 .find_map(|event| match &event {
1241 Event::RefUpdateEvent {
1242 ref_name,
1243 new_oid: MaybeZeroOid::NonZero(new_oid),
1244 ..
1245 } if ref_name == reference_name => Some(*new_oid),
1246 _ => None,
1247 });
1248 Ok(oid)
1249 }
1250
1251 #[instrument]
1264 fn get_cursor_main_branch_oid(
1265 &self,
1266 cursor: EventCursor,
1267 repo: &Repo,
1268 ) -> eyre::Result<NonZeroOid> {
1269 let main_branch_reference_name = repo.get_main_branch()?.get_reference_name()?;
1270 let main_branch_oid = self.get_cursor_branch_oid(cursor, &main_branch_reference_name)?;
1271 match main_branch_oid {
1272 Some(main_branch_oid) => Ok(main_branch_oid),
1273 None => {
1274 repo.get_main_branch_oid()
1277 }
1278 }
1279 }
1280
1281 fn get_cursor_branch_oid_to_names(
1292 &self,
1293 cursor: EventCursor,
1294 repo: &Repo,
1295 ) -> eyre::Result<HashMap<NonZeroOid, HashSet<ReferenceName>>> {
1296 let mut ref_name_to_oid: HashMap<&ReferenceName, NonZeroOid> = HashMap::new();
1297 let cursor_event_id: usize = cursor.event_id.try_into().unwrap();
1298 for event in self.events[..cursor_event_id].iter() {
1299 match event {
1300 Event::RefUpdateEvent {
1301 new_oid: MaybeZeroOid::NonZero(new_oid),
1302 ref_name,
1303 ..
1304 } => {
1305 ref_name_to_oid.insert(ref_name, *new_oid);
1306 }
1307 Event::RefUpdateEvent {
1308 new_oid: MaybeZeroOid::Zero,
1309 ref_name,
1310 ..
1311 } => {
1312 ref_name_to_oid.remove(ref_name);
1313 }
1314 _ => {}
1315 }
1316 }
1317
1318 let mut result: HashMap<NonZeroOid, HashSet<ReferenceName>> = HashMap::new();
1319 for (ref_name, ref_oid) in ref_name_to_oid.iter() {
1320 if let CategorizedReferenceName::LocalBranch { .. } =
1321 CategorizedReferenceName::new(ref_name)
1322 {
1323 result
1324 .entry(*ref_oid)
1325 .or_default()
1326 .insert((*ref_name).clone());
1327 }
1328 }
1329
1330 let main_branch_oid = self.get_cursor_main_branch_oid(cursor, repo)?;
1331 result
1332 .entry(main_branch_oid)
1333 .or_default()
1334 .insert(self.main_branch_reference_name.clone());
1335 Ok(result)
1336 }
1337
1338 pub fn get_references_snapshot(
1340 &self,
1341 repo: &Repo,
1342 cursor: EventCursor,
1343 ) -> eyre::Result<RepoReferencesSnapshot> {
1344 let head_oid = self.get_cursor_head_oid(cursor);
1345 let main_branch_oid = self.get_cursor_main_branch_oid(cursor, repo)?;
1346 let branch_oid_to_names = self.get_cursor_branch_oid_to_names(cursor, repo)?;
1347 Ok(RepoReferencesSnapshot {
1348 head_oid,
1349 main_branch_oid,
1350 branch_oid_to_names,
1351 })
1352 }
1353
1354 pub fn get_event_before_cursor(&self, cursor: EventCursor) -> Option<(isize, &Event)> {
1359 if cursor.event_id == 0 {
1360 None
1361 } else {
1362 let previous_cursor_event_id: usize = (cursor.event_id - 1).try_into().unwrap();
1363 Some((cursor.event_id, &self.events[previous_cursor_event_id]))
1364 }
1365 }
1366
1367 pub fn get_tx_events_before_cursor(&self, cursor: EventCursor) -> Option<(isize, &[Event])> {
1374 let prev_tx_cursor = self.advance_cursor_by_transaction(cursor, -1);
1375 let EventCursor {
1376 event_id: prev_event_id,
1377 } = prev_tx_cursor;
1378 let EventCursor {
1379 event_id: curr_event_id,
1380 } = cursor;
1381 let tx_events =
1382 &self.events[prev_event_id.try_into().unwrap()..curr_event_id.try_into().unwrap()];
1383 match tx_events {
1384 [] => None,
1385 events => Some((prev_event_id + 1, events)),
1386 }
1387 }
1388
1389 pub fn get_events_since_cursor(&self, cursor: EventCursor) -> &[Event] {
1394 let cursor_event_id: usize = cursor.event_id.try_into().unwrap();
1395 &self.events[cursor_event_id..]
1396 }
1397}
1398
1399pub mod testing {
1401 use super::*;
1402
1403 pub fn new_event_replayer(main_branch_reference_name: ReferenceName) -> EventReplayer {
1405 EventReplayer::new(main_branch_reference_name)
1406 }
1407
1408 pub fn new_event_transaction_id(id: isize) -> EventTransactionId {
1410 EventTransactionId::Id(id)
1411 }
1412
1413 pub fn new_event_cursor(event_id: isize) -> EventCursor {
1415 EventCursor { event_id }
1416 }
1417
1418 pub fn redact_event_timestamp(mut event: Event) -> Event {
1420 match event {
1421 Event::RewriteEvent {
1422 ref mut timestamp, ..
1423 }
1424 | Event::RefUpdateEvent {
1425 ref mut timestamp, ..
1426 }
1427 | Event::CommitEvent {
1428 ref mut timestamp, ..
1429 }
1430 | Event::ObsoleteEvent {
1431 ref mut timestamp, ..
1432 }
1433 | Event::UnobsoleteEvent {
1434 ref mut timestamp, ..
1435 }
1436 | Event::WorkingCopySnapshot {
1437 ref mut timestamp, ..
1438 } => *timestamp = 0.0,
1439 }
1440 event
1441 }
1442
1443 pub fn get_event_replayer_events(event_replayer: &EventReplayer) -> &Vec<Event> {
1445 &event_replayer.events
1446 }
1447}