src/Pure/Admin/build_log.scala
changeset 78991 ae2f5fd0bb5d
parent 78985 24e686fe043e
child 78992 bd250213c262
--- a/src/Pure/Admin/build_log.scala	Sat Nov 18 21:14:34 2023 +0100
+++ b/src/Pure/Admin/build_log.scala	Sun Nov 19 12:46:41 2023 +0100
@@ -1190,6 +1190,88 @@
   }
 
 
+
+  /** build history **/
+
+  object History {
+    sealed case class Entry(
+      known: Boolean,
+      isabelle_version: String,
+      afp_version: Option[String],
+      pull_date: Date
+    ) {
+      def unknown: Boolean = !known
+      def versions: (String, Option[String]) = (isabelle_version, afp_version)
+
+      def known_versions(rev: String, afp_rev: Option[String]): Boolean =
+        known && rev.nonEmpty && isabelle_version == rev &&
+        (afp_rev.isEmpty || afp_rev.get.nonEmpty && afp_version == afp_rev)
+    }
+
+    object Run {
+      val empty: Run = Run()
+      def longest(runs: List[Run]): Run = runs.foldLeft(empty)(_ max _)
+    }
+
+    sealed case class Run(entries: List[Entry] = Nil) {
+      def is_empty: Boolean = entries.isEmpty
+      val length: Int = entries.length
+      def max(other: Run): Run = if (length >= other.length) this else other
+      def median: Option[Entry] = if (is_empty) None else Some(entries(length / 2))
+
+      override def toString: String = {
+        val s = if (is_empty) "" else "length = " + length + ", median = " + median.get.pull_date
+        "Build_Log.History.Run(" + s + ")"
+      }
+    }
+
+    def retrieve(
+      db: SQL.Database,
+      days: Int,
+      rev: String,
+      afp_rev: Option[String],
+      sql: PostgreSQL.Source
+    ): History = {
+      val afp = afp_rev.isDefined
+
+      val entries =
+        db.execute_query_statement(
+          private_data.select_recent_versions(
+            days = days, rev = rev, afp_rev = afp_rev, sql = SQL.where(sql)),
+          List.from[Entry],
+          { res =>
+            val known = res.bool(private_data.known)
+            val isabelle_version = res.string(Prop.isabelle_version)
+            val afp_version = if (afp) proper_string(res.string(Prop.afp_version)) else None
+            val pull_date = res.date(private_data.pull_date(afp))
+            Entry(known, isabelle_version, afp_version, pull_date)
+          })
+
+      new History(entries)
+    }
+  }
+
+  final class History private(val entries: List[History.Entry]) {
+    override def toString: String = "Build_Log.History(" + entries.length + ")"
+
+    def unknown_runs(
+      pre: History.Entry => Boolean = _ => true,
+      post: History.Run => Boolean = _ => true
+    ): List[History.Run] = {
+      var rest = entries.filter(pre)
+      val result = new mutable.ListBuffer[History.Run]
+      while (rest.nonEmpty) {
+        val (a, b) = Library.take_prefix[History.Entry](_.unknown, rest.dropWhile(_.known))
+        val run = History.Run(a)
+        if (!run.is_empty && post(run)) result += run
+        rest = b
+      }
+      result.toList
+    }
+  }
+
+
+
   /** maintain build_log database **/
 
   def build_log_database(options: Options, logs: List[Path],