Class: Carson::Ledger

Inherits:
Object
  • Object
show all
Defined in:
lib/carson/ledger.rb

Constant Summary collapse

UNSET =
Object.new
ACTIVE_DELIVERY_STATES =
Delivery::ACTIVE_STATES
SQLITE_HEADER =
"SQLite format 3\0".b.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:) ⇒ Ledger

Returns a new instance of Ledger.



12
13
14
15
16
# File 'lib/carson/ledger.rb', line 12

def initialize( path: )
	@path = File.expand_path( path )
	FileUtils.mkdir_p( File.dirname( @path ) )
	migrate_legacy_state_if_needed!
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



18
19
20
# File 'lib/carson/ledger.rb', line 18

def path
  @path
end

Instance Method Details

#active_deliveries(repo_path:) ⇒ Object

Lists active deliveries for a repository in creation order.



101
102
103
104
105
106
107
108
109
# File 'lib/carson/ledger.rb', line 101

def active_deliveries( repo_path: )
	state = load_state
	repo_paths = repo_identity_paths( repo_path: repo_path )

	state[ "deliveries" ]
		.select { |_key, data| repo_paths.include?( data[ "repo_path" ] ) && ACTIVE_DELIVERY_STATES.include?( data[ "status" ] ) }
		.sort_by { |key, data| [ delivery_sequence( data: data ), key ] }
		.map { |key, data| build_delivery( key: key, data: data ) }
end

#active_delivery(repo_path:, branch_name:) ⇒ Object

Looks up the active delivery for a branch, if one exists.



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/carson/ledger.rb', line 68

def active_delivery( repo_path:, branch_name: )
	state = load_state
	repo_paths = repo_identity_paths( repo_path: repo_path )

	candidates = state[ "deliveries" ].select do |_key, data|
		repo_paths.include?( data[ "repo_path" ] ) &&
			data[ "branch_name" ] == branch_name &&
			ACTIVE_DELIVERY_STATES.include?( data[ "status" ] )
	end

	return nil if candidates.empty?

	key, data = candidates.max_by { |k, d| [ d[ "updated_at" ].to_s, delivery_sequence( data: d ), k ] }
	build_delivery( key: key, data: data )
end

#integrated_deliveries(repo_path:) ⇒ Object

Lists integrated deliveries that still retain a worktree path.



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/carson/ledger.rb', line 112

def integrated_deliveries( repo_path: )
	state = load_state
	repo_paths = repo_identity_paths( repo_path: repo_path )

	state[ "deliveries" ]
		.select do |_key, data|
			repo_paths.include?( data[ "repo_path" ] ) &&
				data[ "status" ] == "integrated" &&
				!data[ "worktree_path" ].to_s.strip.empty?
		end
		.sort_by { |key, data| [ data[ "integrated_at" ].to_s, delivery_sequence( data: data ), key ] }
		.map { |key, data| build_delivery( key: key, data: data ) }
end

#latest_delivery(repo_path:, branch_name:) ⇒ Object

Looks up the newest delivery for a branch across active and terminal states.



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/carson/ledger.rb', line 85

def latest_delivery( repo_path:, branch_name: )
	state = load_state
	repo_paths = repo_identity_paths( repo_path: repo_path )

	candidates = state[ "deliveries" ].select do |_key, data|
		repo_paths.include?( data[ "repo_path" ] ) &&
			data[ "branch_name" ] == branch_name
	end

	return nil if candidates.empty?

	key, data = candidates.max_by { |k, d| [ d[ "updated_at" ].to_s, delivery_sequence( data: d ), k ] }
	build_delivery( key: key, data: data )
end

#record_revision(delivery:, cause:, provider:, status:, summary:) ⇒ Object

Records one revision cycle against a delivery.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/carson/ledger.rb', line 164

def record_revision( delivery:, cause:, provider:, status:, summary: )
	timestamp = now_utc

	with_state do |state|
		_key, data = resolve_delivery_entry( state: state, delivery: delivery )

		revisions = data[ "revisions" ] ||= []
		next_number = ( revisions.map { |r| r[ "number" ].to_i }.max || 0 ) + 1
		finished = %w[completed failed stalled].include?( status ) ? timestamp : nil

		revision_data = {
			"number" => next_number,
			"cause" => cause,
			"provider" => provider,
			"status" => status,
			"started_at" => timestamp,
			"finished_at" => finished,
			"summary" => summary
		}
		revisions << revision_data
		data[ "updated_at" ] = timestamp

		build_revision( data: revision_data )
	end
end

#revisions_for_delivery(delivery:) ⇒ Object

Returns revisions for a delivery in ascending order.



