Class: RSpecTracer::RemoteCache::GitAncestry Private
- Inherits:
-
Object
- Object
- RSpecTracer::RemoteCache::GitAncestry
- Defined in:
- lib/rspec_tracer/remote_cache/git_ancestry.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Git ancestry walker. Given the current branch + default branch, answers “which commit should I upload cache under?” and “which prior commits should I try as cache candidates?”
Behavior preserved verbatim from 1.x ‘RemoteCache::Repo` - USER_FACING_SURFACE.md pins the 25-commit ancestry, branch_refs, history-rewrite resilience, merge-commit handling, and shallow- clone guidance as user contracts. Any change here affects every existing CI config that sets `fetch-depth: 25` or relies on the current “nearest ancestor” heuristic.
Four scenarios this class handles:
1. Main branch build (GIT_BRANCH == GIT_DEFAULT_BRANCH):
merge_base_branch! is a no-op. branch_ref = HEAD (or HEAD^1
if HEAD is itself a merge commit, e.g. main just absorbed a
feature branch via --no-ff merge). Ancestry walks HEAD^'s
linear history up to 25 commits.
2. GitHub Actions PR (checkout of refs/pull/N/merge, detached
HEAD on a synthetic merge commit):
merge_base_branch! runs `git fetch origin <branch>:<branch>`
+ `git checkout <branch>` + `git merge origin/<default>
--no-edit --no-ff`. After this HEAD is a (possibly new) merge
commit. branch_ref = HEAD^1 (the PR branch tip). Ancestry
walks HEAD^1's 25-commit history UNION `HEAD^1..origin/HEAD`
(default branch commits the PR hasn't absorbed yet).
3. Jenkins / CircleCI / Travis PR (checkout of raw PR branch):
merge_base_branch! materializes the merge commit that GHA
provides automatically. Result is identical to scenario 2.
4. PR branch behind main: same flow as 2/3. The explicit merge
ensures RSpec runs against the merged state and ancestry
picks up main's recent commits as candidates.
The merge is a WORKING-TREE MUTATION. Callers should run ‘merge_base_branch!` exactly once at the start of each Rake task invocation (download + upload each instantiate a fresh orchestrator and re-run the merge; idempotent on a clean tree). See RSPEC_TRACER.md “Caching on CI” for the user-facing rationale.
Defined Under Namespace
Classes: GitAncestryError
Constant Summary collapse
- ANCESTRY_DEPTH =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Matches 1.x. 25 commits is a 14-year-stable tradeoff between cache hit rate on slow trunks and ancestry-walk cost.
25
Instance Attribute Summary collapse
-
#branch_name ⇒ Object
readonly
private
Internal attribute.
-
#default_branch_name ⇒ Object
readonly
private
Internal attribute.
Instance Method Summary collapse
-
#ancestry_refs ⇒ Object
private
Hash=> committer_timestamp of candidate ancestor commits.
-
#branch_ref ⇒ Object
private
The SHA to upload cache under, and to seed ancestry walk from.
-
#initialize(default_branch:, branch:) ⇒ GitAncestry
constructor
private
Internal method on the tracer pipeline.
-
#merge_base_branch! ⇒ Object
private
Materialize the PR-merged state: fetch + checkout + merge default.
-
#merge_commit? ⇒ Boolean
private
True when HEAD has two parents (i.e., a merge commit).
-
#pr_build? ⇒ Boolean
private
True when this is a PR build (current branch != default branch).
Constructor Details
#initialize(default_branch:, branch:) ⇒ GitAncestry
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal method on the tracer pipeline.
66 67 68 69 70 71 72 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 66 def initialize(default_branch:, branch:) raise GitAncestryError, 'default_branch is required' if default_branch.nil? || default_branch.to_s.empty? raise GitAncestryError, 'branch is required' if branch.nil? || branch.to_s.empty? @default_branch_name = default_branch.to_s.chomp @branch_name = branch.to_s.chomp end |
Instance Attribute Details
#branch_name ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal attribute.
62 63 64 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 62 def branch_name @branch_name end |
#default_branch_name ⇒ Object (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Internal attribute.
62 63 64 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 62 def default_branch_name @default_branch_name end |
Instance Method Details
#ancestry_refs ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Hash=> committer_timestamp of candidate ancestor commits. Newest-first ordering is applied by the orchestrator when merging with branch_refs; this method returns insertion order.
Walk rule:
refs = Set[]
if merge_commit?: refs |= rev-list --max-count=25 branch_ref..origin/HEAD
refs |= rev-list --max-count=25 branch_ref
refs -= ignored_refs # drop synthetic HEAD when merged
Returns {} when the walk yields nothing (new repo, shallow clone shorter than 25, etc.). Not an error.
134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 134 def ancestry_refs return @ancestry_refs if defined?(@ancestry_refs) branch_ref # materialize ignored_refs ref_list = Set.new ref_list |= rev_list("#{@branch_ref}..origin/HEAD") if merge_commit? ref_list |= rev_list(@branch_ref.to_s) @ancestry_refs = (ref_list - @ignored_refs) end |
#branch_ref ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The SHA to upload cache under, and to seed ancestry walk from. When HEAD is a merge commit (normal PR case after merge_base_branch! runs), branch_ref = HEAD^1 (the real branch tip). Uploading under HEAD directly would key the cache under a synthetic merge SHA that no future build’s ancestry walk can reach.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 107 def branch_ref return @branch_ref if defined?(@branch_ref) head = head_ref if merge_commit? parents = merged_parents @branch_ref = parents.first @ignored_refs = [head] else @branch_ref = head @ignored_refs = [] end @branch_ref end |
#merge_base_branch! ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Materialize the PR-merged state: fetch + checkout + merge default. No-op on main builds. Idempotent on a clean tree (a subsequent ‘git merge origin/<default>` when already merged produces “Already up to date”, no new merge commit).
Raises GitAncestryError on fetch/checkout/merge failure. The orchestrator catches and logs; the user’s Rake task exits cleanly with a warning but proceeds to cold-run.
94 95 96 97 98 99 100 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 94 def merge_base_branch! return if @default_branch_name == @branch_name pull_remote_branch! if current_branch != @branch_name merge_default_branch! reset_memo! end |
#merge_commit? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
True when HEAD has two parents (i.e., a merge commit). Cached.
80 81 82 83 84 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 80 def merge_commit? return @merge_commit if defined?(@merge_commit) @merge_commit = system('git', 'rev-parse', 'HEAD^2', out: File::NULL, err: File::NULL) end |
#pr_build? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
True when this is a PR build (current branch != default branch).
75 76 77 |
# File 'lib/rspec_tracer/remote_cache/git_ancestry.rb', line 75 def pr_build? @branch_name != @default_branch_name end |