Class: Agentilda::Adoption

Inherits:
Object
  • Object
show all
Defined in:
lib/agentilda/adoption.rb

Overview

Gives a plan folder to every pull request that has no plan to point at.

resync prs can resolve most pull requests from their branch or their diff. What it cannot resolve it used to flag for a human — "diff touches 021.00, 022.00, 024.00 and none is obviously primary" — and stop. That is honest but not useful: the work exists, it is unspecified, and leaving it unnumbered means it never appears in the index at all.

So an orphan is adopted instead. It gets a retroactive plan of its own, numbered in the gap after the furthest plan it can see, holding the pull request that produced it.

Why the numbering is allocated serially

The obvious parallel design — every worker reads the highest number and adds .01 — is deterministic but not unique, and the collision is the common case rather than the rare one. Fifteen open pull requests branched off the same main all see the same highest number and all compute the same slot. Determinism is not the property that matters here; distinctness is.

So the pipeline is gather in parallel, allocate serially, apply in parallel. The allocation is pure arithmetic over an array that is already in memory, so the serial section costs microseconds; the two expensive phases — reading each branch and writing each folder — keep the whole fan-out. And because the slots are handed out before any worker starts, no worker can want a number another worker holds: no locks, no retries, and the same input produces the same numbers every run.

Defined Under Namespace

Classes: Adoptee

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(tree:, github: GitHub.new, root: nil, jobs: UI.default_jobs) ⇒ Adoption

Returns a new instance of Adoption.

Parameters:

  • tree (Agentilda::Tree)
  • github (Agentilda::GitHub) (defaults to: GitHub.new)
  • root (String) (defaults to: nil)

    repository root, for reading branches

  • jobs (Integer) (defaults to: UI.default_jobs)

    workers for the two parallel phases



59
60
61
62
63
64
# File 'lib/agentilda/adoption.rb', line 59

def initialize(tree:, github: GitHub.new, root: nil, jobs: UI.default_jobs)
  @tree = tree
  @github = github
  @root = root || File.dirname(tree.dir)
  @jobs = jobs
end

Instance Attribute Details

#githubAgentilda::GitHub (readonly)

Returns:



70
71
72
# File 'lib/agentilda/adoption.rb', line 70

def github
  @github
end

#jobsInteger (readonly)

Returns:

  • (Integer)


76
77
78
# File 'lib/agentilda/adoption.rb', line 76

def jobs
  @jobs
end

#rootString (readonly)

Returns:

  • (String)


73
74
75
# File 'lib/agentilda/adoption.rb', line 73

def root
  @root
end

#treeAgentilda::Tree (readonly)

Returns:



67
68
69
# File 'lib/agentilda/adoption.rb', line 67

def tree
  @tree
end

Instance Method Details

#call(pulls) ⇒ Array<Agentilda::Adoption::Adoptee>

Adopt every orphan: create its folder and record its pull request.

Parameters:

  • pulls (Array<Hash>)

Returns:



90
91
92
93
94
95
96
97
# File 'lib/agentilda/adoption.rb', line 90

def call(pulls)
  adoptees = plan(pulls)
  return adoptees if adoptees.empty?

  created = Parallel.map(adoptees, in_threads: jobs) { |adoptee| adopt(adoptee) }
  tree.reload
  created
end

#plan(pulls) ⇒ Array<Agentilda::Adoption::Adoptee>

Work out what each orphan would be given, without creating anything.

Parameters:

  • pulls (Array<Hash>)

    the orphans, from Resync::Prs

Returns:



82
83
84
# File 'lib/agentilda/adoption.rb', line 82

def plan(pulls)
  allocate(gather(pulls))
end