bitfab 0.30.0 → 0.30.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 +4 -4
- data/lib/bitfab/replay.rb +186 -0
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a3e03384934c51dd57e27ac1213dca8f88b41ead5a0980c04bb10ba0ad464d8
|
|
4
|
+
data.tar.gz: da9c5574b00bd66ba481a78e0f1e0ce4f8e9336b35a7b2e9013498b475622237
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 97f2755495103e92c94258f1763a1066bfc5a1944168e10dec1ad1e0bd24a58ca916791122cf3673f52326ca0d5b89b7ca8324071d40b6c408f3153167f6c95c
|
|
7
|
+
data.tar.gz: 61ba2d4b92cfa2c2665753319c6acf67dda2d48a547088d1ab47b2e9006835fe869185f3861d4e378f5c8e76167009f2580f385b5f6416467a24ae5b9dce8242
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "json"
|
|
5
|
+
require "open3"
|
|
6
|
+
require "timeout"
|
|
5
7
|
|
|
6
8
|
require_relative "constants"
|
|
7
9
|
require_relative "mock_override"
|
|
@@ -179,6 +181,19 @@ module Bitfab
|
|
|
179
181
|
|
|
180
182
|
include_db_branch_lease = !environment.nil?
|
|
181
183
|
|
|
184
|
+
# An explicit code change always wins; only when the caller passed neither
|
|
185
|
+
# field do we fall back to the payload the replay wrapper captured from git
|
|
186
|
+
# and handed over via BITFAB_CODE_CHANGE_PATH. This is what makes an
|
|
187
|
+
# experiment show its diff even when the run happened outside the
|
|
188
|
+
# assistant's make-change rails.
|
|
189
|
+
if code_change_description.nil? && code_change_files.nil?
|
|
190
|
+
captured = resolve_auto_code_change(name)
|
|
191
|
+
unless captured.nil?
|
|
192
|
+
code_change_description = captured[0]
|
|
193
|
+
code_change_files = captured[1]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
182
197
|
replay_data = http_client.start_replay(
|
|
183
198
|
trace_function_key,
|
|
184
199
|
effective_limit,
|
|
@@ -295,6 +310,177 @@ module Bitfab
|
|
|
295
310
|
result
|
|
296
311
|
end
|
|
297
312
|
|
|
313
|
+
# Read the code-change payload the replay wrapper captured and pointed us at
|
|
314
|
+
# via BITFAB_CODE_CHANGE_PATH. Returns [description, files] or nil.
|
|
315
|
+
# Best-effort: any error (no env var, missing file, bad JSON) yields nil so
|
|
316
|
+
# the replay proceeds with no code change rather than raising.
|
|
317
|
+
TRUNK_CANDIDATES = ["origin/HEAD", "origin/main", "origin/master", "main", "master"].freeze
|
|
318
|
+
CC_MAX_FILES = 60
|
|
319
|
+
CC_MAX_FILE_BYTES = 500_000
|
|
320
|
+
CC_MAX_TOTAL_BYTES = 2_000_000
|
|
321
|
+
|
|
322
|
+
# Auto-capture the code change to attach when the caller passed none. Lives
|
|
323
|
+
# in replay() (the one call guaranteed to run on every replay) so it works no
|
|
324
|
+
# matter how the replay was launched. Precedence: a BITFAB_CODE_CHANGE_PATH
|
|
325
|
+
# override, else the git diff vs trunk. Best-effort: any failure yields nil.
|
|
326
|
+
# An explicit code_change_files always wins over this.
|
|
327
|
+
def resolve_auto_code_change(label = nil)
|
|
328
|
+
return nil if ENV["BITFAB_DISABLE_CODE_CHANGE_CAPTURE"]
|
|
329
|
+
|
|
330
|
+
from_env = read_code_change_file
|
|
331
|
+
return from_env unless from_env.nil?
|
|
332
|
+
|
|
333
|
+
capture_code_change_from_git(Dir.pwd, label)
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def read_code_change_file
|
|
337
|
+
path = ENV["BITFAB_CODE_CHANGE_PATH"]
|
|
338
|
+
return nil if path.nil? || path.empty?
|
|
339
|
+
|
|
340
|
+
begin
|
|
341
|
+
parsed = JSON.parse(File.read(path))
|
|
342
|
+
files = parsed["files"]
|
|
343
|
+
# A malformed payload (non-array, or entries that aren't hashes) must
|
|
344
|
+
# yield no code change, never crash normalize_code_change_files.
|
|
345
|
+
files = nil unless files.is_a?(Array) && files.all?(Hash)
|
|
346
|
+
description = parsed["description"]
|
|
347
|
+
description = nil unless description.is_a?(String)
|
|
348
|
+
return nil if files.nil? && description.nil?
|
|
349
|
+
|
|
350
|
+
[description, files]
|
|
351
|
+
rescue
|
|
352
|
+
nil
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def git(cwd, args)
|
|
357
|
+
# capture3 so git's stderr (e.g. "not a git repository") is swallowed, not
|
|
358
|
+
# leaked to the host process's console.
|
|
359
|
+
out, _err, status = Timeout.timeout(30) do
|
|
360
|
+
Open3.capture3("git", *args, chdir: cwd)
|
|
361
|
+
end
|
|
362
|
+
return nil unless status.success?
|
|
363
|
+
|
|
364
|
+
# Tag as UTF-8 and scrub invalid bytes so blob contents compare cleanly
|
|
365
|
+
# against the UTF-8 working-file reads and never break JSON.generate later
|
|
366
|
+
# (a non-UTF-8 file that slips past the null-byte check would otherwise
|
|
367
|
+
# abort the whole start-replay request).
|
|
368
|
+
out.force_encoding("UTF-8").scrub
|
|
369
|
+
rescue
|
|
370
|
+
nil
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def cc_ref_exists?(root, ref)
|
|
374
|
+
!git(root, ["rev-parse", "--verify", "#{ref}^{object}"]).nil?
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Returns [base, from_trunk]; from_trunk is false on the HEAD fallback.
|
|
378
|
+
def cc_resolve_base(root)
|
|
379
|
+
forced = ENV["BITFAB_CODE_CHANGE_BASE"]
|
|
380
|
+
if forced && cc_ref_exists?(root, forced)
|
|
381
|
+
mb = git(root, ["merge-base", "HEAD", forced])
|
|
382
|
+
return [mb.strip, true] if mb && !mb.strip.empty?
|
|
383
|
+
|
|
384
|
+
rp = git(root, ["rev-parse", "--verify", forced])
|
|
385
|
+
return (rp && !rp.strip.empty?) ? [rp.strip, true] : nil
|
|
386
|
+
end
|
|
387
|
+
TRUNK_CANDIDATES.each do |candidate|
|
|
388
|
+
next unless cc_ref_exists?(root, candidate)
|
|
389
|
+
|
|
390
|
+
mb = git(root, ["merge-base", "HEAD", candidate])
|
|
391
|
+
return [mb.strip, true] if mb && !mb.strip.empty?
|
|
392
|
+
end
|
|
393
|
+
cc_ref_exists?(root, "HEAD") ? ["HEAD", false] : nil
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def cc_blob_bytes(root, base, path)
|
|
397
|
+
out = git(root, ["cat-file", "-s", "#{base}:#{path}"])
|
|
398
|
+
(out && !out.strip.empty?) ? out.strip.to_i : 0
|
|
399
|
+
rescue
|
|
400
|
+
0
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def cc_working_bytes(root, path)
|
|
404
|
+
File.size(File.join(root, path))
|
|
405
|
+
rescue
|
|
406
|
+
0
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def capture_code_change_from_git(cwd, label = nil)
|
|
410
|
+
root = git(cwd, ["rev-parse", "--show-toplevel"])
|
|
411
|
+
return nil if root.nil? || root.strip.empty?
|
|
412
|
+
|
|
413
|
+
root = root.strip
|
|
414
|
+
resolved = cc_resolve_base(root)
|
|
415
|
+
return nil if resolved.nil?
|
|
416
|
+
|
|
417
|
+
base, from_trunk = resolved
|
|
418
|
+
|
|
419
|
+
tracked = git(root, ["diff", "--name-status", "--no-renames", "-z", base, "--", ":!.bitfab"]) || ""
|
|
420
|
+
untracked = git(root, ["ls-files", "--others", "--exclude-standard", "-z", "--", ":!.bitfab"]) || ""
|
|
421
|
+
|
|
422
|
+
entries = cc_parse_name_status_z(tracked)
|
|
423
|
+
untracked.split("\0").reject(&:empty?).each { |p| entries << ["A", p] }
|
|
424
|
+
return nil if entries.empty?
|
|
425
|
+
|
|
426
|
+
files = []
|
|
427
|
+
total = 0
|
|
428
|
+
entries.each do |status, path|
|
|
429
|
+
break if files.length >= CC_MAX_FILES
|
|
430
|
+
|
|
431
|
+
# Skip oversized files by size BEFORE reading their full contents, so a
|
|
432
|
+
# huge changed file can't OOM/stall replay just to be discarded.
|
|
433
|
+
before_bytes = (status == "A") ? 0 : cc_blob_bytes(root, base, path)
|
|
434
|
+
after_bytes = (status == "D") ? 0 : cc_working_bytes(root, path)
|
|
435
|
+
next if before_bytes > CC_MAX_FILE_BYTES || after_bytes > CC_MAX_FILE_BYTES
|
|
436
|
+
|
|
437
|
+
# Normalize CRLF -> LF on both sides: git's blob is already LF-normalized
|
|
438
|
+
# (autocrlf clean), so a raw CRLF working file would otherwise skew every
|
|
439
|
+
# line as changed. Comparing/storing LF keeps the diff meaningful.
|
|
440
|
+
before = (status == "A") ? "" : (git(root, ["show", "#{base}:#{path}"]) || "").gsub("\r\n", "\n")
|
|
441
|
+
after = (status == "D") ? "" : cc_read_working_file(root, path).gsub("\r\n", "\n")
|
|
442
|
+
next if before == after
|
|
443
|
+
|
|
444
|
+
size = before.bytesize + after.bytesize
|
|
445
|
+
next if total + size > CC_MAX_TOTAL_BYTES ||
|
|
446
|
+
cc_looks_binary?(before) || cc_looks_binary?(after)
|
|
447
|
+
|
|
448
|
+
total += size
|
|
449
|
+
files << {"path" => path, "before" => before, "after" => after}
|
|
450
|
+
end
|
|
451
|
+
return nil if files.empty?
|
|
452
|
+
|
|
453
|
+
subject = git(root, ["log", "-1", "--format=%s", "HEAD"])
|
|
454
|
+
subject = subject ? subject.strip : ""
|
|
455
|
+
head = ((label && !label.strip.empty?) ? label.strip : nil) || (subject.empty? ? nil : subject) || "Working-tree change"
|
|
456
|
+
word = (files.length == 1) ? "file" : "files"
|
|
457
|
+
against = from_trunk ? "vs trunk" : "uncommitted (vs HEAD)"
|
|
458
|
+
["#{head} (#{files.length} #{word} changed #{against})", files]
|
|
459
|
+
rescue
|
|
460
|
+
nil
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def cc_parse_name_status_z(raw)
|
|
464
|
+
parts = raw.split("\0").reject(&:empty?)
|
|
465
|
+
out = []
|
|
466
|
+
i = 0
|
|
467
|
+
while i + 1 < parts.length
|
|
468
|
+
out << [parts[i][0], parts[i + 1]]
|
|
469
|
+
i += 2
|
|
470
|
+
end
|
|
471
|
+
out
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def cc_read_working_file(root, path)
|
|
475
|
+
File.read(File.join(root, path), encoding: "UTF-8")
|
|
476
|
+
rescue
|
|
477
|
+
""
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def cc_looks_binary?(str)
|
|
481
|
+
str[0, 8000].include?("\0")
|
|
482
|
+
end
|
|
483
|
+
|
|
298
484
|
def write_replay_result_file(result)
|
|
299
485
|
result_path = ENV["BITFAB_REPLAY_RESULT_PATH"]
|
|
300
486
|
return if result_path.nil? || result_path.empty?
|
data/lib/bitfab/version.rb
CHANGED