fun_ci 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c32c8feba3da028e17991565e2221423f97cab25535eb940667306b8f21205b
4
- data.tar.gz: 83ec5dc09376e04feb8d08e7d863c6a574c2814754e9e57f3745d8719d4fb01e
3
+ metadata.gz: c30d5baf69419b87d2fd79cd45ac0f0f38de132b8d75e97cd7da0d0deef0c7fc
4
+ data.tar.gz: 504335aa06c6c3f3a2b2e067349bfaac1a90402843a206a7da76bbf51afd34ee
5
5
  SHA512:
6
- metadata.gz: 352c02313f711498642a148a7639e559ab4c1dbbd92c12f3c83f6a1cefd78263545c617428c0293ab5c2da4e6f2b70984f6bbe8db402538c84bb3d243cce6314
7
- data.tar.gz: 6d873b080a2d5417b046aab0516cb116a92ae705b72207ab634b14196d8d18856540411f864bd424c1eb995d34018b89a07e7059eef47f1eeacdf4a5851b2088
6
+ metadata.gz: 8a2e2817e52d36a4cd27743727b25c98b1063396bb64777fe3fd1d3d4323765073433dbe5366b622092bdabca0944fb62818ac64ebd441b468467040c84aa9e6
7
+ data.tar.gz: c04951b18e20491ce6388c5a51cbda72ee4417fc44fb21bb7484d0ad82dd3c137a6e3657c313281a6c6ebb40da185972ad7a57e019a0ee46004db9caae59dbb0
data/CHANGELOG.md CHANGED
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.0.1] - 2026-09-29
11
+
12
+ ### Fixed
13
+ - `fun-ci console` could show a blank screen and stay that way. When a new
14
+ commit cancelled a run in the same second one of its stages started, the
15
+ stage was recorded as ending before it began. The renderer rejected the
16
+ negative duration and, with it, every board Ruby sent, so nothing was drawn
17
+ while that run was listed; the only sign was a line per second in
18
+ `.fun-ci/console.log`. Ends are now recorded to the millisecond, and a stage
19
+ already recorded that way reads as taking no time, so an existing database
20
+ needs no repair. `fun-ci status`, `why` and `events` no longer report a
21
+ negative time for such a stage either.
22
+ - A commit made on a detached HEAD, as every commit a rebase replays is, was
23
+ recorded with no branch name, so `fun-ci status` ended in "on" and the
24
+ console showed a gap where the branch goes. Such runs are now on the branch
25
+ `detached`, whether a hook or `fun-ci wait` started them, and runs already
26
+ recorded without a name are renamed when fun-ci next opens its database.
27
+
10
28
  ## [2.0.0] - 2026-09-29
11
29
 
12
30
  fun-ci 2.0 has two ways in. People watch a new console, drawn by a Rust
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Gem Version](https://badge.fury.io/rb/fun_ci.svg)](https://badge.fury.io/rb/fun_ci)
2
+
1
3
  # Fun-CI
2
4
 
3
5
  Opinionated local CI that checks your code before it leaves your machine. Every commit runs lint, build, a fast suite and a slow suite on your own computer, each held to a strict time budget, so the feedback stays fast.
@@ -16,8 +16,9 @@ module FunCi
16
16
  def subject(sha) = git("log", "-1", "--format=%s", sha).to_s
17
17
  def toplevel = File.realpath(git("rev-parse", "--show-toplevel"))
18
18
 
19
- # The branch checked out, as the git hooks name it; "unknown" when HEAD is detached.
20
- def branch = git("branch", "--show-current").to_s.then { |name| name.empty? ? "unknown" : name }
19
+ # The branch checked out, as the git hooks name it: "" when HEAD is
20
+ # detached, which trigger names (Pipeline::Commit).
21
+ def branch = git("branch", "--show-current").to_s
21
22
 
22
23
  private
23
24
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
- require "time"
5
4
  require_relative "../evidence/document"
5
+ require_relative "../persistence/stage_job"
6
6
 
7
7
  module FunCi
8
8
  module Agent
@@ -62,9 +62,7 @@ module FunCi
62
62
  end
63
63
 
64
64
  def self.seconds(job)
65
- return nil unless job[:started_at] && job[:completed_at]
66
-
67
- (Time.parse(job[:completed_at]) - Time.parse(job[:started_at])).round(1)
65
+ Persistence::StageJob.elapsed_duration(job)&.round(1)
68
66
  end
69
67
  private_class_method :kept, :seconds
70
68
  end
@@ -42,12 +42,12 @@ module FunCi
42
42
  # Records the slow stage failed, unless its result was recorded meanwhile.
43
43
  def self.slow_suite_died(db, job_id)
44
44
  db.execute("UPDATE stage_jobs SET status = 'failed', completed_at = ? WHERE id = ? AND status = 'running'",
45
- [Time.now.utc.iso8601, job_id])
45
+ [Time.now.utc.iso8601(3), job_id])
46
46
  end