191
192
193
# File 'lib/carson/ledger.rb', line 191

def revisions_for_delivery( delivery: )
	delivery.revisions.sort_by( &:number )
end

#update_delivery(delivery:, status: UNSET, pr_number: UNSET, pr_url: UNSET, pull_request_state: UNSET, pull_request_draft: UNSET, pull_request_merged_at: UNSET, merge_proof: UNSET, cause: UNSET, summary: UNSET, worktree_path: UNSET, integrated_at: UNSET, superseded_at: UNSET) ⇒ Object

Updates a delivery record in place.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/carson/ledger.rb', line 127

def update_delivery(
	delivery:,
	status: UNSET,
	pr_number: UNSET,
	pr_url: UNSET,
	pull_request_state: UNSET,
	pull_request_draft: UNSET,
	pull_request_merged_at: UNSET,
	merge_proof: UNSET,
	cause: UNSET,
	summary: UNSET,
	worktree_path: UNSET,
	integrated_at: UNSET,
	superseded_at: UNSET
)
	with_state do |state|
		key, data = resolve_delivery_entry( state: state, delivery: delivery )

		data[ "status" ] = status unless status.equal?( UNSET )
		data[ "pr_number" ] = pr_number unless pr_number.equal?( UNSET )
		data[ "pr_url" ] = pr_url unless pr_url.equal?( UNSET )
		data[ "pull_request_state" ] = pull_request_state unless pull_request_state.equal?( UNSET )
		data[ "pull_request_draft" ] = pull_request_draft unless pull_request_draft.equal?( UNSET )
		data[ "pull_request_merged_at" ] = pull_request_merged_at unless pull_request_merged_at.equal?( UNSET )
		data[ "merge_proof" ] = serialise_merge_proof( merge_proof: merge_proof ) unless merge_proof.equal?( UNSET )
		data[ "cause" ] = cause unless cause.equal?( UNSET )
		data[ "summary" ] = summary unless summary.equal?( UNSET )
		data[ "worktree_path" ] = worktree_path unless worktree_path.equal?( UNSET )
		data[ "integrated_at" ] = integrated_at unless integrated_at.equal?( UNSET )
		data[ "superseded_at" ] = superseded_at unless superseded_at.equal?( UNSET )
		data[ "updated_at" ] = now_utc

		build_delivery( key: key, data: data, repository: delivery.repository )
	end
end

#upsert_delivery(repository:, branch_name:, head:, worktree_path:, pr_number:, pr_url:, status:, summary:, cause:, pull_request_state: nil, pull_request_draft: nil, pull_request_merged_at: nil, merge_proof: nil) ⇒ Object

Creates or refreshes a delivery for the same branch head.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/carson/ledger.rb', line 21

def upsert_delivery(
	repository:, branch_name:, head:, worktree_path:, pr_number:, pr_url:, status:, summary:, cause:,
	pull_request_state: nil, pull_request_draft: nil, pull_request_merged_at: nil, merge_proof: nil
)
	timestamp = now_utc

	with_state do |state|
		repo_paths = repo_identity_paths( repo_path: repository.path )
		matches = matching_deliveries(
			state: state,
			repo_paths: repo_paths,
			branch_name: branch_name,
			head: head
		)
		key = delivery_key( repo_path: repository.path, branch_name: branch_name, head: head )
		sequence = matches.map { |_existing_key, data| delivery_sequence( data: data ) }.compact.min
		created_at = matches.map { |_existing_key, data| data.fetch( "created_at", "" ).to_s }.reject( &:empty? ).min || timestamp
		revisions = merged_revisions( entries: matches )
		matches.each { |existing_key, _data| state[ "deliveries" ].delete( existing_key ) }

		supersede_branch!( state: state, repo_path: repository.path, branch_name: branch_name, timestamp: timestamp )
		state[ "deliveries" ][ key ] = {
			"sequence" => sequence || next_delivery_sequence!( state: state ),
			"repo_path" => repository.path,
			"branch_name" => branch_name,
			"head" => head,
			"worktree_path" => worktree_path,
			"status" => status,
			"pr_number" => pr_number,
			"pr_url" => pr_url,
			"pull_request_state" => pull_request_state,
			"pull_request_draft" => pull_request_draft,
			"pull_request_merged_at" => pull_request_merged_at,
			"merge_proof" => serialise_merge_proof( merge_proof: merge_proof ),
			"cause" => cause,
			"summary" => summary,
			"created_at" => created_at,
			"updated_at" => timestamp,
			"integrated_at" => nil,
			"superseded_at" => nil,
			"revisions" => revisions
		}
		build_delivery( key: key, data: state[ "deliveries" ][ key ], repository: repository )
	end
end