47
47
 
48
48
  def self.cancelled(db, run)
49
49
  db.execute("UPDATE stage_jobs SET status = 'cancelled', completed_at = ? " \
50
- "WHERE pipeline_run_id = ? AND #{ACTIVE}", [Time.now.utc.iso8601, run.id])
50
+ "WHERE pipeline_run_id = ? AND #{ACTIVE}", [Time.now.utc.iso8601(3), run.id])
51
51
  PipelineRun.update_status(db, run.id, "cancelled")
52
52
  end
53
53
 
@@ -50,9 +50,13 @@ module FunCi
50
50
  with_setup_lock(db.filename("main")) do
51
51
  [PIPELINE_RUNS_TABLE, STAGE_JOBS_TABLE].each { |table| db.execute(table) }
52
52
  ADDED_COLUMNS.each { |table, column, type| add_column_if_missing(db, table, column, type) }
53
+ db.execute(NAME_DETACHED)
53
54
  end
54
55
  end
55
56
 
57
+ # Runs started on a detached HEAD before 2.0.1 were kept with no branch name.
58
+ NAME_DETACHED = "UPDATE pipeline_runs SET branch = 'detached' WHERE branch = ''"
59
+
56
60
  def self.with_setup_lock(db_path)
57
61
  File.open("#{db_path}.setup-lock", File::RDWR | File::CREAT, 0o644) do |lock|
58
62
  lock.flock(File::LOCK_EX)
@@ -76,10 +76,12 @@ module FunCi
76
76
  [new_status, Time.now.utc.iso8601(3), id])
77
77
  end
78
78
 
79
+ # Never less than nothing: rows kept before ends were recorded to the
80
+ # millisecond can end in the second they started, before their start.
79
81
  def self.elapsed_duration(job)
80
82
  return nil unless job[:started_at] && job[:completed_at]
81
83
 
82
- Time.parse(job[:completed_at]) - Time.parse(job[:started_at])
84
+ [Time.parse(job[:completed_at]) - Time.parse(job[:started_at]), 0].max
83
85
  end
84
86
 
85
87
  def self.row_to_hash(row)
@@ -14,6 +14,14 @@ module FunCi
14
14
 
15
15
  Commit = Data.define(:sha, :branch)
16
16
 
17
+ # On a detached HEAD git names no branch, and the hooks pass "": the run
18
+ # is kept, shown and cancelled by others on the branch "detached".
19
+ class Commit
20
+ DETACHED = "detached"
21
+
22
+ def initialize(sha:, branch:) = super(sha: sha, branch: branch.empty? ? DETACHED : branch)
23
+ end
24
+
17
25
  Io = Data.define(:stdout, :stderr) do
18
26
  def initialize(stdout: $stdout, stderr: $stderr) = super
19
27
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Loaded on its own by the gemspec, so it must not require anything.
4
4
  module FunCi
5
- VERSION = "2.0.0"
5
+ VERSION = "2.0.1"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fun_ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Thyge Madsen